核心思想:逻辑层计算坐标,而非依赖 Canvas 旋转 若要用一句话概括核心,那就是:在 RayCasting 游戏里,绘制动态视野边界线(FOV),最可靠的方式是在逻辑层用数学公式把坐标“算”出来,而不是依赖 Canvas 的绘图上下文旋转。 在实现类 Doom 的 RayCasting 游戏时,
JavaScript实现旋转视野FOV射线绘制
时间:2026-07-06
浏览:582
文章目录
核心思想:逻辑层计算坐标,而非依赖 Canvas 旋转
若要用一句话概括核心,那就是:在 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)
};
}
具体实现:从玩家顶点计算 FOV 射线
假设玩家实体是个宽高均为 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 游戏底层可控性的关键所在。
实用工具推荐
绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
选择画质和输出格式,预览压缩前后的大小。