ECS 思维模型
在写第一行 Bevy 代码之前,值得先花十分钟建立 ECS 的思维模型。这十分钟会在之后的每一章持续产生回报——Bevy 里几乎所有 API 的形状,都是这个模型的直接推论。
从继承树到组合
传统面向对象引擎的经典做法是继承:Player 继承 Character,Character 继承 GameObject。项目小的时候这很自然,长大之后问题就来了——“会飞的载具”和“能驾驶的飞行生物”该挂在树的哪个分支?基类越长越胖,每个子类只用得上其中一小部分。
ECS 的回答是放弃树,改用组合:一个游戏对象不“是”什么,而是“有”什么能力的集合。
世界是一张表
把游戏世界想象成一张表。用第 20 章我们要亲手实现的打砖块游戏(Breakout)举例:
Transform | Velocity | Sprite | Health | |
|---|---|---|---|---|
| 挡板 | ✓ | ✓ | ✓ | |
| 球 | ✓ | ✓ | ✓ | |
| 砖块 ×56 | ✓ | ✓ | ✓ |
ECS 的三个要素就是这张表的三个视角:
- Entity(实体):表的“行号”。它只是一个 ID,本身不携带任何数据和行为——挡板、球、每一块砖,各是一个 Entity。
- Component(组件):表的“列”。纯数据,按需挂到实体上:位置(
Transform)、速度(Velocity)、外观(Sprite)、耐久(Health)。 - System(系统):对表的“查询 + 处理”。它是一个普通函数,声明自己关心哪些列,引擎把所有匹配的行喂给它。
比如“移动”这个逻辑写成一个 System:给我所有同时具有 Transform 和 Velocity 的实体,我把速度累加到位置上。它自然作用于挡板和球,自动跳过砖块——没有任何 if 判断对象类型的代码。想让砖块也能动?给它插上 Velocity 这一列就行,不需要动任何继承关系。
这个模型还附带一个不那么显眼的转变:控制反转。你不再编写“主循环”逐个调用对象的 update(),而是注册一批规则(System),引擎每帧替你调度它们。你的代码从“指挥一切”变成“声明规则”。
为什么 ECS 和 Rust 般配
这张“表”的比喻在 Rust 里有两个具体的技术回报:
借用规则变成并行调度的依据。 每个 System 的函数签名都精确声明了它读哪些列、写哪些列。Bevy 的调度器据此判断哪些 System 互不冲突,自动让它们在多个线程上并行运行——你不写一行线程代码。Rust 的“共享只读、独占可写”借用规则,在这里从编译期的约束升级成了运行时的并行化方案。
数据布局对缓存友好。 同类组件在内存中连续存储,System 顺序扫过它们时缓存命中率高。这是 ECS 引擎高性能的主要来源之一,细节在第 11 章展开。
模型就建立到这里。它现在还有点抽象——下一节把环境装好,第 2 章你就能亲眼看到这张“表”跑起来。