Skip to main content

Scenes

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.

js
const game = new ex.Engine(...);
const player = new ex.Actor();
const enemy = new ex.Actor();
// add them to the "root" scene
game.add(player);
game.add(enemy);
// start game
game.start();
js
const game = new ex.Engine(...);
const player = new ex.Actor();
const enemy = new ex.Actor();
// add them to the "root" scene
game.add(player);
game.add(enemy);
// start game
game.start();

You can also add actors to a scene instance specifically using Scene.add:

js
const game = new ex.Engine();
const level1 = new ex.Scene();
const player = new ex.Actor();
const enemy = new ex.Actor();
// add actors to level1
level1.add(player);
level1.add(enemy);
// add level1 to the game
game.add('level1', level1);
// start the game
game.start().then(() => {
// after player clicks start game, for example
game.goToScene('level1');
});
js
const game = new ex.Engine();
const level1 = new ex.Scene();
const player = new ex.Actor();
const enemy = new ex.Actor();
// add actors to level1
level1.add(player);
level1.add(enemy);
// add level1 to the game
game.add('level1', level1);
// start the game
game.start().then(() => {
// after player clicks start game, for example
game.goToScene('level1');
});

Scene lifecycle

A scene has a basic lifecycle that dictates how it is initialized, updated, and drawn. Once a scene is added to the engine it will follow this lifecycle:

Scene Lifecycle

For more complex games, you might want more control over a scene in which case you can extend Scene. This is useful for menus, custom loaders, and levels.

Adding a scene

Use Engine.add to add a new scene to the game. Each scene has a string key you can assign. You can then use Engine.goToScene to switch scenes which runs the scene lifecycle hooks.

ts
class MainMenu extends ex.Scene {}
// add to game and activate it
game.add('mainmenu', new MainMenu());
game.goToScene('mainmenu');
ts
class MainMenu extends ex.Scene {}
// add to game and activate it
game.add('mainmenu', new MainMenu());
game.goToScene('mainmenu');

Initialization

note

This is the recommended hook for setting up scene state

Scene.onInitialize is called once when the scene is created for use in the game. It is called before Scene.onActivate and can be used to set up the scene state. This is typically where you'd add any actors to the scene, set up initial state, and other startup tasks.

ts
class MainMenu extends Scene {
private _startButton: StartButton;
/**
* Start-up logic, called once
*/
public onInitialize(engine: Engine) {
// initialize scene actors
this._startButton = new StartButton();
this.add(this._startButton);
}
}
ts
class MainMenu extends Scene {
private _startButton: StartButton;
/**
* Start-up logic, called once
*/
public onInitialize(engine: Engine) {
// initialize scene actors
this._startButton = new StartButton();
this.add(this._startButton);
}
}

You can even call Engine.start to preload assets for your scene, to avoid having to load them at game initialization time:

ts
class MainMenu extends Scene {
private _loaded: boolean = false;
/**
* Start-up logic, called once
*/
public onInitialize(engine: Engine) {
// load scene-specific assets
engine.start(sceneLoader).then(() => {
this._loaded = true;
});
}
}
ts
class MainMenu extends Scene {
private _loaded: boolean = false;
/**
* Start-up logic, called once
*/
public onInitialize(engine: Engine) {
// load scene-specific assets
engine.start(sceneLoader).then(() => {
this._loaded = true;
});
}
}

Activation

Scene.onActivate is called when the engine switches to the scene. It may be called more than once during the lifetime of a game, if you switch back and forth between scenes. It is useful for taking action before showing the scene. You may use this hook over onInitialize for anything specific to the context in which the scene was activated.

Data can be passed to a scene during activation via the goToScene('sceneKey', { some: 'data' }).

ts
interface MyLevelData {
spawnLocation: Vector;
}
class MainMenu extends Scene<MyLevelData> {
private startButton: StartButton;
/**
* Each time the scene is entered (Engine.goToScene)
*/
public onActivate(ctx: SceneActivationContext<MyLevelData>) {
const { spawnLocation } = ctx.data;
console.log(spawnLocation);
if (ctx.previousScene instanceof Level) {
this.startButton.text = 'Resume game';
} else {
this.startButton.text = 'Start game';
}
}
}
ts
interface MyLevelData {
spawnLocation: Vector;
}
class MainMenu extends Scene<MyLevelData> {
private startButton: StartButton;
/**
* Each time the scene is entered (Engine.goToScene)
*/
public onActivate(ctx: SceneActivationContext<MyLevelData>) {
const { spawnLocation } = ctx.data;
console.log(spawnLocation);
if (ctx.previousScene instanceof Level) {
this.startButton.text = 'Resume game';
} else {
this.startButton.text = 'Start game';
}
}
}

Deactivation

Scene.onDeactivate is called when the engine exits a scene and is typically used for cleanup, exit tasks, and garbage collection.

ts
class Level extends Scene {
/**
* Each time the scene is exited (Engine.goToScene)
*/
public onDeactivate(ctx: SceneActivationContext) {
this.saveState();
}
}
ts
class Level extends Scene {
/**
* Each time the scene is exited (Engine.goToScene)
*/
public onDeactivate(ctx: SceneActivationContext) {
this.saveState();
}
}

Loading

Scenes can now load resources specific to them by implementing the Scene.onPreLoad.

typescript
import * as ex from 'excalibur';
class LevelOne extends ex.Scene {
spriteFont!: ex.ImageSource;
playerSpriteSheet!: ex.ImageSource;
override onPreLoad(loader: DefaultLoader) {
this.spriteFont = new ex.ImageSource('./res/spritefont.png');
this.playerSpriteSheet = new ex.ImageSource('./res/player-sheet.png');
loader.addResource(this.spriteFont);
loader.addResource(this.playerSpriteSheet);
}
}
typescript
import * as ex from 'excalibur';
class LevelOne extends ex.Scene {
spriteFont!: ex.ImageSource;
playerSpriteSheet!: ex.ImageSource;
override onPreLoad(loader: DefaultLoader) {
this.spriteFont = new ex.ImageSource('./res/spritefont.png');
this.playerSpriteSheet = new ex.ImageSource('./res/player-sheet.png');
loader.addResource(this.spriteFont);
loader.addResource(this.playerSpriteSheet);
}
}

Read more in the loaders documentation;