跳转到内容

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 Enemy

ECS 方式 — 自由组合行为:

Player = Entity + LocalTransform + Sprite + Health + PlayerInput
Enemy = Entity + LocalTransform + Sprite + Health + AI
Bullet = 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');

定义后,在编辑器中选择实体 → 添加组件 → 从菜单中选择 HealthPlayer

详见组件

系统

系统是查询具有特定组件的实体并操作它们的函数。它们自动运行在场景中所有匹配的实体上。

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
└────┬─────┘ │
└────────┘
  1. 场景搭建 — 在编辑器中放置实体并挂载组件
  2. 脚本 — 定义自定义组件并注册系统
  3. 启动 — 启动系统运行一次
  4. 更新循环 — 系统每帧查询和处理场景中的实体
  5. 渲染 — C++ 后端自动渲染所有拥有 SpriteCamera 组件的实体

如何与 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)

详见系统查询

下一步

  • 组件 — 内置和自定义组件
  • 系统 — 定义和调度系统
  • 查询 — 查询实体
  • 资源 — 全局单例数据