Actors
Actors are prebuilt Excalibur Entities that come with the batteries included for most applications. Such as position, velocity, graphics, and collision detection.
Actors are the recommended way to use Excalibur to show something on the screen.
If you don't want these built-ins and you want to build only what you want, read about entities here
Actors use collision geometry for pointer events meaning they need at least a width/height or custom collider, but can be configured to use graphics bounds for pointer testing.
Actor.pointer and PointerComponent
Read more here
Basic actors
For quick and dirty games, you may be able to create an instance of an Actor and manipulate it directly.
Actors (and other entities) must be added to a Scene to be drawn and updated on-screen.
game.add is a convenience method for adding an actor to the current scene. The equivalent verbose call is game.currentScene.add.
Custom actors
For "real-world" games, it is common to extend the Actor
class.
This is optional, and gives you much greater control and encapsulates logic for that
actor.
ts
classShootemUpPlayer extendsex .Actor {publichealth : number = 100;publicammo : number = 20;constructor() {super({x : 10,y : 10 });}shoot () {if (this.ammo < 1) {return;}this.ammo -= 1;}}
ts
classShootemUpPlayer extendsex .Actor {publichealth : number = 100;publicammo : number = 20;constructor() {super({x : 10,y : 10 });}shoot () {if (this.ammo < 1) {return;}this.ammo -= 1;}}
Custom actors make it easy to hook into the actor lifecycle and encapsulate the actor's state better than a basic actor.
Actor lifecycle
An actor has a basic lifecycle that dictates how it is initialized, updated, and drawn. Once an actor is part of a scene, it will follow this lifecycle.
Updating actors
In most games, things are happening on screen: the background is parallax-ing, your hero responds to input, and enemies shoot bullets. In Excalibur, the logic that updates game state is run during the update loop. Actors are a way to encapsulate that logic, such as a Player
, Enemy
, and MenuButton
. Actors are designed to be pretty much anything!
Initialization
You should override the Actor.onInitialize method to perform any startup logic
for an actor (such as configuring state). onInitialize
gets called
once before the first frame of an actor is drawn/updated. It is passed
an instance of Engine to access global state or perform coordinate math.
This is the recommended way to manage startup logic for actor, not the constructor since you don't incur the cost of initialization until an actor is ready to be updated in the game.
ts
classPlayer extendsex .Actor {publiclevel = 1;publicendurance = 0;publicfortitude = 0;constructor() {super({x : 50,y : 50 });}publiconInitialize (_engine :ex .Engine ) {this.endurance = 20;this.fortitude = 16;}publicgetMaxHealth () {return 0.4 * this.endurance + 0.9 * this.fortitude + this.level * 1.2;}}
ts
classPlayer extendsex .Actor {publiclevel = 1;publicendurance = 0;publicfortitude = 0;constructor() {super({x : 50,y : 50 });}publiconInitialize (_engine :ex .Engine ) {this.endurance = 20;this.fortitude = 16;}publicgetMaxHealth () {return 0.4 * this.endurance + 0.9 * this.fortitude + this.level * 1.2;}}
There are three ways to hook into the update loop of an actor: Actor.onPreUpdate, Actor.update, and Actor.onPostUpdate. Actors (and other entities in Excalibur) all have "core" logic that runs in the update or draw loop. The pre- and post-method hooks allow you to choose when you want to run logic in each phase. Normally you will run logic in the "post" hook but sometimes you may want to completely override the core logic or run logic that uses state that was updated before the core logic runs.
All update methods are passed an instance of the Excalibur engine, which
can be used to perform coordinate math or access global state. It is also
passed delta
which is the time in milliseconds since the last frame, which can be used
to perform time-based movement or time-based math (such as a timer).
Reference Actor lifecycle for a breakdown of each phase and when things are executed.
Update
You can override the core Actor.update but it is important to call the super.update(...)
method in order to preserve the Excalibur core update implementation.
ts
classPlayer extendsex .Actor {publicupdate (engine :ex .Engine ,delta : number) {// Custom update heresuper.update (engine ,delta );// Custom update here}}
ts
classPlayer extendsex .Actor {publicupdate (engine :ex .Engine ,delta : number) {// Custom update heresuper.update (engine ,delta );// Custom update here}}
Actors will not function properly if you override Actor.update()
without calling the super.update()
Pre-update
Override the Actor.onPreUpdate method to update the state of your actor before Actor.update.
Important: This logic will run before the core Excalibur update logic runs, so you may not have the latest transform matrix applied or other positional information updated. Essentially you will be working with the last frame's state.
ts
classPlayer extendsex .Actor {publiconPreUpdate (engine :ex .Engine ,delta : number) {// update velocitythis.vel .setTo (-1, 0);}}
ts
classPlayer extendsex .Actor {publiconPreUpdate (engine :ex .Engine ,delta : number) {// update velocitythis.vel .setTo (-1, 0);}}
Post-update
Actor.onPostUpdate is called after Actor.update to prepare state for the next frame. Things that need to be updated include state, drawing, and position.
Actor.onPostUpdate is the recommended method to override for adding update logic to your actors since it runs after Excalibur has done all the update logic for the frame and before things get drawn to the screen.
ts
classPlayer extendsex .Actor {privatehealth : number = 100;publiconPostUpdate (engine :ex .Engine ,delta : number) {// check if player diedif (this.health <= 0) {this.kill ();return;}}}
ts
classPlayer extendsex .Actor {privatehealth : number = 100;publiconPostUpdate (engine :ex .Engine ,delta : number) {// check if player diedif (this.health <= 0) {this.kill ();return;}}}
Drawing actors
Actors by default have no associated graphics, meaning that they will be rendered non-visually unless you've assigned a default Actor.color.
If an actor has a color and width/height or radius is set, it will draw a box or circle in that color.
This can be useful at the beginning of development when you're just tinkering but for most games you might want to add sprites, animations, and other drawings over simple shapes.
Sprite graphics
Think of an ImageSource as the raw image file that will be loaded into Excalibur from a url.
In order for it to be drawn it must be converted to a Sprite. A common pattern is to define graphics in the onInitialize
method of an actor.
ts
import {playerImage } from './resources';classPlayer extendsex .Actor {publiconInitialize () {// Set as the default drawingthis.graphics .use (playerImage .toSprite ());}}
ts
import {playerImage } from './resources';classPlayer extendsex .Actor {publiconInitialize () {// Set as the default drawingthis.graphics .use (playerImage .toSprite ());}}
Important! Actors have a default anchor of (0.5, 0.5) which means their graphics are positioned in their center (not top-left) by default.
HTML Canvas graphics
If you need to do some custom drawing using the CanvasRenderingContext2D
the new Canvas graphic has your back.
ts
constcanvas = newex .Canvas ({cache : true, // If true draw once until flagged dirty again, otherwise draw every timedraw : (ctx :CanvasRenderingContext2D ) => {ctx .fillStyle = 'red';ctx .fillRect (0, 0, 200, 200);}});classPlayer extendsex .Actor {publiconInitialize (engine :ex .Engine ) {// set as the "default" drawingthis.graphics .use (canvas );}}
ts
constcanvas = newex .Canvas ({cache : true, // If true draw once until flagged dirty again, otherwise draw every timedraw : (ctx :CanvasRenderingContext2D ) => {ctx .fillStyle = 'red';ctx .fillRect (0, 0, 200, 200);}});classPlayer extendsex .Actor {publiconInitialize (engine :ex .Engine ) {// set as the "default" drawingthis.graphics .use (canvas );}}
Pre-draw
Actor.graphics.onPreDraw is run before the core draw logic to prepare the frame.
Actor.graphics.onPreDraw runs before Excalibur has run all its draw logic to apply effects, transform information, etc. so you essentially are working with the last frame's draw state.
Reference Actor lifecycle for a breakdown of each phase and when things are executed.
ts
classPlayer extendsex .Actor {constructor(config ?:ex .ActorArgs ) {super(config );/*** ADVANCED: This is run before Actor.graphics.onPreDraw core logic.*/this.graphics .onPreDraw = (ctx :ex .ExcaliburGraphicsContext ,delta : number) => {// custom drawing};}}
ts
classPlayer extendsex .Actor {constructor(config ?:ex .ActorArgs ) {super(config );/*** ADVANCED: This is run before Actor.graphics.onPreDraw core logic.*/this.graphics .onPreDraw = (ctx :ex .ExcaliburGraphicsContext ,delta : number) => {// custom drawing};}}
Post-draw
Actor.graphics.onPostDraw is run after the core draw and will draw in the current frame.
Actor.graphics.onPostDraw is the recommended method to override since Excalibur has run its core draw logic and you can now customize what gets drawn during the current frame.
ts
classPlayer extendsex .Actor {constructor(config ?:ex .ActorArgs ) {super(config );/*** ADVANCED: This is run after the core draw logic.*/this.graphics .onPostDraw = (ctx :ex .ExcaliburGraphicsContext ,delta : number) => {// custom drawing};}}
ts
classPlayer extendsex .Actor {constructor(config ?:ex .ActorArgs ) {super(config );/*** ADVANCED: This is run after the core draw logic.*/this.graphics .onPostDraw = (ctx :ex .ExcaliburGraphicsContext ,delta : number) => {// custom drawing};}}
Adding actors to the scene
For an Actor to be drawn and updated, it needs to be part of the "scene graph". The Engine provides several easy ways to quickly add/remove actors from the current scene.
ts
constplayer = newex .Actor ();constenemy = newex .Actor ();// add them to the "root" scenegame .add (player );game .add (enemy );// start gamegame .start ();
ts
constplayer = newex .Actor ();constenemy = newex .Actor ();// add them to the "root" scenegame .add (player );game .add (enemy );// start gamegame .start ();
You can also add actors to a Scene instance specifically:
ts
constlevel1 = newex .Scene ();// add actors to level1level1 .add (player );level1 .add (enemy );// add level1 to the gamegame .add ('level1',level1 );// start the gamegame .start ();// after player clicks start game, for examplegame .goToScene ('level1');
ts
constlevel1 = newex .Scene ();// add actors to level1level1 .add (player );level1 .add (enemy );// add level1 to the gamegame .add ('level1',level1 );// start the gamegame .start ();// after player clicks start game, for examplegame .goToScene ('level1');
Collision Detection
By default Actors do not participate in collisions. If you wish to make an actor participate, you need to switch from the default prevent collision to an active, fixed, or passive collision type.
For more information on collisions, see Physics.