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
Each Excalibur System loops through all entities in System.priority
order.
There are two system types update and draw.
SystemType.Update type systems all run before SystemType.Draw type systems.
Excalibur has a few built in systems that are used to enable the default behavior. These Systems
are defined at Scene constructor time.
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.
The collision system uses the TransformComponent, MotionComponent, and ColliderComponent to implement collision detection and resolution behavior.
The Excalibur GraphicsSystem takes any entity with a TransformComponent and a GraphicsComponent and draws it to the screen using the ExcaliburGraphicsContext.
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
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.
class SearchComponent extends ex.Component<'search'> {
public readonly type = 'search'
constructor(public target: ex.Vector) {
super();
}
}
class SearchSystem extends ex.System<ex.TransformComponent | SearchComponent> {
// Types need to be listed as a const literal
public readonly types = ['ex.transform', 'search'] as const;
// 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(entities: ex.Entity[], delta: number) {
for (let entity of entities) {
const target = entity.get(SearchComponent)!.target;
// ex.TransformComponent is a built in type
const transform = entity.get(ex.TransformComponent) as 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!