Skip to content

Systems

Systems contain game logic. They declare what data they need via parameters, and ESEngine injects the data automatically. Systems operate on all entities in the scene that match the query.

Defining Systems

import { defineSystem, Res, Time, Query, Mut, LocalTransform, Velocity } from 'esengine';
const movementSystem = defineSystem(
[Res(Time), Query(Mut(LocalTransform), Velocity)],
(time, query) => {
for (const [entity, transform, velocity] of query) {
transform.position.x += velocity.linear.x * time.delta;
transform.position.y += velocity.linear.y * time.delta;
}
}
);

This system automatically processes every entity in the scene that has both LocalTransform and Velocity components.

defineSystem takes two arguments:

  1. Parameters — an array of Query, Res, ResMut, or Commands descriptors
  2. Function — receives the resolved parameters in the same order

Registering Systems

Register systems with top-level functions:

import { addSystem, addStartupSystem, addSystemToSchedule, Schedule } from 'esengine';
addStartupSystem(setupSystem); // Schedule.Startup
addSystem(movementSystem); // Schedule.Update
addSystemToSchedule(Schedule.FixedUpdate, physicsSystem);

Schedule Types

ScheduleWhen it runs
StartupOnce at the beginning
FirstEvery frame, before PreUpdate
PreUpdateEvery frame, before Update
UpdateEvery frame (main game logic)
PostUpdateEvery frame, after Update
LastEvery frame, after PostUpdate
FixedPreUpdateAt fixed intervals, before FixedUpdate
FixedUpdateAt fixed intervals (physics)
FixedPostUpdateAt fixed intervals, after FixedUpdate

System Parameters

Commands

Create, modify, and destroy entities at runtime:

import { Commands, Sprite, LocalTransform } from 'esengine';
defineSystem([Commands()], (cmds) => {
// Spawn a new entity with components (chainable)
const bullet = cmds.spawn()
.insert(LocalTransform, { position: { x: 0, y: 0, z: 0 } })
.insert(Sprite, { size: { x: 8, y: 8 } })
.id();
// Modify an existing entity
cmds.entity(bullet)
.insert(Velocity, { linear: { x: 100, y: 0 } })
.remove(Sprite);
// Despawn an entity
cmds.despawn(bullet);
// Insert a resource
cmds.insertResource(Score, { value: 0 });
});

Commands API

MethodReturnsDescription
cmds.spawn()EntityCommandsCreate a new entity, returns a builder
cmds.entity(entity)EntityCommandsGet a builder for an existing entity
cmds.despawn(entity)CommandsQueue entity for destruction
cmds.insertResource(res, value)CommandsInsert or overwrite a resource

EntityCommands API

spawn() and entity() return an EntityCommands builder. All methods are chainable:

MethodReturnsDescription
.insert(component, data?)thisAdd or update a component
.remove(component)thisRemove a component
.id()EntityGet the entity ID

Query

Iterate entities with specific components:

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

See Queries for the full API.

Res (read-only resource)

import { Res, Time } from 'esengine';
defineSystem([Res(Time)], (time) => {
console.log(`Delta: ${time.delta}s`);
});

ResMut (mutable resource)

import { ResMut } from 'esengine';
defineSystem([ResMut(GameState)], (state) => {
state.value.score += 10;
});

See Resources for the full API.

Combining Parameters

defineSystem(
[Commands(), Res(Time), Res(Input), Query(Mut(LocalTransform), Velocity)],
(cmds, time, input, query) => {
// All parameters available
}
);

Example: Player Movement

Define a Speed component and attach it to the player entity in the editor. Then write a system:

src/components/Speed.ts
import { defineComponent } from 'esengine';
export const Speed = defineComponent('Speed', { value: 200 });
src/systems/movement.ts
import { defineSystem, addSystem, Res, Time, Input, Query, Mut, LocalTransform } from 'esengine';
import { Speed } from '../components/Speed';
addSystem(defineSystem(
[Res(Time), Res(Input), Query(Mut(LocalTransform), Speed)],
(time, input, query) => {
for (const [entity, transform, speed] of query) {
if (input.isKeyDown('KeyD')) {
transform.position.x += speed.value * time.delta;
}
if (input.isKeyDown('KeyA')) {
transform.position.x -= speed.value * time.delta;
}
}
}
));

Next Steps

  • Queries — query filters and iteration methods
  • Resources — global singleton data