系统
系统是每帧运行的函数。你声明它需要什么——一个组件查询,加上资源和事件—— Estella 就会在调度中用恰好这些参数调用它。
import { defineComponent, defineSystem, addSystem, Query, Mut, Res, Time, Transform} from 'esengine';
const Speed = defineComponent('Speed', { value: 200 });
addSystem(defineSystem( [Res(Time), Query(Mut(Transform), Speed)], (time, query) => { for (const [entity, transform, speed] of query) { transform.position.x += speed.value * time.delta; } }));系统的第一个参数是它的参数列表——它将按顺序收到的数据:
Query(...)—— 遍历同时拥有列出的每个组件的实体。每次迭代产出[entity, ...组件]。Mut(Component)—— 系统会写入的组件。未包裹的组件是只读的。Res(Resource)/ResMut(Resource)—— 一个共享资源,只读或可写(例如提供帧计时的Time)。Commands()—— 延迟的结构性改动(生成 / 销毁 / 增删组件),在下次 flush 时应用。EventReader(E)/EventWriter(E)—— 读取或发送一条类型化事件流。
系统活在由有序阶段组成的**调度(schedule)**里。用 addSystem 注册进默认的
Update 阶段,用 addStartupSystem 注册进 Startup(只跑一次),或用
addSystemToSchedule(Schedule.X, system) 注册进任意阶段:
| 阶段 | 何时运行 |
|---|---|
Startup |
首帧之前,一次。 |
First → PreUpdate → Update → PostUpdate → Last |
每帧,按此顺序。 |
FixedPreUpdate → FixedUpdate → FixedPostUpdate |
固定步长(物理 / 复制节拍)——每帧 0..n 次。 |
同一阶段内,系统按注册顺序运行。要约束顺序,在 App 上带排序选项注册——它们按系统
名字引用,所以给要排序的系统起名:
import { Schedule } from 'esengine';
const move = defineSystem([...], moveFn, { name: 'move' });const camera = defineSystem([...], cameraFn, { name: 'camera' });
app.addSystemToSchedule(Schedule.Update, move);app.addSystemToSchedule(Schedule.Update, camera, { runAfter: ['move'], // 相机跟在移动之后 runIf: () => isPlaying, // 条件为 false 时跳过该系统});场景系统只在其所属场景处于激活状态时运行,因此多个场景可以共存于同一个世界而互不干扰。
过滤与变更检测
Section titled “过滤与变更检测”查询可以在“拥有这些组件”之外进一步收窄,并对变更做出反应:
// 额外的存在性过滤——要求/排除但不读取的组件:Query(Transform, Mut(Velocity)).without(Frozen).with(Enemy)
// 用 filter() 做布尔组合:import { Query, With, Without, Or, Not } from 'esengine';Query(Transform).filter(Or(With(Player), With(Ally)))
// 变更检测——只要该组件自本系统上次运行以来被添加/写过的实体:import { Query, Added, Changed } from 'esengine';Query(Added(Health)) // 本帧新增Query(Changed(Transform)) // 上次运行后被写过Removed(C) 是它自己的查询,产出丢失了 C 的实体:
import { defineSystem, Removed } from 'esengine';defineSystem([Removed(Health)], (removed) => { // Health 是你自己的组件 for (const entity of removed) { /* 清理 */ }});查询结果在 for…of 之外还有便捷方法:
| 方法 | 返回 |
|---|---|
query.single() |
唯一的匹配(否则 null)——用于单独的玩家/相机。 |
query.count() / query.isEmpty() |
匹配数 / 是否为空。 |
query.toArray() |
所有结果组成数组。 |
query.forEach((e, ...c) => …) |
每个匹配调用一次回调。 |
事件是一条类型化、解耦的消息流——一个系统 send,另一个下一帧读取,两者之间无直接引用。
定义一个事件,在 app 上注册,然后把 EventWriter / EventReader 作为系统参数:
import { defineEvent, defineSystem, EventWriter, EventReader, Commands } from 'esengine';
const Damaged = defineEvent<{ entity: number; amount: number }>('Damaged');app.addEvent(Damaged);
// 生产者:addSystem(defineSystem([EventWriter(Damaged)], (damaged) => { damaged.send({ entity: 12, amount: 5 });}));
// 消费者(读取自上次运行以来发送的):addSystem(defineSystem([EventReader(Damaged), Commands()], (damaged, cmds) => { for (const evt of damaged) { /* evt.entity、evt.amount */ }}));reader 可迭代,还提供 .isEmpty() / .toArray()。完整的生产者/消费者模式见
event-system 示例。