Skip to main content

Systems

Core behavior in Excalibur is implemented by Systems. Systems process all entities that have matching component types and perform some action.

Examples of systems are the GraphicsSystem, MotionSystem, CollisionSystem, and DebugSystem

System

Each Excalibur System loops through all entities in System.priority order. There are a list of built in system priorities SystemPriority that can be used to assign priority. Lower number means higher priority which runs first.

typescript
/**
* Higher priorities run earlier than others in the system update
*/
export const SystemPriority = {
Highest: -Infinity,
Higher: -5,
Average: 0,
Lower: 5,
Lowest: Infinity
} as const;
typescript
/**
* Higher priorities run earlier than others in the system update
*/
export const SystemPriority = {
Highest: -Infinity,
Higher: -5,
Average: 0,
Lower: 5,
Lowest: Infinity
} as const;

System Types

There are two system types update and draw.

SystemType.Update type systems all run before SystemType.Draw type systems.

Lifecycle

Systems execute in this order

  1. one-time constructor(world: World)
    • Initialize your query here
  2. one-time initialize(world: World, scene: Scene)
    • Initialize anything that relies on the scene/engine here
  3. every frame preupdate(scene: Scene, elapsedMs: number)
    • Run setup code for the update
  4. every frame update(delta: number)
    • Update all your entities here
  5. every frame preupdate(scene: Scene, elapsedMs: number)
    • Run cleanup code for the update

Built in Systems

Excalibur has a few built in systems that are used to enable the default behavior. These Systems are defined at Scene constructor time.

MotionSystem

The motion system implements motion on entities, like Actors moving with velocity and acceleration.

This system makes use of the TransformComponent and MotionComponent to implement motion.

If a BodyComponent is present that will be used to apply sleep or global acceleration Physics.acc to all CollisionType.Active bodies.

CollisionSystem

The collision system uses the TransformComponent, MotionComponent, and ColliderComponent to implement collision detection and resolution behavior.

GraphicsSystem

The Excalibur GraphicsSystem takes any entity with a TransformComponent and a GraphicsComponent and draws it to the screen using the ExcaliburGraphicsContext.

DebugSystem

The debug system is slightly odd, it operates on all entities with a TransformComponent to display debug information to the screen when Engine.showDebug is enabled.

Read more about the debug options here Debug

Implementing your own Components & Systems

To build your own component, extend the Excalibur Component abstract class and pick a unique type name (duplicate type names will cause problems).

For custom component types it is recommended you prefix your types, like type = 'myCustomPrefixTransform'

In this example, we create a "search" type component, that searches for a target position. Notice how this component implementation is mostly data.

typescript
class SearchComponent extends ex.Component {
constructor(public target: ex.Vector) {
super();
}
}
class SearchSystem extends ex.System {
query: Query<typeof SearchComponent>;
constructor(world: World) {
this.query = world.query([TransformComponent, SearchComponent]);
}
// Lower numbers mean higher priority
// 99 is low priority
public priority = 99;
// Run this system in the "update" phase
public systemType = ex.SystemType.Update
private _searchSpeed = 100 // pixels/sec
public update(delta: number) {
for (let entity of this.query.entities) {
const target = entity.get(SearchComponent).target;
const transform = entity.get(ex.TransformComponent);
const direction = target.sub(transform.pos);
const motion = direction.normalize().scale(this._searchSpeed);
// Moves these entities towards the target at 10 pixels per second
transform.pos = transform.pos.add(motion.scale(delta / 1000))
}
}
}
const scene = new ex.Scene();
scene.world.add(new SearchSystem());
// Actors come with batteries included built in features
const actor = new ex.Actor({
pos: ex.vec(100, 100),
width: 30,
height: 30,
color: ex.Color.Red
});
actor.addComponent(new SearchComponent(ex.vec(600, 400)));
typescript
class SearchComponent extends ex.Component {
constructor(public target: ex.Vector) {
super();
}
}
class SearchSystem extends ex.System {
query: Query<typeof SearchComponent>;
constructor(world: World) {
this.query = world.query([TransformComponent, SearchComponent]);
}
// Lower numbers mean higher priority
// 99 is low priority
public priority = 99;
// Run this system in the "update" phase
public systemType = ex.SystemType.Update
private _searchSpeed = 100 // pixels/sec
public update(delta: number) {
for (let entity of this.query.entities) {
const target = entity.get(SearchComponent).target;
const transform = entity.get(ex.TransformComponent);
const direction = target.sub(transform.pos);
const motion = direction.normalize().scale(this._searchSpeed);
// Moves these entities towards the target at 10 pixels per second
transform.pos = transform.pos.add(motion.scale(delta / 1000))
}
}
}
const scene = new ex.Scene();
scene.world.add(new SearchSystem());
// Actors come with batteries included built in features
const actor = new ex.Actor({
pos: ex.vec(100, 100),
width: 30,
height: 30,
color: ex.Color.Red
});
actor.addComponent(new SearchComponent(ex.vec(600, 400)));

Any entity that has the new component attached will be processed by the new system once added to the world!