Skip to content

System Types

The framework provides several different system base classes for different use cases.

The most basic system class, all other systems inherit from it:

import { EntitySystem, ECSSystem, Matcher } from '@esengine/ecs-framework';
@ECSSystem('Movement')
class MovementSystem extends EntitySystem {
constructor() {
// Use Matcher to define entity conditions to process
super(Matcher.all(Position, Velocity));
}
protected process(entities: readonly Entity[]): void {
for (const entity of entities) {
const position = entity.getComponent(Position);
const velocity = entity.getComponent(Velocity);
if (position && velocity) {
position.x += velocity.dx * Time.deltaTime;
position.y += velocity.dy * Time.deltaTime;
}
}
}
}

Suitable for systems that don’t need to process entities individually:

@ECSSystem('Physics')
class PhysicsSystem extends ProcessingSystem {
constructor() {
super(); // No Matcher needed
}
public processSystem(): void {
// Execute physics world step
this.physicsWorld.step(Time.deltaTime);
}
}

Passive systems don’t actively process, mainly used for listening to entity add and remove events:

@ECSSystem('EntityTracker')
class EntityTrackerSystem extends PassiveSystem {
constructor() {
super(Matcher.all(Health));
}
protected onAdded(entity: Entity): void {
console.log(`Health entity added: ${entity.name}`);
}
protected onRemoved(entity: Entity): void {
console.log(`Health entity removed: ${entity.name}`);
}
}

Systems that execute at fixed time intervals:

@ECSSystem('AutoSave')
class AutoSaveSystem extends IntervalSystem {
constructor() {
// Execute every 5 seconds
super(5.0, Matcher.all(SaveData));
}
protected process(entities: readonly Entity[]): void {
console.log('Executing auto save...');
// Save game data
this.saveGameData(entities);
}
private saveGameData(entities: readonly Entity[]): void {
// Save logic
}
}

Matcher is used to define which entities a system needs to process. It provides flexible condition combinations:

// Must have both Position and Velocity components
const matcher1 = Matcher.all(Position, Velocity);
// Must have at least one of Health or Shield components
const matcher2 = Matcher.any(Health, Shield);
// Must not have Dead component
const matcher3 = Matcher.none(Dead);
// Complex combination conditions
const complexMatcher = Matcher.all(Position, Velocity)
.any(Player, Enemy)
.none(Dead, Disabled);
@ECSSystem('Combat')
class CombatSystem extends EntitySystem {
constructor() {
super(complexMatcher);
}
}
// Match by tag
const tagMatcher = Matcher.byTag(1); // Match entities with tag 1
// Match by name
const nameMatcher = Matcher.byName("Player"); // Match entities named "Player"
// Single component match
const componentMatcher = Matcher.byComponent(Health); // Match entities with Health component
// Match no entities
const nothingMatcher = Matcher.nothing(); // For systems that only need lifecycle callbacks
// empty() - Empty condition, matches all entities
const emptyMatcher = Matcher.empty();
// nothing() - Matches no entities, for systems that only need lifecycle methods
const nothingMatcher = Matcher.nothing();
// Use case: Systems that only need onBegin/onEnd lifecycle
@ECSSystem('FrameTimer')
class FrameTimerSystem extends EntitySystem {
constructor() {
super(Matcher.nothing()); // Process no entities
}
protected onBegin(): void {
// Execute at the start of each frame
console.log('Frame started');
}
protected process(entities: readonly Entity[]): void {
// Never called because there are no matching entities
}
protected onEnd(): void {
// Execute at the end of each frame
console.log('Frame ended');
}
}
System TypeUse Case
EntitySystemGeneral system that processes matched entities one by one
ProcessingSystemNo entity list needed, only executes global logic
PassiveSystemOnly listens for entity add/remove events
IntervalSystemExecutes at fixed time intervals
WorkerEntitySystemCompute-intensive tasks requiring multi-threading