游戏引擎源码阅读
概述
引擎文档教你"怎么用",引擎源码告诉你"为什么这样设计"。读源码不必逐行通读,抓三条主线即可获得最大收益:
- 主循环:一帧内发生了什么事、顺序如何
- 生命周期:脚本/组件在什么时机被引擎调用
- 调度模型:对象如何被组织、更新、渲染
本文以 Phaser、Cocos Creator、Unity 三款引擎为例,分别剖析它们的核心机制。
Phaser:游戏循环源码
Phaser 是典型的"框架型" H5 引擎,核心类 TimeStep 管理循环,Game 驱动场景调度。
简化主循环
Phaser 的 Game.step() 是每帧的入口,内部顺序大致为:
// Game.step() 简化逻辑
step(time, delta) {
this.time.update(time, delta) // 时间系统:dt 计算、时钟、定时器
this.inputManager.update() // 输入系统:合并键盘/鼠标/触摸
this.scene.update() // 场景逻辑更新
this.renderer.render(this.scene) // 渲染
}关键点在于场景的 update 与 render 分离:
scene.update()会遍历场景内所有对象的preUpdate → update → postUpdaterenderer.render()才真正提交绘制
TimeStep 的设计
TimeStep 负责把 requestAnimationFrame 的时间戳换算成稳定的 delta:
// TimeStep 核心:计算与钳制 delta
step(time) {
let delta = time - this.lastTime
delta = Math.min(delta, this.maxFps) // 钳制单帧上限,防止切后台回来"跳秒"
this.lastTime = time
this.delta = delta
// 可选的固定步长模式(forceSetTimeOut / smoothStep)
}这与游戏循环与时间管理中讲过的"Delta Time 钳制"完全对应——引擎把这套方案内置成了基础设施。读 Phaser 源码时建议从 Game.step() 和 TimeStep 两个类入手。
Cocos Creator:组件系统
Cocos Creator 采用"实体(Node)+ 组件(Component)"架构:节点是纯位置容器,行为全部由挂载的组件提供。这与 游戏设计模式与架构 中 ECS 的分工理念同源。
组件生命周期
Cocos 的 Component 有明确的生命周期钩子,由引擎在固定时机调用:
class MyBehavior extends Component {
onLoad() { } // 节点激活、组件就绪时调用一次
start() { } // 首次 update 前调用
update(dt) { } // 每帧调用:逻辑更新
lateUpdate(dt) { } // update 之后:相机跟随等依赖其他组件结果的逻辑
onDestroy() { } // 销毁时清理
}引擎如何驱动组件
组件本身不知道"自己何时被调用"——引擎在每一帧遍历节点树,对每个节点的组件列表依次调用对应钩子:
// 引擎内部(简化):每帧调度
nodeTree.forEach(node => {
for (const comp of node.components) {
comp.update(dt) // 组件的 update 由引擎统一驱动
}
})update 调用顺序取决于组件的 执行优先级(executionOrder)——优先级小的先执行,避免组件间时序冲突。理解"引擎遍历组件调用钩子"这一模型,就抓住了组件系统的本质。
Unity:核心流程
Unity 的脚本基于 C# 的 MonoBehaviour,生命周期钩子比 Cocos 更细分,最核心的是时间相关的两个:
| 钩子 | 频率 | 用途 |
|---|---|---|
FixedUpdate | 固定步长(默认 50Hz) | 物理计算、刚体移动,保证稳定 |
Update | 每帧 | 常规游戏逻辑 |
LateUpdate | 每帧(Update 之后) | 相机跟随等收尾逻辑 |
Unity 主循环(PlayerLoop)的简化顺序:
FixedUpdate(固定步长,N 次)
↓
Update(每帧一次)
↓
LateUpdate(每帧一次)
↓
渲染(Culling → Rendering → Post Processing)Unity 的引擎性(渲染、物理、动画)大量用 C++ 实现,C# 脚本只是"每帧被调用的钩子"。FixedUpdate 与 Update 的区分是理解 Unity 的关键:物理相关的移动放在 FixedUpdate,表现相关的逻辑放在 Update。
三引擎核心机制对比
| 维度 | Phaser | Cocos Creator | Unity |
|---|---|---|---|
| 语言 | JavaScript | TypeScript | C# |
| 核心架构 | 场景 + 对象树 | 节点 + 组件 | GameObject + MonoBehaviour |
| 循环驱动 | requestAnimationFrame | 引擎内部主循环 | PlayerLoop(C++) |
| 更新钩子 | preUpdate/update/postUpdate | start/update/lateUpdate | FixedUpdate/Update/LateUpdate |
| 固定步长 | TimeStep 可选 | 物理系统固定步长 | 物理固定步长 |
| 组件复用 | 场景对象挂脚本 | 组件可拖拽到任意节点 | 组件拖拽到任意 GameObject |
三条主线贯穿三款引擎:主循环驱动 → 生命周期钩子 → 组件/脚本调度。无论引擎叫什么名字,理解"引擎每帧都做了什么、在什么时机调用我的代码"就掌握了源码阅读的第一把钥匙。
组件系统示例
下面的可运行示例用"对象 + 组件"架构实现了引擎组件系统的核心:每个 GameObject 挂载若干组件(Rotator 自转、ScalePulse 缩放、Orbit 公转、Mover 移动),引擎每帧遍历所有对象、依次调用组件的 update()。点击场景中的对象,可以在检查器中查看它挂载的组件与实时属性:
这正是 Cocos 的 Component.update 与 Unity 的 MonoBehaviour.Update 在运行时做的事——理解了这个小例子,再看引擎文档里的生命周期图就会豁然开朗。