我正在用 Node.js 构建游戏引擎(真的)
I’m happy to translate the article for you, but I’ll need the full text of the post (the parts you want translated) pasted here. Please provide the article’s content, and I’ll return a Simplified‑Chinese version while keeping the source line, formatting, markdown, and any code blocks or URLs unchanged.
虽然标题听起来很疯狂……但它真的在发生。
而且看起来相当不错。
结果
类 Godot 的场景树
双屏 + 剔除
物理
带程序化生成地图的角色控制器
一旦你阅读了 Node.js 源码——或者说任何运行时的源码——你就会意识到这些东西是多么新颖且强大。此时你根本停不下来;你必须去构建它。
于是 Nexus 就这样诞生了,它同样基于另一个项目:一个用于 Node.js 的 C++ N‑API 渲染器。更多细节请见这里:
How I built a Renderer for Node.js
但每个项目都有它的问题。我会说它大约 70 % 是样板代码,30 % 是真正的编程,而这 30 % 正是新颖、痛苦、决策密集的部分。
这 30 % 中很大一块是 物理 和 相机。
Spoiler: 非常困难。
公平地说,我之前在 C 语言里从零构建过物理引擎,但 JavaScript 是另一回事。它表达力较弱、采用垃圾回收、单线程,并且带来了全新的问题领域。
尽管如此……它看起来很不错。
我还有一个激进的优化计划——见 I Tried to Beat WebAssembly With Node.js。
摄像机
问题: 你的屏幕是静止的。它不移动,也不缩放。
那么当玩家位于 屏幕尺寸 + 1 像素 时会怎样?
- 世界是无限的。
- 屏幕是有限的。
我无法移动真实的屏幕,但如果我创建一个 伪屏幕 呢?一个模拟的、不是实际的屏幕,可以自由移动。
World (large area)
+-------------------------------------------------------------+
| |
| . . . . . . . . . . . . . . . . . . . . . . . . . . . |
| |----------------------- Screen ---------------------| |
| | | |
| | | |
| | | |
| | | |
| | | |
| |---------------- (real screen) -------------------| |
| ^ |
| | |
| (player P) |
| P |
| C|
| [Camera viewport] ------------------ |
| .----------------------------------. |
| | | |
| | CAMERA (C) | |
| | | |
| '----------------------------------' |
| |
+-------------------------------------------------------------+
图例:
- 大方框 = 世界
- 标记为 “Screen” 的内部矩形 = 真实屏幕(位于世界内部)
- “P” = 位于真实屏幕外的玩家点
- 靠近 P 的虚线/较小矩形标记为 Camera = 可以自由移动的伪屏幕/摄像机视口
进入全屏模式 退出全屏模式
伪屏幕看到的内容会被映射到真实屏幕上。这就是你的 视锥 → 视口 → 屏幕 转换。
听起来很简单,直到你意识到现在每个对象和组件都有 两个坐标系:
| 系统 | 原点 |
|---|---|
| 左上角 | 渲染器 |
| 自由流动、基于中心 | 摄像机 |
真正的痛点在于在这些系统之间进行 相互转换。而且这还不是全部。
摄像机一旦存在,就必须有用:
- 一个能够平滑跟随玩家的控制器,
- 剔除视锥/视口之外的像素,
- 将每个像素转换为屏幕空间(如有需要,还要能逆向转换)。
在翻译的疯狂之上,你还要加入跟随行为、抖动、剔除、多摄像机……
不过,正如你在角色控制器演示中看到的,它看起来已经相当不错了。
物理引擎
之前
这很难,不仅因为它是实时的,还因为它是模拟实时的。事物移动得很快。事物碰撞得很快。你不能靠 console.log 来解决这个问题。
所以这一次我选择了 Matter.js —— 纯 JavaScript 的稳固库。它能工作。
直到我意识到它不支持 运动学刚体(kinematic bodies),而这几乎是每个 2D 游戏的核心:你的传感器、AI、移动平台以及更多。
引擎通常提供的刚体类型
| 类型 | 描述 |
|---|---|
| Static | 不可移动;用于地形/场景,不受力的影响 |
| Dynamic | 完全模拟;受力和碰撞影响 |
| Kinematic | 通过脚本/控制移动;会碰撞但不受物理力驱动 |
运动学刚体是游戏玩法的核心。
但我是一名开发者,于是我想:
“很简单。运动学刚体就是会移动的静态刚体。”
巨大的错误。
静态刚体不会与其他静态刚体碰撞(例如,在这种情况下子弹不会与运动学敌人或移动平台碰撞),因为引擎假设 static 意味着永不移动。现在我只能硬改 Matter.js,自己手动处理碰撞。
结果并不理想。
我在模拟器之外进行模拟,把“受污染”的结果再喂回去,希望物理引擎能配合。
这当然是可行的。
但也极其烦人。
于是我没有再在这上面浪费四天,而是做了更理智的选择,换用了 planck.js,它实际上支持我需要的所有功能。
使用 Planck.js 带来的新问题
-
Planck 的坐标系是 左下角。
-
我现在必须在 三 种系统之间进行转换:
- 左上角 ← 渲染器
- 中间中心 ← 相机
- 左下角 ← planck.js
-
Planck 使用 米 为单位,而渲染器和相机使用 像素。
这又是一个翻译层。
所以我现在正在处理的事情是:迁移到新的物理引擎,构建全新的坐标转换层……并且仍然乐在其中。




