如何在 JavaScript 中实现基于旋转的视野FOV射线绘制

作者:袖梨 2026-06-30

本文讲解如何在不依赖 Canvas 2D 上下文 rotate() 方法的前提下,通过数学旋转公式精确计算立方体顶点坐标,从而绘制出随角色朝向动态变化的视野边界线(FOV),适用于 RayCasting 游戏开发。

本文讲解如何在不依赖 canvas 2d 上下文 `rotate()` 方法的前提下,通过数学旋转公式精确计算立方体顶点坐标,从而绘制出随角色朝向动态变化的视野边界线(fov),适用于 raycasting 游戏开发。

在实现类 Doom 的 RayCasting 游戏时,一个核心视觉元素是动态视野(Field of View, FOV)——通常表现为从玩家视角出发、沿角色朝向左右张开的两条射线,模拟人眼或摄像机的可视锥形区域。但直接调用 ctx.rotate() 会改变绘图上下文的坐标系,却不更新原始几何数据,导致后续碰撞检测、光线投射或 UI 定位失效。因此,必须在逻辑层完成坐标变换,而非仅依赖渲染层旋转。

关键在于:将立方体(如玩家代表的方块)的局部顶点坐标,绕其中心点按当前朝向角(弧度制)进行二维平面旋转变换。标准旋转公式如下(以点 P(x,y) 绕中心 C(cx,cy) 逆时针旋转 θ 弧度):

function rotatePoint(point, center, angleRad) {  const dx = point.x - center.x;  const dy = point.y - center.y;  return {    x: center.x + dx * Math.cos(angleRad) - dy * Math.sin(angleRad),    y: center.y + dx * Math.sin(angleRad) + dy * Math.cos(angleRad)  };}

假设你的玩家实体是一个宽高均为 size 的正方形,其左上角位于 (this.x, this.y),则四个顶点为:

  • 左上:(this.x, this.y)
  • 右上:(this.x + size, this.y)
  • 右下:(this.x + size, this.y + size)
  • 左下:(this.x, this.y + size)

视野通常取前向两侧边缘,例如取右上角和左上角作为 FOV 起始点(即“眼睛”位置在顶部中点,视野张角由这两点张成)。先确定旋转中心(如取顶部中点 cCenter = {x: this.x + size/2, y: this.y}),再对两个关键顶点(如右上 (this.x + size, this.y) 和左上 (this.x, this.y))应用上述函数:

立即学习“Java免费学习笔记(深入)”;

const cCenter = { x: this.x + this.width / 2, y: this.y };const rightTop = { x: this.x + this.width, y: this.y };const leftTop  = { x: this.x, y: this.y };const rotatedRight = this.rotatePoint(rightTop, cCenter, this.radians());const rotatedLeft  = this.rotatePoint(leftTop,  cCenter, this.radians());// 绘制 FOV 射线:从中心出发,延伸至足够远(如 300px)const length = 300;const fovRightX = cCenter.x + (rotatedRight.x - cCenter.x) * (length / Math.hypot(rotatedRight.x - cCenter.x, rotatedRight.y - cCenter.y));const fovRightY = cCenter.y + (rotatedRight.y - cCenter.y) * (length / Math.hypot(rotatedRight.x - cCenter.x, rotatedRight.y - cCenter.y));const fovLeftX = cCenter.x + (rotatedLeft.x - cCenter.x) * (length / Math.hypot(rotatedLeft.x - cCenter.x, rotatedLeft.y - cCenter.y));const fovLeftY = cCenter.y + (rotatedLeft.y - cCenter.y) * (length / Math.hypot(rotatedLeft.x - cCenter.x, rotatedLeft.y - cCenter.y));// 渲染this.desenho.beginPath();this.desenho.strokeStyle = "#4a90e2";this.desenho.moveTo(cCenter.x, cCenter.y);this.desenho.lineTo(fovRightX, fovRightY);this.desenho.moveTo(cCenter.x, cCenter.y);this.desenho.lineTo(fovLeftX,  fovLeftY);this.desenho.stroke();

⚠️ 注意事项:

  • this.radians() 必须返回 弧度值(若存储为角度,需先乘以 Math.PI / 180);
  • 所有坐标计算应在 draw() 前完成,确保逻辑与渲染解耦;
  • 若需更真实 FOV(如固定张角 60°),应独立定义视角半角 fovHalf = Math.PI / 6,再以 cCenter 为原点、this.radians() 为基准方向,分别计算 ±fovHalf 的射线终点,而非依赖模型顶点;
  • 性能敏感场景建议预缓存 Math.cos/sin 值,避免每帧重复计算。

通过手动坐标旋转,你不仅获得了可预测、可调试的 FOV 几何,还为后续光线投射、碰撞检测和 HUD 对齐奠定了坚实的数据基础——这才是 RayCasting 游戏底层可控性的关键所在。

相关文章

精彩推荐