新增功能: - EntityHandle: 轻量级实体句柄 (28位索引 + 20位代数) - SystemScheduler: 声明式系统调度,支持 @Stage/@Before/@After/@InSet 装饰器 - EpochManager: 帧级变更检测 - CompiledQuery: 预编译类型安全查询 API 改进: - EntitySystem 添加 getBefore()/getAfter()/getSets() getter 方法 - Entity 添加 markDirty() 辅助方法 - IScene 添加 epochManager 属性 - CommandBuffer.pendingCount 修正为返回实际操作数 文档更新: - 更新系统调度和查询相关文档
751 lines
20 KiB
Markdown
751 lines
20 KiB
Markdown
# 实体查询系统
|
||
|
||
实体查询是 ECS 架构的核心功能之一。本指南将介绍如何使用 Matcher 和 QuerySystem 来查询和筛选实体。
|
||
|
||
## 核心概念
|
||
|
||
### Matcher - 查询条件描述符
|
||
|
||
Matcher 是一个链式 API,用于描述实体查询条件。它本身不执行查询,而是作为条件传递给 EntitySystem 或 QuerySystem。
|
||
|
||
### QuerySystem - 查询执行引擎
|
||
|
||
QuerySystem 负责实际执行查询,内部使用响应式查询机制自动优化性能。
|
||
|
||
## 在 EntitySystem 中使用 Matcher
|
||
|
||
这是最常见的使用方式。EntitySystem 通过 Matcher 自动筛选和处理符合条件的实体。
|
||
|
||
### 基础用法
|
||
|
||
```typescript
|
||
import { EntitySystem, Matcher, Entity, Component } from '@esengine/ecs-framework';
|
||
|
||
class PositionComponent extends Component {
|
||
public x: number = 0;
|
||
public y: number = 0;
|
||
}
|
||
|
||
class VelocityComponent extends Component {
|
||
public vx: number = 0;
|
||
public vy: number = 0;
|
||
}
|
||
|
||
class MovementSystem extends EntitySystem {
|
||
constructor() {
|
||
// 方式1: 使用 Matcher.empty().all()
|
||
super(Matcher.empty().all(PositionComponent, VelocityComponent));
|
||
|
||
// 方式2: 直接使用 Matcher.all() (等价)
|
||
// super(Matcher.all(PositionComponent, VelocityComponent));
|
||
}
|
||
|
||
protected process(entities: readonly Entity[]): void {
|
||
for (const entity of entities) {
|
||
const pos = entity.getComponent(PositionComponent)!;
|
||
const vel = entity.getComponent(VelocityComponent)!;
|
||
|
||
pos.x += vel.vx;
|
||
pos.y += vel.vy;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 添加到场景
|
||
scene.addEntityProcessor(new MovementSystem());
|
||
```
|
||
|
||
### Matcher 链式 API
|
||
|
||
#### all() - 必须包含所有组件
|
||
|
||
```typescript
|
||
class HealthSystem extends EntitySystem {
|
||
constructor() {
|
||
// 实体必须同时拥有 Health 和 Position 组件
|
||
super(Matcher.empty().all(HealthComponent, PositionComponent));
|
||
}
|
||
|
||
protected process(entities: readonly Entity[]): void {
|
||
// 只处理同时拥有两个组件的实体
|
||
}
|
||
}
|
||
```
|
||
|
||
#### any() - 至少包含一个组件
|
||
|
||
```typescript
|
||
class DamageableSystem extends EntitySystem {
|
||
constructor() {
|
||
// 实体至少拥有 Health 或 Shield 其中之一
|
||
super(Matcher.any(HealthComponent, ShieldComponent));
|
||
}
|
||
|
||
protected process(entities: readonly Entity[]): void {
|
||
// 处理拥有生命值或护盾的实体
|
||
}
|
||
}
|
||
```
|
||
|
||
#### none() - 不能包含指定组件
|
||
|
||
```typescript
|
||
class AliveEntitySystem extends EntitySystem {
|
||
constructor() {
|
||
// 实体不能拥有 DeadTag 组件
|
||
super(Matcher.all(HealthComponent).none(DeadTag));
|
||
}
|
||
|
||
protected process(entities: readonly Entity[]): void {
|
||
// 只处理活着的实体
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 组合条件
|
||
|
||
```typescript
|
||
class CombatSystem extends EntitySystem {
|
||
constructor() {
|
||
super(
|
||
Matcher.empty()
|
||
.all(PositionComponent, HealthComponent) // 必须有位置和生命
|
||
.any(WeaponComponent, MagicComponent) // 至少有武器或魔法
|
||
.none(DeadTag, FrozenTag) // 不能是死亡或冰冻状态
|
||
);
|
||
}
|
||
|
||
protected process(entities: readonly Entity[]): void {
|
||
// 处理可以战斗的活着的实体
|
||
}
|
||
}
|
||
```
|
||
|
||
#### nothing() - 不匹配任何实体
|
||
|
||
用于创建只需要生命周期方法(`onBegin`、`onEnd`)但不需要处理实体的系统。
|
||
|
||
```typescript
|
||
class FrameTimerSystem extends EntitySystem {
|
||
constructor() {
|
||
// 不匹配任何实体
|
||
super(Matcher.nothing());
|
||
}
|
||
|
||
protected onBegin(): void {
|
||
// 每帧开始时执行
|
||
Performance.markFrameStart();
|
||
}
|
||
|
||
protected process(entities: readonly Entity[]): void {
|
||
// 永远不会被调用,因为没有匹配的实体
|
||
}
|
||
|
||
protected onEnd(): void {
|
||
// 每帧结束时执行
|
||
Performance.markFrameEnd();
|
||
}
|
||
}
|
||
```
|
||
|
||
#### empty() vs nothing() 的区别
|
||
|
||
| 方法 | 行为 | 使用场景 |
|
||
|------|------|----------|
|
||
| `Matcher.empty()` | 匹配**所有**实体 | 需要处理场景中所有实体 |
|
||
| `Matcher.nothing()` | 不匹配**任何**实体 | 只需要生命周期回调,不处理实体 |
|
||
|
||
```typescript
|
||
// empty() - 返回场景中的所有实体
|
||
class AllEntitiesSystem extends EntitySystem {
|
||
constructor() {
|
||
super(Matcher.empty());
|
||
}
|
||
|
||
protected process(entities: readonly Entity[]): void {
|
||
// entities 包含场景中的所有实体
|
||
console.log(`场景中共有 ${entities.length} 个实体`);
|
||
}
|
||
}
|
||
|
||
// nothing() - 不返回任何实体
|
||
class NoEntitiesSystem extends EntitySystem {
|
||
constructor() {
|
||
super(Matcher.nothing());
|
||
}
|
||
|
||
protected process(entities: readonly Entity[]): void {
|
||
// entities 永远是空数组,此方法不会被调用
|
||
}
|
||
}
|
||
```
|
||
|
||
### 按标签查询
|
||
|
||
```typescript
|
||
class PlayerSystem extends EntitySystem {
|
||
constructor() {
|
||
// 查询特定标签的实体
|
||
super(Matcher.empty().withTag(Tags.PLAYER));
|
||
}
|
||
|
||
protected process(entities: readonly Entity[]): void {
|
||
// 只处理玩家实体
|
||
}
|
||
}
|
||
```
|
||
|
||
### 按名称查询
|
||
|
||
```typescript
|
||
class BossSystem extends EntitySystem {
|
||
constructor() {
|
||
// 查询特定名称的实体
|
||
super(Matcher.empty().withName('Boss'));
|
||
}
|
||
|
||
protected process(entities: readonly Entity[]): void {
|
||
// 只处理名为 'Boss' 的实体
|
||
}
|
||
}
|
||
```
|
||
|
||
## 直接使用 QuerySystem
|
||
|
||
如果不需要创建系统,可以直接使用 Scene 的 querySystem 进行查询。
|
||
|
||
### 基础查询方法
|
||
|
||
```typescript
|
||
// 获取场景的查询系统
|
||
const querySystem = scene.querySystem;
|
||
|
||
// 查询拥有所有指定组件的实体
|
||
const result1 = querySystem.queryAll(PositionComponent, VelocityComponent);
|
||
console.log(`找到 ${result1.count} 个移动实体`);
|
||
console.log(`查询耗时: ${result1.executionTime.toFixed(2)}ms`);
|
||
|
||
// 查询拥有任意指定组件的实体
|
||
const result2 = querySystem.queryAny(WeaponComponent, MagicComponent);
|
||
console.log(`找到 ${result2.count} 个战斗单位`);
|
||
|
||
// 查询不包含指定组件的实体
|
||
const result3 = querySystem.queryNone(DeadTag);
|
||
console.log(`找到 ${result3.count} 个活着的实体`);
|
||
```
|
||
|
||
### 按标签查询
|
||
|
||
```typescript
|
||
const playerResult = querySystem.queryByTag(Tags.PLAYER);
|
||
for (const player of playerResult.entities) {
|
||
console.log('玩家:', player.name);
|
||
}
|
||
```
|
||
|
||
### 按名称查询
|
||
|
||
```typescript
|
||
const bossResult = querySystem.queryByName('Boss');
|
||
if (bossResult.count > 0) {
|
||
const boss = bossResult.entities[0];
|
||
console.log('找到Boss:', boss);
|
||
}
|
||
```
|
||
|
||
### 按单个组件查询
|
||
|
||
```typescript
|
||
const healthResult = querySystem.queryByComponent(HealthComponent);
|
||
console.log(`有 ${healthResult.count} 个实体拥有生命值`);
|
||
```
|
||
|
||
## 性能优化
|
||
|
||
### 自动缓存
|
||
|
||
QuerySystem 内部使用响应式查询自动缓存结果,相同的查询条件会直接使用缓存:
|
||
|
||
```typescript
|
||
// 第一次查询,执行实际查询
|
||
const result1 = querySystem.queryAll(PositionComponent);
|
||
console.log('fromCache:', result1.fromCache); // false
|
||
|
||
// 第二次相同查询,使用缓存
|
||
const result2 = querySystem.queryAll(PositionComponent);
|
||
console.log('fromCache:', result2.fromCache); // true
|
||
```
|
||
|
||
### 实体变化自动更新
|
||
|
||
当实体添加/移除组件时,查询缓存会自动更新:
|
||
|
||
```typescript
|
||
// 查询拥有武器的实体
|
||
const before = querySystem.queryAll(WeaponComponent);
|
||
console.log('之前:', before.count); // 假设为 5
|
||
|
||
// 给实体添加武器
|
||
const enemy = scene.createEntity('Enemy');
|
||
enemy.addComponent(new WeaponComponent());
|
||
|
||
// 再次查询,自动包含新实体
|
||
const after = querySystem.queryAll(WeaponComponent);
|
||
console.log('之后:', after.count); // 现在是 6
|
||
```
|
||
|
||
### 查询性能统计
|
||
|
||
```typescript
|
||
const stats = querySystem.getStats();
|
||
console.log('总查询次数:', stats.queryStats.totalQueries);
|
||
console.log('缓存命中率:', stats.queryStats.cacheHitRate);
|
||
console.log('缓存大小:', stats.cacheStats.size);
|
||
```
|
||
|
||
## 实际应用场景
|
||
|
||
### 场景1: 物理系统
|
||
|
||
```typescript
|
||
class PhysicsSystem extends EntitySystem {
|
||
constructor() {
|
||
super(Matcher.empty().all(TransformComponent, RigidbodyComponent));
|
||
}
|
||
|
||
protected process(entities: readonly Entity[]): void {
|
||
for (const entity of entities) {
|
||
const transform = entity.getComponent(TransformComponent)!;
|
||
const rigidbody = entity.getComponent(RigidbodyComponent)!;
|
||
|
||
// 应用重力
|
||
rigidbody.velocity.y -= 9.8 * Time.deltaTime;
|
||
|
||
// 更新位置
|
||
transform.position.x += rigidbody.velocity.x * Time.deltaTime;
|
||
transform.position.y += rigidbody.velocity.y * Time.deltaTime;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 场景2: 渲染系统
|
||
|
||
```typescript
|
||
class RenderSystem extends EntitySystem {
|
||
constructor() {
|
||
super(
|
||
Matcher.empty()
|
||
.all(TransformComponent, SpriteComponent)
|
||
.none(InvisibleTag) // 排除不可见实体
|
||
);
|
||
}
|
||
|
||
protected process(entities: readonly Entity[]): void {
|
||
// 按 z-order 排序
|
||
const sorted = entities.slice().sort((a, b) => {
|
||
const zA = a.getComponent(TransformComponent)!.z;
|
||
const zB = b.getComponent(TransformComponent)!.z;
|
||
return zA - zB;
|
||
});
|
||
|
||
// 渲染实体
|
||
for (const entity of sorted) {
|
||
const transform = entity.getComponent(TransformComponent)!;
|
||
const sprite = entity.getComponent(SpriteComponent)!;
|
||
|
||
renderer.drawSprite(sprite.texture, transform.position);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 场景3: 碰撞检测
|
||
|
||
```typescript
|
||
class CollisionSystem extends EntitySystem {
|
||
constructor() {
|
||
super(Matcher.empty().all(TransformComponent, ColliderComponent));
|
||
}
|
||
|
||
protected process(entities: readonly Entity[]): void {
|
||
// 简单的 O(n²) 碰撞检测
|
||
for (let i = 0; i < entities.length; i++) {
|
||
for (let j = i + 1; j < entities.length; j++) {
|
||
this.checkCollision(entities[i], entities[j]);
|
||
}
|
||
}
|
||
}
|
||
|
||
private checkCollision(a: Entity, b: Entity): void {
|
||
const transA = a.getComponent(TransformComponent)!;
|
||
const transB = b.getComponent(TransformComponent)!;
|
||
const colliderA = a.getComponent(ColliderComponent)!;
|
||
const colliderB = b.getComponent(ColliderComponent)!;
|
||
|
||
if (this.isOverlapping(transA, colliderA, transB, colliderB)) {
|
||
// 触发碰撞事件
|
||
scene.eventSystem.emit('collision', { entityA: a, entityB: b });
|
||
}
|
||
}
|
||
|
||
private isOverlapping(...args: any[]): boolean {
|
||
// 碰撞检测逻辑
|
||
return false;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 场景4: 一次性查询
|
||
|
||
```typescript
|
||
// 在系统外部执行一次性查询
|
||
class GameManager {
|
||
private scene: Scene;
|
||
|
||
public countEnemies(): number {
|
||
const result = this.scene.querySystem.queryByTag(Tags.ENEMY);
|
||
return result.count;
|
||
}
|
||
|
||
public findNearestEnemy(playerPos: Vector2): Entity | null {
|
||
const enemies = this.scene.querySystem.queryByTag(Tags.ENEMY);
|
||
|
||
let nearest: Entity | null = null;
|
||
let minDistance = Infinity;
|
||
|
||
for (const enemy of enemies.entities) {
|
||
const transform = enemy.getComponent(TransformComponent);
|
||
if (!transform) continue;
|
||
|
||
const distance = Vector2.distance(playerPos, transform.position);
|
||
if (distance < minDistance) {
|
||
minDistance = distance;
|
||
nearest = enemy;
|
||
}
|
||
}
|
||
|
||
return nearest;
|
||
}
|
||
}
|
||
```
|
||
|
||
## 编译查询 (CompiledQuery)
|
||
|
||
> **v2.4.0+**
|
||
|
||
CompiledQuery 是一个轻量级的查询工具,提供类型安全的组件访问和变更检测支持。适合临时查询、工具开发和简单的迭代场景。
|
||
|
||
### 基本用法
|
||
|
||
```typescript
|
||
// 创建编译查询
|
||
const query = scene.querySystem.compile(Position, Velocity);
|
||
|
||
// 类型安全的遍历 - 组件参数自动推断类型
|
||
query.forEach((entity, pos, vel) => {
|
||
pos.x += vel.vx * deltaTime;
|
||
pos.y += vel.vy * deltaTime;
|
||
});
|
||
|
||
// 获取实体数量
|
||
console.log(`匹配实体数: ${query.count}`);
|
||
|
||
// 获取第一个匹配的实体
|
||
const first = query.first();
|
||
if (first) {
|
||
const [entity, pos, vel] = first;
|
||
console.log(`第一个实体: ${entity.name}`);
|
||
}
|
||
```
|
||
|
||
### 变更检测
|
||
|
||
CompiledQuery 支持基于 epoch 的变更检测:
|
||
|
||
```typescript
|
||
class RenderSystem extends EntitySystem {
|
||
private _query: CompiledQuery<[typeof Transform, typeof Sprite]>;
|
||
private _lastEpoch = 0;
|
||
|
||
protected onInitialize(): void {
|
||
this._query = this.scene!.querySystem.compile(Transform, Sprite);
|
||
}
|
||
|
||
protected process(entities: readonly Entity[]): void {
|
||
// 只处理 Transform 或 Sprite 发生变化的实体
|
||
this._query.forEachChanged(this._lastEpoch, (entity, transform, sprite) => {
|
||
this.updateRenderData(entity, transform, sprite);
|
||
});
|
||
|
||
// 保存当前 epoch 作为下次检查的起点
|
||
this._lastEpoch = this.scene!.epochManager.current;
|
||
}
|
||
|
||
private updateRenderData(entity: Entity, transform: Transform, sprite: Sprite): void {
|
||
// 更新渲染数据
|
||
}
|
||
}
|
||
```
|
||
|
||
### 函数式 API
|
||
|
||
CompiledQuery 提供了丰富的函数式 API:
|
||
|
||
```typescript
|
||
const query = scene.querySystem.compile(Position, Health);
|
||
|
||
// map - 转换实体数据
|
||
const positions = query.map((entity, pos, health) => ({
|
||
x: pos.x,
|
||
y: pos.y,
|
||
healthPercent: health.current / health.max
|
||
}));
|
||
|
||
// filter - 过滤实体
|
||
const lowHealthEntities = query.filter((entity, pos, health) => {
|
||
return health.current < health.max * 0.2;
|
||
});
|
||
|
||
// find - 查找第一个匹配的实体
|
||
const target = query.find((entity, pos, health) => {
|
||
return health.current > 0 && pos.x > 100;
|
||
});
|
||
|
||
// toArray - 转换为数组
|
||
const allData = query.toArray();
|
||
for (const [entity, pos, health] of allData) {
|
||
console.log(`${entity.name}: ${pos.x}, ${pos.y}`);
|
||
}
|
||
|
||
// any/empty - 检查是否有匹配
|
||
if (query.any()) {
|
||
console.log('有匹配的实体');
|
||
}
|
||
if (query.empty()) {
|
||
console.log('没有匹配的实体');
|
||
}
|
||
```
|
||
|
||
### CompiledQuery vs EntitySystem
|
||
|
||
| 特性 | CompiledQuery | EntitySystem |
|
||
|------|---------------|--------------|
|
||
| **用途** | 轻量级查询工具 | 完整的系统逻辑 |
|
||
| **生命周期** | 无 | 完整 (onInitialize, onDestroy 等) |
|
||
| **调度集成** | 无 | 支持 @Stage, @Before, @After |
|
||
| **变更检测** | forEachChanged | forEachChanged |
|
||
| **事件监听** | 无 | addEventListener |
|
||
| **命令缓冲** | 无 | this.commands |
|
||
| **类型安全组件** | forEach 参数自动推断 | 需要手动 getComponent |
|
||
| **适用场景** | 临时查询、工具、原型 | 核心游戏逻辑 |
|
||
|
||
**选择建议**:
|
||
|
||
- 使用 **EntitySystem** 处理核心游戏逻辑(移动、战斗、AI 等)
|
||
- 使用 **CompiledQuery** 进行一次性查询、工具开发或简单迭代
|
||
|
||
### CompiledQuery API 参考
|
||
|
||
| 方法 | 说明 |
|
||
|------|------|
|
||
| `forEach(callback)` | 遍历所有匹配实体,类型安全的组件参数 |
|
||
| `forEachChanged(sinceEpoch, callback)` | 只遍历变更的实体 |
|
||
| `first()` | 获取第一个匹配的实体和组件 |
|
||
| `toArray()` | 转换为 [entity, ...components] 数组 |
|
||
| `map(callback)` | 映射转换 |
|
||
| `filter(predicate)` | 过滤实体 |
|
||
| `find(predicate)` | 查找第一个满足条件的实体 |
|
||
| `any()` | 是否有任何匹配 |
|
||
| `empty()` | 是否没有匹配 |
|
||
| `count` | 匹配的实体数量 |
|
||
| `entities` | 匹配的实体列表(只读) |
|
||
|
||
## 最佳实践
|
||
|
||
### 1. 优先使用 EntitySystem
|
||
|
||
```typescript
|
||
// 推荐: 使用 EntitySystem
|
||
class GoodSystem extends EntitySystem {
|
||
constructor() {
|
||
super(Matcher.empty().all(HealthComponent));
|
||
}
|
||
|
||
protected process(entities: readonly Entity[]): void {
|
||
// 自动获得符合条件的实体,每帧自动更新
|
||
}
|
||
}
|
||
|
||
// 不推荐: 在 update 中手动查询
|
||
class BadSystem extends EntitySystem {
|
||
constructor() {
|
||
super(Matcher.empty());
|
||
}
|
||
|
||
protected process(entities: readonly Entity[]): void {
|
||
// 每帧手动查询,浪费性能
|
||
const result = this.scene!.querySystem.queryAll(HealthComponent);
|
||
for (const entity of result.entities) {
|
||
// ...
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2. 合理使用 none() 排除条件
|
||
|
||
```typescript
|
||
// 排除已死亡的敌人
|
||
class EnemyAISystem extends EntitySystem {
|
||
constructor() {
|
||
super(
|
||
Matcher.empty()
|
||
.all(EnemyTag, AIComponent)
|
||
.none(DeadTag) // 不处理死亡的敌人
|
||
);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3. 使用标签优化查询
|
||
|
||
```typescript
|
||
// 不好: 查询所有实体再过滤
|
||
const allEntities = scene.querySystem.getAllEntities();
|
||
const players = allEntities.filter(e => e.hasComponent(PlayerTag));
|
||
|
||
// 好: 直接按标签查询
|
||
const players = scene.querySystem.queryByTag(Tags.PLAYER).entities;
|
||
```
|
||
|
||
### 4. 避免过于复杂的查询条件
|
||
|
||
```typescript
|
||
// 不推荐: 过于复杂
|
||
super(
|
||
Matcher.empty()
|
||
.all(A, B, C, D)
|
||
.any(E, F, G)
|
||
.none(H, I, J)
|
||
);
|
||
|
||
// 推荐: 拆分成多个简单系统
|
||
class SystemAB extends EntitySystem {
|
||
constructor() {
|
||
super(Matcher.empty().all(A, B));
|
||
}
|
||
}
|
||
|
||
class SystemCD extends EntitySystem {
|
||
constructor() {
|
||
super(Matcher.empty().all(C, D));
|
||
}
|
||
}
|
||
```
|
||
|
||
## 注意事项
|
||
|
||
### 1. 查询结果是只读的
|
||
|
||
```typescript
|
||
const result = querySystem.queryAll(PositionComponent);
|
||
|
||
// 不要修改返回的数组
|
||
result.entities.push(someEntity); // 错误!
|
||
|
||
// 如果需要修改,先复制
|
||
const mutableArray = [...result.entities];
|
||
mutableArray.push(someEntity); // 正确
|
||
```
|
||
|
||
### 2. 组件添加/移除后的查询时机
|
||
|
||
```typescript
|
||
// 创建实体并添加组件
|
||
const entity = scene.createEntity('Player');
|
||
entity.addComponent(new PositionComponent());
|
||
|
||
// 立即查询可能获取到新实体
|
||
const result = scene.querySystem.queryAll(PositionComponent);
|
||
// result.entities 包含新创建的实体
|
||
```
|
||
|
||
### 3. Matcher 是不可变的
|
||
|
||
```typescript
|
||
const matcher = Matcher.empty().all(PositionComponent);
|
||
|
||
// 链式调用返回新的 Matcher 实例
|
||
const matcher2 = matcher.any(VelocityComponent);
|
||
|
||
// matcher 本身不变
|
||
console.log(matcher === matcher2); // false
|
||
```
|
||
|
||
## Matcher API 快速参考
|
||
|
||
### 静态创建方法
|
||
|
||
| 方法 | 说明 | 示例 |
|
||
|------|------|------|
|
||
| `Matcher.all(...types)` | 必须包含所有指定组件 | `Matcher.all(Position, Velocity)` |
|
||
| `Matcher.any(...types)` | 至少包含一个指定组件 | `Matcher.any(Health, Shield)` |
|
||
| `Matcher.none(...types)` | 不能包含任何指定组件 | `Matcher.none(Dead)` |
|
||
| `Matcher.byTag(tag)` | 按标签查询 | `Matcher.byTag(1)` |
|
||
| `Matcher.byName(name)` | 按名称查询 | `Matcher.byName("Player")` |
|
||
| `Matcher.byComponent(type)` | 按单个组件查询 | `Matcher.byComponent(Health)` |
|
||
| `Matcher.empty()` | 创建空匹配器(匹配所有实体) | `Matcher.empty()` |
|
||
| `Matcher.nothing()` | 不匹配任何实体 | `Matcher.nothing()` |
|
||
| `Matcher.complex()` | 创建复杂查询构建器 | `Matcher.complex()` |
|
||
|
||
### 链式方法
|
||
|
||
| 方法 | 说明 | 示例 |
|
||
|------|------|------|
|
||
| `.all(...types)` | 添加必须包含的组件 | `.all(Position)` |
|
||
| `.any(...types)` | 添加可选组件(至少一个) | `.any(Weapon, Magic)` |
|
||
| `.none(...types)` | 添加排除的组件 | `.none(Dead)` |
|
||
| `.exclude(...types)` | `.none()` 的别名 | `.exclude(Disabled)` |
|
||
| `.one(...types)` | `.any()` 的别名 | `.one(Player, Enemy)` |
|
||
| `.withTag(tag)` | 添加标签条件 | `.withTag(1)` |
|
||
| `.withName(name)` | 添加名称条件 | `.withName("Boss")` |
|
||
| `.withComponent(type)` | 添加单组件条件 | `.withComponent(Health)` |
|
||
|
||
### 实用方法
|
||
|
||
| 方法 | 说明 |
|
||
|------|------|
|
||
| `.getCondition()` | 获取查询条件(只读) |
|
||
| `.isEmpty()` | 检查是否为空条件 |
|
||
| `.isNothing()` | 检查是否为 nothing 匹配器 |
|
||
| `.clone()` | 克隆匹配器 |
|
||
| `.reset()` | 重置所有条件 |
|
||
| `.toString()` | 获取字符串表示 |
|
||
|
||
### 常用组合示例
|
||
|
||
```typescript
|
||
// 基础移动系统
|
||
Matcher.all(Position, Velocity)
|
||
|
||
// 可攻击的活着的实体
|
||
Matcher.all(Position, Health)
|
||
.any(Weapon, Magic)
|
||
.none(Dead, Disabled)
|
||
|
||
// 所有带标签的敌人
|
||
Matcher.byTag(Tags.ENEMY)
|
||
.all(AIComponent)
|
||
|
||
// 只需要生命周期的系统
|
||
Matcher.nothing()
|
||
```
|
||
|
||
## 相关 API
|
||
|
||
- [Matcher](../api/classes/Matcher.md) - 查询条件描述符 API 参考
|
||
- [QuerySystem](../api/classes/QuerySystem.md) - 查询系统 API 参考
|
||
- [EntitySystem](../api/classes/EntitySystem.md) - 实体系统 API 参考
|
||
- [Entity](../api/classes/Entity.md) - 实体 API 参考
|