游戏世界的构成
| 类别 | 举例 | 引擎中的抽象 |
|---|---|---|
| 动态物 | 坦克、无人机、NPC | GameObject(GO) |
| 静态物 | 房屋、机棚、量网塔 | GameObject |
| 环境 | 地形、天空、植被 | 独立系统(地形系统、TOD) |
| 不可见物体 | 空气墙、触发器、规则 | GameObject |
结论:一切皆 GameObject(简称 GO)。
Component-Based 架构
为什么不用深继承
- 多重继承菱形问题(水陆坦克到底是“车”还是“船”?)
- 代码膨胀、难维护
组件化解法
把“属性”与“行为”拆成独立 Component,按需拼装:
| 无人机示例 | 对应组件 |
|---|---|
| 空间位置 | Transform |
| 外形 | Model/Mesh |
| 移动能力 | Motor |
| 血量 | Health |
| AI 巡逻 | AI_Patrol |
| 攻击行为 | Combat |
优势:
- 组合 > 继承
- 运行时动态增删(Unity/UE)
- 数据与行为解耦,利于并行与批量处理(后续 ECS)
让世界动起来:Tick 机制
- Tick = 引擎每隔固定帧间隔(如 33 ms)推动世界一步
- 每个 Component 实现
Tick(float deltaTime):- Motor 更新位置
- Animation 更新骨骼
- Physics 更新碰撞体
- Combat 检测开火
- 现代引擎为提高缓存命中率,按系统批量 Tick(先 Tick 所有 Motor,再 Tick 所有 Animation…)
对象间通信:Event 系统
| 直接调用(紧耦合) | 事件邮件(松耦合) |
|---|---|
| 炮弹爆炸→逐个调用周围对象.takeDamage() | 炮弹爆炸→PostEvent(“Damage”, 100)→周围对象次日 Tick 时查收邮件并扣血 |
- 邮箱模型 = 确定性 + 可扩展
- 实现细节:字符串哈希 / RTTI / 反射(UE Blueprint、Unity SendMessage)
场景管理:从暴力遍历到空间加速
| 方法 | 核心思想 | 适用场景 |
|---|---|---|
| 暴力 O(n²) | 每帧遍历全体对象 | 小 demo |
| 均匀网格 | 世界打格子,只查邻近格 | 对象分布均匀 |
| 四叉树 / 八叉树 | 递归细分,空区域不再分 | 2D/3D 稀疏世界 |
| BVH | 按包围盒自底向上合并 | 大量动态物体,更新快 |
引擎通常内置多种结构,供产品根据场景特点切换。
进阶话题
| 问题 | 常用策略 |
|---|---|
| 事件顺序问题 | 引入邮局概念,而不是每个组件都随便触发事件或者监听事件 |
| Tick 过长 | 时间补偿 / 跳帧 / 分批处理(differed processing) |
| 渲染线程与逻辑线程同步 | 逻辑先 Tick→写快照→渲染线程消费,额外延迟 1~2 帧 |
| 组件模式缺点 | 查询开销、缓存不友好→高级课引入 ECS(SOA+System 批量) |
| 物理与动画耦合 | 动画提供初始姿态→物理模拟→动画 Blend 回插值,实现“真实倒地带感” |
| 事件调试 | 可视化邮件:3D 悬浮文字、逐帧 Pause、Log 洪流 |
一句话总结
游戏世界 = GameObject 容器 + Component 拼装 + Tick 推进 + Event 通信 + 空间索引 加速。
理解这五步,你就拥有了手写引擎的“地图”。