ECS 架构
ESEngine 使用实体-组件-系统(ECS)架构管理游戏对象。ECS 将身份(Entity)、数据(Component)和行为(System)分离。
什么是 ECS?
| 概念 | 角色 | 示例 |
|---|---|---|
| 实体 (Entity) | 唯一标识符(只是一个数字) | 玩家、敌人、子弹 |
| 组件 (Component) | 附加在实体上的数据 | LocalTransform、Sprite、Health |
| 系统 (System) | 操作具有特定组件的实体的逻辑 | 移动系统、渲染系统 |
为什么选择 ECS?
传统 OOP 方式:
GameObject├── Player extends GameObject│ ├── FlyingPlayer extends Player│ └── SwimmingPlayer extends Player└── Enemy extends GameObject └── FlyingEnemy extends EnemyECS 方式 — 自由组合行为:
Player = Entity + LocalTransform + Sprite + Health + PlayerInputEnemy = Entity + LocalTransform + Sprite + Health + AIBullet = Entity + LocalTransform + Sprite + Velocity + Damage优势:
- 组合 — 自由搭配组件,无需僵化的继承层级
- 数据局部性 — 同类型组件连续存储,提升缓存性能
- 关注点分离 — 系统包含逻辑,组件持有数据
- 灵活性 — 运行时添加或移除组件来改变行为
实体
实体只是一个数字(ID)。在 ESEngine 中,实体通常在场景编辑器中创建 — 可视化地放置、添加组件、配置属性。
运行时也可以通过 Commands 创建实体(如子弹、粒子):
import { defineSystem, addStartupSystem, Commands, type Entity } from 'esengine';
addStartupSystem(defineSystem([Commands()], (cmds) => { const bullet: Entity = cmds.spawn().id(); cmds.despawn(bullet);}));组件
组件是附加在实体上的数据。在代码中定义组件,然后在场景编辑器中挂载到实体上。
- 内置组件 — LocalTransform、Sprite、Camera 等(编辑器中始终可用)
- 自定义组件 — 用
defineComponent/defineTag定义(保存后出现在编辑器中)
import { defineComponent, defineTag } from 'esengine';
const Health = defineComponent('Health', { current: 100, max: 100 });const Player = defineTag('Player');定义后,在编辑器中选择实体 → 添加组件 → 从菜单中选择 Health 或 Player。
详见组件。
系统
系统是查询具有特定组件的实体并操作它们的函数。它们自动运行在场景中所有匹配的实体上。
import { defineSystem, addSystem, Res, Time, Query, Mut, LocalTransform } from 'esengine';import { Health } from './components';
addSystem(defineSystem( [Res(Time), Query(Mut(LocalTransform), Health)], (time, query) => { for (const [entity, transform, health] of query) { if (health.current <= 0) { transform.position.y -= 100 * time.delta; } } }));详见系统。
ECS 流程
┌──────────────────────┐ │ 编辑器:场景搭建 │ 放置实体,挂载组件 └──────────┬───────────┘ ▼ ┌──────────────────────┐ │ 脚本:定义 │ defineComponent + defineSystem + addSystem └──────────┬───────────┘ ▼ ┌──────────┐ │ 启动 │ 启动系统运行一次 └────┬─────┘ ▼ ┌──────────┐ │ 更新 │◄─┐ 系统每帧查询场景中的实体 └────┬─────┘ │ ▼ │ ┌──────────┐ │ │ 渲染 │ │ C++ 后端绘制所有 Sprite/Camera └────┬─────┘ │ └────────┘- 场景搭建 — 在编辑器中放置实体并挂载组件
- 脚本 — 定义自定义组件并注册系统
- 启动 — 启动系统运行一次
- 更新循环 — 系统每帧查询和处理场景中的实体
- 渲染 — C++ 后端自动渲染所有拥有
Sprite和Camera组件的实体
如何与 ECS 交互
World 存储所有实体和组件。在脚本中,通过系统参数与之交互:
| 你想做什么 | 系统参数 |
|---|---|
| 遍历具有特定组件的实体 | Query(ComponentA, ComponentB) |
| 读取组件数据 | Query(Component) — 数据随迭代获取 |
| 修改组件数据 | Query(Mut(Component)) — 用 Mut 包裹 |
| 创建新实体 | Commands() → cmds.spawn() |
| 销毁实体 | Commands() → cmds.despawn(entity) |
| 运行时添加组件 | Commands() → cmds.entity(e).insert(Component, data) |
| 运行时移除组件 | Commands() → cmds.entity(e).remove(Component) |
| 读取资源 | Res(ResourceType) |
| 修改资源 | ResMut(ResourceType) |