
本文详解 p5.js 中因坐标系混淆导致的“僵尸奔向原点”问题,指出核心错误在于渲染时误将世界坐标直接叠加而非转换为屏幕坐标,并提供修正后的向量计算与渲染方案。
本文详解 p5.js 中因坐标系混淆导致的“僵尸奔向原点”问题,指出核心错误在于渲染时误将世界坐标直接叠加而非转换为屏幕坐标,并提供修正后的向量计算与渲染方案。
在 p5.js 开发 2D 追逐类游戏(如僵尸追玩家)时,一个常见却隐蔽的 bug 是:僵尸看似在追逐玩家,实则不断绕着画布原点 (0, 0) 打转,甚至最终堆叠在左上角。问题根源往往不在运动逻辑本身,而在于坐标空间的理解与转换失误。
回顾原始代码中的 ZombieHandler.render() 方法:
rect(zombie[0] + Player.posX + winX / 2,
zombie[1] + Player.posY + winY / 2,
30, 30, 5, 5, 5, 5);
这段代码存在严重逻辑错误:
- zombie[0] 和 zombie[1] 是僵尸在世界坐标系中的绝对位置(例如 [2000, -500]);
- Player.posX/posY 同样是玩家在世界坐标系中的位置;
- 而 winX / 2, winY / 2 是屏幕中心偏移量——但此处直接相加,相当于把两个世界坐标强行映射到屏幕,完全忽略了相机平移(camera offset) 的作用。
✅ 正确做法是:所有渲染坐标必须基于“相机视角”进行转换。即:
屏幕坐标 = 世界坐标 − 相机坐标 + 画布中心
由于你的 Player.control() 已返回更新后的 cameraX/cameraY(代表世界坐标系中“画面中心”对应的位置),因此每个僵尸的屏幕位置应为:
const screenX = zombie[0] - cameraX + winX / 2; const screenY = zombie[1] - cameraY + winY / 2; rect(screenX, screenY, 30, 30, 5, 5, 5, 5);
同时,确保 ZombieHandler.control() 中的朝向计算准确无误(它本身是正确的):
// ✅ 正确:计算从僵尸指向玩家的方向角(弧度) let angle = Math.atan2(Player.posY - zombie[1], Player.posX - zombie[0]); zombie[0] += zombie[2] * Math.cos(angle); // 水平位移 zombie[1] += zombie[2] * Math.sin(angle); // 垂直位移
⚠️ 注意事项:
- 不要在 draw() 中重复绑定键盘事件:document.onkeydown 等应移至 setup() 中初始化一次,否则每帧重绑造成性能浪费且行为不可预测;
- 避免浮点累积误差:长期运行后 Player.posX/Y 可能偏离预期,建议对边界做软钳制或使用 constrain();
- 防重叠优化:若僵尸数量增多,可引入简易分离力(如 p5.Vector.sub(zombieA.pos, zombieB.pos).limit(5)),参考 p5.js Flocking 示例;
- 调试技巧:临时用 text() 显示关键坐标(如 zombie[0], zombie[1], cameraX, cameraY),验证数值是否符合预期。
最后,精简并重构后的 ZombieHandler 核心逻辑如下:
class ZombieHandler {
constructor(list) {
this.list = list;
}
render(cameraX, cameraY) { // 接收相机参数,而非 Player 实例
for (let z of this.list) {
const sx = z[0] - cameraX + width / 2; // width/height 替代 winX/winY 更健壮
const sy = z[1] - cameraY + height / 2;
fill(0);
rect(sx, sy, 30, 30, 5);
}
}
control(Player) {
for (let z of this.list) {
const dx = Player.posX - z[0];
const dy = Player.posY - z[1];
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 1) { // 避免除零 & 贴脸抖动
z[0] += (dx / dist) * z[2];
z[1] += (dy / dist) * z[2];
}
}
}
}
通过厘清世界坐标、相机坐标与屏幕坐标的三层关系,并统一使用向量归一化方式更新位置,即可彻底解决“僵尸绕原点旋转”的假性追逐问题,让追逐逻辑真正可靠、可扩展。











