2025-10-14 13:30:48 +08:00
|
|
|
# 实体查询系统
|
|
|
|
|
|
|
|
|
|
实体查询是 ECS 架构的核心功能之一。本指南将介绍如何使用 Matcher 和 QuerySystem 来查询和筛选实体。
|
|
|
|
|
|
|
|
|
|
## 核心概念
|
|
|
|
|
|
|
|
|
|
### Matcher - 查询条件描述符
|
|
|
|
|
|
|
|
|
|
Matcher 是一个链式 API,用于描述实体查询条件。它本身不执行查询,而是作为条件传递给 EntitySystem 或 QuerySystem。
|
|
|
|
|
|
|
|
|
|
### QuerySystem - 查询执行引擎
|
|
|
|
|
|
|
|
|
|
QuerySystem 负责实际执行查询,内部使用响应式查询机制自动优化性能。
|
|
|
|
|
|
|
|
|
|
## 在 EntitySystem 中使用 Matcher
|
|
|
|
|
|
|
|
|
|
这是最常见的使用方式。EntitySystem 通过 Matcher 自动筛选和处理符合条件的实体。
|
|
|
|
|
|
|
|
|
|
### 基础用法
|
|
|
|
|
|
|
|
|
|
```typescript
|
2025-12-08 21:26:35 +08:00
|
|
|
import { EntitySystem, Matcher, Entity, Component } from '@esengine/ecs-framework';
|
2025-10-14 13:30:48 +08:00
|
|
|
|
|
|
|
|
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 {
|
|
|
|
|
// 处理可以战斗的活着的实体
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2025-11-28 11:03:34 +08:00
|
|
|
#### 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 永远是空数组,此方法不会被调用
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2025-10-14 13:30:48 +08:00
|
|
|
### 按标签查询
|
|
|
|
|
|
|
|
|
|
```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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 最佳实践
|
|
|
|
|
|
|
|
|
|
### 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
|
|
|
|
|
```
|
|
|
|
|
|
2025-11-28 11:03:34 +08:00
|
|
|
## 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()
|
|
|
|
|
```
|
|
|
|
|
|
2025-10-14 13:30:48 +08:00
|
|
|
## 相关 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 参考
|