Entity Query System
Entity querying is one of the core features of ECS architecture. This guide introduces how to use Matcher and QuerySystem to query and filter entities.
Core Concepts
Section titled “Core Concepts”Matcher - Query Condition Descriptor
Section titled “Matcher - Query Condition Descriptor”Matcher is a chainable API used to describe entity query conditions. It doesn’t execute queries itself but passes conditions to EntitySystem or QuerySystem.
QuerySystem - Query Execution Engine
Section titled “QuerySystem - Query Execution Engine”QuerySystem is responsible for actually executing queries, using reactive query mechanisms internally for automatic performance optimization.
Using Matcher in EntitySystem
Section titled “Using Matcher in EntitySystem”This is the most common usage. EntitySystem automatically filters and processes entities matching conditions through Matcher.
Basic Usage
Section titled “Basic Usage”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() { // Method 1: Use Matcher.empty().all() super(Matcher.empty().all(PositionComponent, VelocityComponent));
// Method 2: Use Matcher.all() directly (equivalent) // 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; } }}
// Add to scenescene.addEntityProcessor(new MovementSystem());Matcher Chainable API
Section titled “Matcher Chainable API”all() - Must Include All Components
Section titled “all() - Must Include All Components”class HealthSystem extends EntitySystem { constructor() { // Entity must have both Health and Position components super(Matcher.empty().all(HealthComponent, PositionComponent)); }
protected process(entities: readonly Entity[]): void { // Only process entities with both components }}any() - Include At Least One Component
Section titled “any() - Include At Least One Component”class DamageableSystem extends EntitySystem { constructor() { // Entity must have at least Health or Shield super(Matcher.any(HealthComponent, ShieldComponent)); }
protected process(entities: readonly Entity[]): void { // Process entities with health or shield }}none() - Must Not Include Components
Section titled “none() - Must Not Include Components”class AliveEntitySystem extends EntitySystem { constructor() { // Entity must not have DeadTag component super(Matcher.all(HealthComponent).none(DeadTag)); }
protected process(entities: readonly Entity[]): void { // Only process living entities }}Combined Conditions
Section titled “Combined Conditions”class CombatSystem extends EntitySystem { constructor() { super( Matcher.empty() .all(PositionComponent, HealthComponent) // Must have position and health .any(WeaponComponent, MagicComponent) // At least weapon or magic .none(DeadTag, FrozenTag) // Not dead or frozen ); }
protected process(entities: readonly Entity[]): void { // Process living entities that can fight }}nothing() - Match No Entities
Section titled “nothing() - Match No Entities”Used for systems that only need lifecycle methods (onBegin, onEnd) but don’t process entities.
class FrameTimerSystem extends EntitySystem { constructor() { // Match no entities super(Matcher.nothing()); }
protected onBegin(): void { // Execute at frame start Performance.markFrameStart(); }
protected process(entities: readonly Entity[]): void { // Never called because no matching entities }
protected onEnd(): void { // Execute at frame end Performance.markFrameEnd(); }}empty() vs nothing()
Section titled “empty() vs nothing()”| Method | Behavior | Use Case |
|---|---|---|
Matcher.empty() | Match all entities | Process all entities in scene |
Matcher.nothing() | Match no entities | Only need lifecycle callbacks |
Using QuerySystem Directly
Section titled “Using QuerySystem Directly”If you don’t need to create a system, you can use Scene’s querySystem directly.
Basic Query Methods
Section titled “Basic Query Methods”// Get scene's query systemconst querySystem = scene.querySystem;
// Query entities with all specified componentsconst result1 = querySystem.queryAll(PositionComponent, VelocityComponent);console.log(`Found ${result1.count} moving entities`);console.log(`Query time: ${result1.executionTime.toFixed(2)}ms`);
// Query entities with any specified componentconst result2 = querySystem.queryAny(WeaponComponent, MagicComponent);console.log(`Found ${result2.count} combat units`);
// Query entities without specified componentsconst result3 = querySystem.queryNone(DeadTag);console.log(`Found ${result3.count} living entities`);Query by Tag and Name
Section titled “Query by Tag and Name”// Query by tagconst playerResult = querySystem.queryByTag(Tags.PLAYER);for (const player of playerResult.entities) { console.log('Player:', player.name);}
// Query by nameconst bossResult = querySystem.queryByName('Boss');if (bossResult.count > 0) { const boss = bossResult.entities[0]; console.log('Found Boss:', boss);}
// Query by single componentconst healthResult = querySystem.queryByComponent(HealthComponent);console.log(`${healthResult.count} entities have health`);Performance Optimization
Section titled “Performance Optimization”Automatic Caching
Section titled “Automatic Caching”QuerySystem uses reactive queries internally with automatic caching:
// First query, executes actual queryconst result1 = querySystem.queryAll(PositionComponent);console.log('fromCache:', result1.fromCache); // false
// Second same query, uses cacheconst result2 = querySystem.queryAll(PositionComponent);console.log('fromCache:', result2.fromCache); // trueAutomatic Updates on Entity Changes
Section titled “Automatic Updates on Entity Changes”Query cache updates automatically when entities add/remove components:
// Query entities with weaponsconst before = querySystem.queryAll(WeaponComponent);console.log('Before:', before.count); // Assume 5
// Add weapon to entityconst enemy = scene.createEntity('Enemy');enemy.addComponent(new WeaponComponent());
// Query again, automatically includes new entityconst after = querySystem.queryAll(WeaponComponent);console.log('After:', after.count); // Now 6More Topics
Section titled “More Topics”- Matcher API - Complete Matcher API reference
- Compiled Query - CompiledQuery advanced usage
- Best Practices - Query optimization and practical applications