跳转到内容

系统

系统包含游戏逻辑。它们通过参数声明需要的数据,Estella 自动注入。系统自动操作场景中所有匹配的实体

定义系统

import { defineSystem, Res, Time, Query, Mut, Transform, Velocity } from 'esengine';
const movementSystem = defineSystem(
[Res(Time), Query(Mut(Transform), Velocity)],
(time, query) => {
for (const [entity, transform, velocity] of query) {
transform.position.x += velocity.linear.x * time.delta;
transform.position.y += velocity.linear.y * time.delta;
}
}
);

此系统自动处理场景中所有同时拥有 TransformVelocity 组件的实体。

defineSystem 接受两个参数:

  1. 参数列表QueryResResMutCommandsGetWorldEventWriterEventReader 描述符的数组
  2. 函数 — 按相同顺序接收解析后的参数

注册系统

使用顶层函数注册系统:

import { addSystem, addStartupSystem, addSystemToSchedule, Schedule } from 'esengine';
addStartupSystem(setupSystem); // Schedule.Startup
addSystem(movementSystem); // Schedule.Update
addSystemToSchedule(Schedule.FixedUpdate, physicsSystem);

Schedule 类型

Schedule运行时机
Startup启动时运行一次
First每帧最先运行
PreUpdate每帧 Update 之前
Update每帧运行(主要游戏逻辑)
PostUpdate每帧 Update 之后
Last每帧最后运行
FixedPreUpdate固定间隔,FixedUpdate 之前
FixedUpdate固定间隔(物理)
FixedPostUpdate固定间隔,FixedUpdate 之后

系统排序

同一 Schedule 内的多个系统默认按注册顺序执行。如果系统之间有执行顺序依赖,可以通过 defineSystem 的第三个参数指定 runBeforerunAfter

import { defineSystem, Query, Res, Time, Mut, Transform, Velocity, Sprite } from 'esengine';
const movementSystem = defineSystem(
[Res(Time), Query(Mut(Transform), Velocity)],
(time, query) => { /* ... */ },
{ name: 'movement' }
);
const renderSystem = defineSystem(
[Query(Transform, Sprite)],
(query) => { /* ... */ },
{ name: 'render', runAfter: ['movement'] }
);
  • runAfter: ['movement'] — 当前系统在 movement 之后执行
  • runBefore: ['render'] — 当前系统在 render 之前执行

引擎使用拓扑排序确定最终执行顺序。如果排序约束形成循环依赖,启动时会抛出错误。

系统参数

Commands

在运行时创建、修改和销毁实体:

import { Commands, Sprite, Transform, Velocity, defineResource } from 'esengine';
const Score = defineResource({ value: 0 });
defineSystem([Commands()], (cmds) => {
// 创建新实体并添加组件(可链式调用)
const bullet = cmds.spawn()
.insert(Transform, { position: { x: 0, y: 0, z: 0 } })
.insert(Sprite, { size: { x: 8, y: 8 } })
.id();
// 修改已有实体
cmds.entity(bullet)
.insert(Velocity, { linear: { x: 100, y: 0, z: 0 } })
.remove(Sprite);
// 销毁实体
cmds.despawn(bullet);
// 插入资源
cmds.insertResource(Score, { value: 0 });
});

Commands API

方法返回值说明
cmds.spawn()EntityCommands创建新实体,返回构建器
cmds.entity(entity)EntityCommands获取已有实体的构建器
cmds.despawn(entity)this将实体加入销毁队列
cmds.insertResource(res, value)this插入或覆盖资源

EntityCommands API

spawn()entity() 返回 EntityCommands 构建器,所有方法可链式调用:

方法返回值说明
.insert(component, data?)this添加或更新组件
.remove(component)this移除组件
.id()Entity获取实体 ID

Query

遍历具有特定组件的实体:

import { Query, Mut, Transform, Sprite } from 'esengine';
defineSystem([Query(Mut(Transform), Sprite)], (query) => {
for (const [entity, transform, sprite] of query) {
transform.position.x += 1;
}
});

详见查询

Res(只读资源)

import { Res, Time } from 'esengine';
defineSystem([Res(Time)], (time) => {
console.log(`Delta: ${time.delta}s`);
});

