Use this skill when working with the Phaser 4 event system. Covers EventEmitter, scene events, game events, custom events, and event-driven communication. Triggers on: events, on, emit, EventEmitter, scene events, listeners.
Use this skill when working with the Phaser 4 event system. Covers EventEmitter, scene events, game events, custom events, and event-driven communication. Triggers on: events, on, emit, EventEmitter, scene events, listeners.
Events System
Phaser uses the EventEmitter pattern (via eventemitter3) throughout the entire framework. Every major system -- Game, Scene, Input, Loader, Cameras, Sound, Tweens, Physics, Textures, Animations -- is an EventEmitter or contains one. Events use lowercase string keys. Phaser provides named constants for all built-in events to avoid typos and enable IDE autocomplete.
Some events use a key-suffix pattern for per-key listening:
// Loader: listen for a specific file completingthis.load.on(Phaser.Loader.Events.FILE_KEY_COMPLETE + 'image-logo', (key, type, data) => {});
// String value: 'filecomplete-image-logo'// Animations: listen for a specific animation completing on a sprite
sprite.on(Phaser.Animations.Events.ANIMATION_COMPLETE_KEY + 'walk', () => {});
// String value: 'animationcomplete-walk'// Textures: listen for a specific texture being addedthis.textures.on(Phaser.Textures.Events.ADD_KEY + 'myTexture', () => {});
// String value: 'addtexture-myTexture'
Context (Third Argument)
The third argument to on/once sets this inside the callback. Defaults to the emitter.
// 'this' inside handler refers to the scenethis.input.on('pointerdown', function (pointer) {
this.cameras.main.shake(100); // 'this' = scene
}, this);
// Arrow functions ignore the context argument (they capture lexical 'this')this.input.on('pointerdown', (pointer) => {
this.cameras.main.shake(100); // 'this' = enclosing scope (scene in create)
});
create() {
this.events.on(Phaser.Scenes.Events.UPDATE, this.onUpdate, this);
// CRITICAL: always clean up on shutdown to prevent leaks on scene restartthis.events.on(Phaser.Scenes.Events.SHUTDOWN, () => {
this.events.off(Phaser.Scenes.Events.UPDATE, this.onUpdate, this);
this.input.off('pointerdown', this.onPointerDown, this);
});
}
Game-Level Events
// game.events fires on the Game instance, shared across all scenes// Access from a scene via this.game.eventsthis.game.events.on(Phaser.Core.Events.BLUR, this.handleBlur, this);
this.game.events.on(Phaser.Core.Events.VISIBLE, this.handleVisible, this);
Inter-Scene Communication
// METHOD 1: game.events — a global event bus accessible from all scenes// Scene A emits:this.game.events.emit('score-changed', this.score);
// Scene B listens:this.game.events.on('score-changed', (score) => { this.scoreText.setText(score); });
// METHOD 2: this.registry — a shared DataManager across all scenes// The registry is a Phaser.Data.DataManager on the Game instance.// Scene A sets data:this.registry.set('score', 100);
// Scene B listens for changes:this.registry.events.on('changedata-score', (parent, value, previousValue) => {
this.scoreText.setText(value);
});
// METHOD 3: Direct scene access via ScenePluginthis.scene.get('UIScene').events.emit('update-health', hp);
Emitter: this.events (the Scene's Systems EventEmitter)
Constant
String
When
BOOT
'boot'
Scene Systems boot (for plugins)
READY
'ready'
Scene Systems fully ready
START
'start'
Scene starts running
CREATE
'create'
After Scene.create() completes
PRE_UPDATE
'preupdate'
Before update each frame
UPDATE
'update'
Main update each frame
POST_UPDATE
'postupdate'
After update each frame
PRE_RENDER
'prerender'
Before render each frame
RENDER
'render'
During render each frame
PAUSE
'pause'
Scene paused
RESUME
'resume'
Scene resumed from pause
SLEEP
'sleep'
Scene put to sleep
WAKE
'wake'
Scene woken from sleep
SHUTDOWN
'shutdown'
Scene shutting down (may restart)
DESTROY
'destroy'
Scene permanently destroyed
ADDED_TO_SCENE
'addedtoscene'
GameObject added to scene
REMOVED_FROM_SCENE
'removedfromscene'
GameObject removed from scene
TRANSITION_INIT
'transitioninit'
Transition initialized (target scene)
TRANSITION_START
'transitionstart'
Transition started (target scene)
TRANSITION_OUT
'transitionout'
Transition out (source scene)
TRANSITION_COMPLETE
'transitioncomplete'
Transition finished
TRANSITION_WAKE
'transitionwake'
Transition wakes target scene
Game Events (Phaser.Core.Events)
Emitter: this.game.events or game.events
Constant
String
When
BOOT
'boot'
Game instance finished booting
READY
'ready'
Game ready to start running
SYSTEM_READY
'systemready'
All global systems ready
PRE_STEP
'prestep'
Before game loop step
STEP
'step'
Main game loop step
POST_STEP
'poststep'
After game loop step
PRE_RENDER
'prerender'
Before rendering all scenes
POST_RENDER
'postrender'
After rendering all scenes
PAUSE
'pause'
Game paused
RESUME
'resume'
Game resumed
BLUR
'blur'
Browser tab lost focus
FOCUS
'focus'
Browser tab gained focus
HIDDEN
'hidden'
Page Visibility API: hidden
VISIBLE
'visible'
Page Visibility API: visible
CONTEXT_LOST
'contextlost'
WebGL context lost
DESTROY
'destroy'
Game being destroyed
Input Events (Phaser.Input.Events)
Emitter: this.input (scene-level) or individual GameObjects. Events exist at three levels: scene-level (this.input), scene-level with gameobject prefix, and directly on interactive GameObjects. See ../input-keyboard-mouse-touch/SKILL.md for full usage.
Per-GameObject events (emitted on the GameObject itself, requires setInteractive()):GAMEOBJECT_POINTER_DOWN'pointerdown' | GAMEOBJECT_POINTER_UP'pointerup' | GAMEOBJECT_POINTER_MOVE'pointermove' | GAMEOBJECT_POINTER_OVER'pointerover' | GAMEOBJECT_POINTER_OUT'pointerout' | GAMEOBJECT_POINTER_WHEEL'wheel'
Drag events (on this.input and on GameObjects with same string):DRAG_START/GAMEOBJECT_DRAG_START'dragstart' | DRAG/GAMEOBJECT_DRAG'drag' | DRAG_END/GAMEOBJECT_DRAG_END'dragend' | DRAG_ENTER/GAMEOBJECT_DRAG_ENTER'dragenter' | DRAG_OVER/GAMEOBJECT_DRAG_OVER'dragover' | DRAG_LEAVE/GAMEOBJECT_DRAG_LEAVE'dragleave' | DROP/GAMEOBJECT_DROP'drop'
You must pass the SAME function reference AND the same context/scope to off() that you used with on(). Anonymous or inline arrow functions cannot be removed.
// BAD: arrow function cannot be removed laterthis.events.on('update', () => { this.doStuff(); });
// GOOD: named method can be removedthis.events.on('update', this.onUpdate, this);
this.events.off('update', this.onUpdate, this);
once() Auto-Removes
once() automatically removes the listener after first fire. No manual cleanup needed.
emitter.listenerCount('update'); // number of listeners for an event
emitter.eventNames(); // ['update', 'player-died'] -- all registered event names
emitter.removeAllListeners('player-died'); // remove all listeners for one event
emitter.removeAllListeners(); // remove ALL listeners for ALL events
The most common source of bugs. If a scene uses on() and the scene restarts via scene.restart(), old listeners persist because on() does not auto-remove. Each restart adds duplicate listeners.
// BAD: leaks listeners on every scene restartcreate() {
this.input.on('pointerdown', this.shoot, this);
}
// GOOD: clean up in shutdowncreate() {
this.input.on('pointerdown', this.shoot, this);
this.events.once(Phaser.Scenes.Events.SHUTDOWN, () => {
this.input.off('pointerdown', this.shoot, this);
});
}
// ALSO GOOD: use once() for events you only need fired oncecreate() {
this.events.once(Phaser.Scenes.Events.CREATE, this.onFirstCreate, this);
}
shutdown vs destroy
SHUTDOWN fires when a scene stops but can restart later. Clean up listeners here.
DESTROY fires when a scene is permanently removed. Use for final cleanup.
A scene restart fires SHUTDOWN then START then CREATE. It does NOT fire DESTROY.
Context Binding
The third argument to on/once sets this inside the callback. Without it, this defaults to the emitter, not the scene. Use this as the third argument with regular functions, or use arrow functions (which capture lexical this).
off() Requires Exact References
off() only works if you pass the exact same function reference (and context) used with on(). Anonymous functions or arrow literals cannot be removed -- store a reference or use a class method.
Input Event Hierarchy
Input events fire in order: (1) GAMEOBJECT_POINTER_DOWN on the GameObject, (2) GAMEOBJECT_DOWN on this.input, (3) POINTER_DOWN on this.input. Higher handlers can stop propagation.
Game Events vs Scene Events
this.game.events and this.events are different emitters. Game events fire once per game loop tick across all scenes. Scene events fire per-scene. Listeners on game.events persist across scene restarts -- always clean them up on SHUTDOWN: