
本文详解如何在JavaScript Canvas中绕指定中心点旋转坐标,精准绘制角色视野边界射线,解决context.rotate()不更新逻辑坐标的痛点,并提供可复用的二维点旋转数学工具函数。
本文详解如何在javascript canvas中绕指定中心点旋转坐标,精准绘制角色视野边界射线,解决context.rotate()不更新逻辑坐标的痛点,并提供可复用的二维点旋转数学工具函数。
在开发类Doom的2D光线投射(Ray Casting)游戏时,实现角色视野(Field of View, FOV)的核心在于:以玩家位置为顶点,向左右视野边缘发射两条射线。但直接调用 ctx.rotate() 仅影响后续绘图的变换矩阵,不会改变你代码中维护的几何坐标——这意味着你无法通过 rotate() 获取旋转后的真实端点来绘制 FOV 边界线,也无法用于后续的光线碰撞检测。
正确的做法是:手动计算坐标旋转。即对 FOV 的两个边缘点(例如:相对于玩家朝向偏移 ±30° 的单位方向点),应用标准的二维绕点旋转公式:
$$ \begin{cases} x' = c_x + (x - c_x)\cos\theta - (y - c_y)\sin\theta \ y' = c_y + (x - c_x)\sin\theta + (y - c_y)\cos\theta \end{cases} $$
其中 $(c_x, c_y)$ 是旋转中心(如玩家位置),$(x, y)$ 是原始点,$\theta$ 是旋转角度(弧度)。
以下是一个生产就绪的工具方法,封装了该逻辑:
// 将点 point 绕中心 centro 逆时针旋转 this.radians(vel) 弧度
rotationMath(point, centro, vel) {
const rad = this.radians(vel); // 假设 this.radians() 能将角度/速度转为弧度
const dx = point.x - centro.x;
const dy = point.y - centro.y;
return {
x: centro.x + dx * Math.cos(rad) - dy * Math.sin(rad),
y: centro.y + dx * Math.sin(rad) + dy * Math.cos(rad)
};
}
使用示例(绘制 FOV 三角形边界):
// 假设 this.x, this.y 是玩家中心坐标;fovAngle = 60°(总视角)
const center = { x: this.x + this.width / 2, y: this.y + this.height / 2 };
const halfFov = this.radians(30); // 单侧30度
// 定义视野“远平面”距离(如150像素)
const viewDist = 150;
// 计算左、右视野边缘点(从中心沿当前朝向±halfFov延伸)
const forwardDir = { x: Math.cos(this.radians()), y: Math.sin(this.radians()) };
// 左边缘方向(朝向左偏 halfFov)
const leftDir = {
x: Math.cos(this.radians() - halfFov),
y: Math.sin(this.radians() - halfFov)
};
// 右边缘方向
const rightDir = {
x: Math.cos(this.radians() + halfFov),
y: Math.sin(this.radians() + halfFov)
};
// 得到实际端点坐标
const leftEnd = {
x: center.x + leftDir.x * viewDist,
y: center.y + leftDir.y * viewDist
};
const rightEnd = {
x: center.x + rightDir.x * viewDist,
y: center.y + rightDir.y * viewDist
};
// 绘制 FOV 区域(可选填充)或仅画两条射线
this.desenho.beginPath();
this.desenho.moveTo(center.x, center.y);
this.desenho.lineTo(leftEnd.x, leftEnd.y);
this.desenho.moveTo(center.x, center.y);
this.desenho.lineTo(rightEnd.x, rightEnd.y);
this.desenho.strokeStyle = '#4a90e2';
this.desenho.lineWidth = 1.5;
this.desenho.stroke();
⚠️ 注意事项:
-
永远避免依赖
ctx.rotate()来获取几何数据:它只作用于渲染管线,不改变你的世界坐标;所有碰撞、光线追踪、UI定位等必须基于手动计算的逻辑坐标; -
this.radians()应确保返回标准弧度值(如angle * Math.PI / 180); - 若需动态 FOV(如奔跑时变宽),可将
halfFov设为变量并随状态更新; - 后续扩展可基于此结构添加多条 FOV 射线(如每 1° 一条)用于光线投射采样。
掌握手动坐标旋转变换,是你构建稳定、可预测、可调试的2D光追引擎的关键一步——它让“视野”真正成为可计算、可交互的游戏机制,而非仅视觉特效。











