如果用一句话概括核心,那就是:在 RayCasting 游戏开发中,绘制动态视野边界线(FOV)最可靠的方式是在逻辑层通过数学公式将坐标“算”出来,而不是依赖 Canvas 绘图上下文的旋转操作。
在实现类似 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),那么它的四个顶点分别是:左上、右上、右下、左下。视野通常取自前向两侧的边缘,例如将右上角和左上角作为 FOV 的起始点(相当于“眼睛”位于顶部中点,视野张角由这两个点张开)。首先确定旋转中心——比如取顶部中点 cCenter = {x: this.x + size/2, y: this.y}——然后对两个关键顶点应用旋转函数:
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和Math.sin的值,避免每帧重复计算,提升渲染效率。
通过手动执行坐标旋转,你不仅获得了可预测、易调试的 FOV 几何数据,还为后续的光线投射、碰撞检测以及 HUD 对齐奠定了坚实的数据基础。归根结底,这才是 RayCasting 游戏底层可控性的关键所在。