ResMut(可变资源)

import { ResMut } from 'esengine';
defineSystem([ResMut(GameState)], (state) => {
state.get().score += 10;
});

详见资源

EventWriter

从系统中发送事件:

import { defineEvent, EventWriter } from 'esengine';
const DamageEvent = defineEvent<{ target: number; amount: number }>('Damage');
defineSystem([EventWriter(DamageEvent)], (writer) => {
writer.send({ target: enemy, amount: 25 });
});

EventReader

接收其他系统发送的事件:

import { EventReader } from 'esengine';
defineSystem([EventReader(DamageEvent)], (reader) => {
for (const event of reader) {
console.log(`${event.target} took ${event.amount} damage`);
}
});

详见事件

GetWorld

直接访问 ECS World。可用 world.get() / world.set() 按实体 ID 进行 O(1) 组件访问,或配合 findEntityByName() 等工具函数:

import { GetWorld, findEntityByName, ProgressBar } from 'esengine';
defineSystem([GetWorld()], (world) => {
const entity = findEntityByName(world, 'HealthBar');
if (entity) {
const bar = world.get(entity, ProgressBar);
bar.value = playerHealth / maxHealth;
world.set(entity, ProgressBar, bar);
}
});

World API

方法返回值说明
world.get(entity, Component)组件数据读取组件(O(1))
world.set(entity, Component, data)void写入组件(O(1))
world.has(entity, Component)boolean检查实体是否有该组件
world.tryGet(entity, Component)data | null安全读取,不存在返回 null
world.valid(entity)boolean检查实体是否存活
world.setParent(child, parent)void设置父子关系(子实体 Transform 相对于父实体)
world.removeParent(entity)void移除父节点,使实体成为根实体

实体层级

使用 setParent / removeParent 建立父子关系。子实体的 Transform 相对于父实体计算。

import { GetWorld, Commands, Transform, Sprite } from 'esengine';
defineSystem([Commands(), GetWorld()], (cmds, world) => {
const parent = cmds.spawn()
.insert(Transform, { position: { x: 100, y: 100, z: 0 } })
.insert(Sprite, {})
.id();
const child = cmds.spawn()
.insert(Transform, { position: { x: 50, y: 0, z: 0 } })
.insert(Sprite, {})
.id();
world.setParent(child, parent);
// 之后可以解除父子关系:
// world.removeParent(child);
});

组合参数

defineSystem(
[Commands(), Res(Time), Res(Input), GetWorld(), Query(Mut(Transform), Velocity)],
(cmds, time, input, world, query) => {
// 所有参数都可用
}
);

示例:玩家移动

定义 Speed 组件,在编辑器中挂载到玩家实体上,然后编写系统:

src/components/Speed.ts
import { defineComponent } from 'esengine';
export const Speed = defineComponent('Speed', { value: 200 });
src/systems/movement.ts
import { defineSystem, addSystem, Res, Time, Input, Query, Mut, Transform } from 'esengine';
import { Speed } from '../components/Speed';
addSystem(defineSystem(
[Res(Time), Res(Input), Query(Mut(Transform), Speed)],
(time, input, query) => {
for (const [entity, transform, speed] of query) {
if (input.isKeyDown('KeyD')) {
transform.position.x += speed.value * time.delta;
}
if (input.isKeyDown('KeyA')) {
transform.position.x -= speed.value * time.delta;
}
}
}
));

异步系统

defineSystem 支持异步函数。适用于需要加载资源的启动系统,或调用异步 API 的系统:

import { defineSystem, addStartupSystem, Res, Prefabs } from 'esengine';
addStartupSystem(defineSystem(
[Res(Prefabs)],
async (prefabs) => {
const { root } = await prefabs.instantiate('prefabs/Enemy.esprefab');
console.log('Spawned enemy:', root);
}
));

异步系统在所有调度阶段均可使用。无需特殊声明——直接传入 async 函数即可,引擎会自动处理。Commands 会在异步函数完成后自动刷新。

错误处理

每个系统在独立的 try/catch 边界内运行。如果系统抛出错误,会被捕获并输出到控制台,不会导致整个游戏循环崩溃。其他系统继续正常执行。

下一步