跳转到内容

查询

查询让你遍历具有特定组件的实体,是系统访问场景中实体数据的主要方式。

基本查询

查询匹配场景中所有拥有全部列出组件的实体:

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

for...of 循环按查询参数的顺序返回 [entity, ...components] 元组。

可变查询

默认情况下,查询结果是只读的。使用 Mut() 获取特定组件的可变访问:

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

迭代方式

for…of

for (const [entity, transform, sprite] of query) {
transform.position.x += 1;
}

forEach

query.forEach((entity, transform, sprite) => {
transform.position.x += 1;
});

single

获取第一个匹配的实体。如果没有匹配的实体则返回 null

const result = query.single();
if (result) {
const [entity, transform, sprite] = result;
// ...
}

工具方法

query.isEmpty(); // 没有实体匹配时返回 true
query.count(); // 匹配的实体数量
query.toArray(); // 将所有结果收集到数组中

查询过滤器

使用 .with().without() 按组件的有无过滤实体,且不将这些组件包含在结果元组中:

import { Query, Transform, Sprite, defineTag } from 'esengine';
const Hidden = defineTag('Hidden');
const Disabled = defineTag('Disabled');
// 匹配有 Transform 和 Sprite 的实体,但只要同时有 Player 的
defineSystem(
[Query(Mut(Transform), Sprite).with(Player)],
(query) => {
for (const [entity, transform, sprite] of query) {
// 保证有 Player,但不在元组中
}
}
);
// 匹配有 Transform 的实体,排除有 Hidden 的
defineSystem(
[Query(Transform).without(Hidden)],
(query) => {
for (const [entity, transform] of query) {
// 只有没有 Hidden 组件的实体
}
}
);

可以链式调用多个 .with().without()

Query(Mut(Transform), Sprite).with(Player).without(Hidden, Disabled)

变更检测过滤器

Added()Changed() 包裹组件,或使用 Removed() 来响应组件的生命周期事件。

Added

匹配自上次系统运行以来新增了组件的实体:

import { Query, Added, Transform } from 'esengine';
defineSystem([Query(Added(Transform))], (query) => {
for (const [entity, transform] of query) {
console.log('New transform on', entity);
}
});

Changed

匹配自上次 tick 以来组件数据被修改(通过 Mut())的实体:

import { Query, Changed, Mut, Text } from 'esengine';
defineSystem([Query(Changed(Text))], (query) => {
for (const [entity, text] of query) {
console.log('Text changed on', entity, text.content);
}
});

Removed

Removed() 是独立参数(不在 Query 内部)。它返回自上次 tick 以来被移除了指定组件的实体:

import { Removed } from 'esengine';
import { Health } from './components';
defineSystem([Removed(Health)], (removed) => {
for (const entity of removed) {
console.log('Health removed from', entity);
}
});

详见变更检测了解基于 tick 的追踪机制。

多个查询

一个系统可以有多个查询来交叉引用不同类型的实体:

import { defineTag } from 'esengine';
const Player = defineTag('Player');
const Enemy = defineTag('Enemy');
defineSystem(
[Query(Transform, Player), Query(Transform, Enemy)],
(players, enemies) => {
for (const [_, playerTransform] of players) {
for (const [_, enemyTransform] of enemies) {
// 检查玩家和每个敌人之间的距离
}
}
}
);

与资源结合

查询经常与资源一起使用:

import { Res, Time, Input, Query, Mut, Transform } from 'esengine';
defineSystem(
[Res(Time), Res(Input), Query(Mut(Transform), Player)],
(time, input, query) => {
for (const [entity, transform] of query) {
if (input.isKeyDown('KeyD')) {
transform.position.x += 200 * time.delta;
}
}
}
);

示例:碰撞检测

定义 HitboxPlayerEnemy 组件,在场景编辑器中挂载到实体上,然后查询:

import { defineSystem, defineComponent, defineTag, addSystem, Query, Transform } from 'esengine';
const Hitbox = defineComponent('Hitbox', { width: 50, height: 50 });
const Player = defineTag('Player');
const Enemy = defineTag('Enemy');
addSystem(defineSystem(
[Query(Transform, Hitbox, Player), Query(Transform, Hitbox, Enemy)],
(players, enemies) => {
for (const [_, pPos, pBox] of players) {
for (const [_, ePos, eBox] of enemies) {
const dx = Math.abs(pPos.position.x - ePos.position.x);
const dy = Math.abs(pPos.position.y - ePos.position.y);
if (dx < (pBox.width + eBox.width) / 2 &&
dy < (pBox.height + eBox.height) / 2) {
// 碰撞!
}
}
}
}
));

迭代安全

在查询迭代过程中,不能直接创建或销毁实体,也不能插入或移除组件。这些操作会改变底层数据结构,导致迭代器失效。引擎会在迭代期间检测到这类操作并抛出错误,防止数据损坏。

正确做法是使用 Commands 将修改延迟到迭代结束后执行:

import { defineSystem, addSystem, Commands, Query, defineComponent } from 'esengine';
const Health = defineComponent('Health', { value: 100 });
const despawnDead = defineSystem(
[Commands(), Query(Health)],
(commands, query) => {
for (const [entity, health] of query) {
if (health.value <= 0) {
commands.despawn(entity); // 延迟执行,安全
}
}
}
);

Commands 的操作会在当前系统函数返回后统一刷新(flush),此时迭代已结束,修改不会影响正在进行的遍历。

下一步

  • 变更检测 — 基于 tick 的 Added、Changed、Removed 追踪
  • 事件 — 解耦的系统间通信
  • 资源 — 全局单例数据如 Time 和 Input
  • 组件 — 所有内置和自定义组件类型