关于 events-system
面向 Phaser 开发者的事件系统技能指南,介绍框架各系统的事件监听、触发与清理方式,并列出常用事件命名空间。
主要功能
- 01监听与触发事件
- 02使用具名事件常量
- 03设置回调上下文
- 04管理场景生命周期监听
- 05查阅各系统事件目录
适用场景
- 01响应指针输入与拖放
- 02处理场景启动和关闭
- 03在场景之间传递事件
- 04跟踪资源加载与动画状态
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ phaserjs/ phaser --skill events-system安装后在 Claude Code 中输入/events-system确认已加载。
详细介绍
事件系统
Phaser 在整个框架中都使用 EventEmitter 模式(通过 eventemitter3)。每个主要系统 —— Game、Scene、Input、Loader、Cameras、Sound、Tweens、Physics、Textures、Animations —— 都是 EventEmitter,或包含一个 EventEmitter。事件使用小写字符串键。Phaser 为所有内置事件提供了具名常量,以避免拼写错误并支持 IDE 自动补全。
关键源码路径: src/events/EventEmitter.js、src/scene/events/、src/core/events/、src/input/events/、src/loader/events/、src/animations/events/、src/cameras/2d/events/、src/sound/events/、src/tweens/events/、src/physics/arcade/events/、src/textures/events/、src/gameobjects/events/、src/time/events/
相关技能: ../scenes/SKILL.md, ../input-keyboard-mouse-touch/SKILL.md
快速开始
// on — listen for an event (persists until removed)
this.input.on('pointerdown', (pointer) => {
console.log('clicked at', pointer.x, pointer.y);
});
// once — listen for an event, auto-removes after first fire
this.events.once('shutdown', () => {
console.log('scene shutting down');
});
// off — remove a specific listener (must pass same function reference)
const handler = (pointer) => { /* ... */ };
this.input.on('pointerdown', handler);
this.input.off('pointerdown', handler);
// emit — fire a custom event with arguments
this.events.emit('player-died', this.player, this.score);
// removeAllListeners — remove all listeners for an event (or all events)
this.events.removeAllListeners('player-died');
this.events.removeAllListeners(); // all events
使用具名常量(推荐)
// Always prefer constants over raw strings to prevent typos
this.events.on(Phaser.Scenes.Events.UPDATE, (time, delta) => {
// runs every frame
});
this.input.on(Phaser.Input.Events.POINTER_DOWN, (pointer) => {
// pointer pressed
});
this.game.events.on(Phaser.Core.Events.BLUR, () => {
// browser tab lost focus
});
核心概念
EventEmitter 基类
Phaser.Events.EventEmitter 继承自 eventemitter3。它添加了 shutdown() 和 destroy() 方法,二者都会调用 removeAllListeners()。
完整 API(继承自 eventemitter3):
| 方法 | 描述 |
|---|---|
on(event, fn, context?) | 添加持久监听器。返回 this 以支持链式调用 |
addListener(event, fn, context?) | on 的别名 |
once(event, fn, context?) | 添加一次性监听器;首次触发后自动移除 |
off(event, fn?, context?, once?) | 移除监听器。移除特定监听器时必须传入相同的 fn 引用 |
removeListener(event, fn?, context?, once?) | off 的别名 |
removeAllListeners(event?) | 移除某个事件的所有监听器,若无参数则移除所有事件的所有监听器 |
emit(event, ...args) | 触发事件。若有监听器则返回 true |
listeners(event) | 返回某个事件的监听器函数数组 |
listenerCount(event) | 返回某个事件的监听器数量 |
eventNames() | 返回拥有监听器的事件名称数组 |
shutdown() | 调用 removeAllListeners() |
destroy() | 调用 removeAllListeners() |
事件字符串与常量
每个内置事件都是一个以小写字符串形式导出的常量。常量名与字符串之间的映射是可预测的:
Phaser.Scenes.Events.UPDATE // 'update'
Phaser.Scenes.Events.PRE_UPDATE // 'preupdate'
Phaser.Scenes.Events.SHUTDOWN // 'shutdown'
Phaser.Core.Events.BOOT // 'boot'
Phaser.Input.Events.POINTER_DOWN // 'pointerdown'
某些事件使用键后缀模式以实现按键监听:
// Loader: listen for a specific file completing
this.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 added
this.textures.on(Phaser.Textures.Events.ADD_KEY + 'myTexture', () => {});
// String value: 'addtexture-myTexture'
上下文(第三个参数)
on/once 的第三个参数用于设置回调内部的 this。默认为事件发射器本身。
// 'this' inside handler refers to the scene
this.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)
});
常见模式
场景生命周期事件
帧循环顺序:preupdate -> update -> Scene.update() -> postupdate -> prerender -> render
create() {
this.events.on(Phaser.Scenes.Events.UPDATE, this.onUpdate, this);
// CRITICAL: always clean up on shutdown to prevent leaks on scene restart
this.events.on(Phaser.Scenes.Events.SHUTDOWN, () => {
this.events.off(Phaser.Scenes.Events.UPDATE, this.onUpdate, this);
this.input.off('pointerdown', this.onPointerDown, this);
});
}
游戏级事件
// game.events fires on the Game instance, shared across all scenes
// Access from a scene via this.game.events
this.game.events.on(Phaser.Core.Events.BLUR, this.handleBlur, this);
this.game.events.on(Phaser.Core.Events.VISIBLE, this.handleVisible, this);
场景间通信
// 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 ScenePlugin
this.scene.get('UIScene').events.emit('update-health', hp);
自定义事件
// Emit custom events with arbitrary data arguments
this.events.emit('player-died', this.player, { lives: this.lives });
this.events.on('player-died', (player, data) => {
console.log('Lives remaining:', data.lives);
});
所有事件命名空间参考
场景事件(Phaser.Scenes.Events)
发射器:this.events(场景的 Systems EventEmitter)
| 常量 | 字符串 | 触发时机 |
|---|---|---|
BOOT | 'boot' | 场景系统启动(用于插件) |
READY | 'ready' | 场景系统完全就绪 |
START | 'start' | 场景开始运行 |
CREATE | 'create' | Scene.create() 完成后 |
PRE_UPDATE | 'preupdate' | 每帧更新之前 |
UPDATE | 'update' | 每帧的主更新 |
POST_UPDATE | 'postupdate' | 每帧更新之后 |
PRE_RENDER | 'prerender' | 每帧渲染之前 |
RENDER | 'render' | 每帧渲染期间 |
PAUSE | 'pause' | 场景已暂停 |
RESUME | 'resume' | 场景从暂停中恢复 |
SLEEP | 'sleep' | 场景进入休眠 |
WAKE | 'wake' | 场景从休眠中唤醒 |
SHUTDOWN | 'shutdown' | 场景正在关闭(可能会重启) |
DESTROY | 'destroy' | 场景被永久销毁 |
ADDED_TO_SCENE | 'addedtoscene' | GameObject 被添加到场景 |
REMOVED_FROM_SCENE | 'removedfromscene' | GameObject 从场景中移除 |
TRANSITION_INIT | 'transitioninit' | 转场已初始化(目标场景) |
TRANSITION_START | 'transitionstart' | 转场已开始(目标场景) |
TRANSITION_OUT | 'transitionout' | 转场退出(源场景) |
TRANSITION_COMPLETE | 'transitioncomplete' | 转场已完成 |
TRANSITION_WAKE | 'transitionwake' | 转场唤醒目标场景 |
游戏事件(Phaser.Core.Events)
发射器:this.game.events 或 game.events
| 常量 | 字符串 | 触发时机 |
|---|---|---|
BOOT | 'boot' | 游戏实例完成启动 |
READY | 'ready' | 游戏已准备好开始运行 |
SYSTEM_READY | 'systemready' | 所有全局系统已就绪 |
PRE_STEP | 'prestep' | 游戏循环步进之前 |
STEP | 'step' | 游戏循环主步进 |
POST_STEP | 'poststep' | 游戏循环步进之后 |
PRE_RENDER | 'prerender' | 渲染所有场景之前 |
POST_RENDER | 'postrender' | 渲染所有场景之后 |
PAUSE | 'pause' | 游戏已暂停 |
RESUME | 'resume' | 游戏已恢复 |
BLUR | 'blur' | 浏览器标签页失去焦点 |
FOCUS | 'focus' | 浏览器标签页获得焦点 |
HIDDEN | 'hidden' | Page Visibility API:隐藏 |
VISIBLE | 'visible' | Page Visibility API:可见 |
CONTEXT_LOST | 'contextlost' | WebGL 上下文丢失 |
DESTROY | 'destroy' | 游戏正在被销毁 |
输入事件(Phaser.Input.Events)
发射器:this.input(场景级)或单个 GameObject。事件存在于三个层级:场景级(this.input)、带 gameobject 前缀的场景级,以及直接位于可交互 GameObject 上。完整用法见 ../input-keyboard-mouse-touch/SKILL.md。
场景级指针事件(在 this.input 上):
POINTER_DOWN 'pointerdown' | POINTER_UP 'pointerup' | POINTER_MOVE 'pointermove' | POINTER_OVER 'pointerover' | POINTER_OUT 'pointerout' | POINTER_WHEEL 'wheel' | POINTER_DOWN_OUTSIDE 'pointerdownoutside' | POINTER_UP_OUTSIDE 'pointerupoutside'
场景级 gameobject 事件(在 this.input 上):
GAMEOBJECT_DOWN 'gameobjectdown' | GAMEOBJECT_UP 'gameobjectup' | GAMEOBJECT_MOVE 'gameobjectmove' | GAMEOBJECT_OVER 'gameobjectover' | GAMEOBJECT_OUT 'gameobjectout' | GAMEOBJECT_WHEEL 'gameobjectwheel'
单个 GameObject 事件(在 GameObject 自身上发射,需要 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'
拖拽事件(在 this.input 上以及具有相同字符串的 GameObject 上):
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'
其他: GAME_OUT 'gameout' | GAME_OVER 'gameover' | POINTERLOCK_CHANGE 'pointerlockchange'
加载器事件(Phaser.Loader.Events)
发射器:this.load
| 常量 | 字符串 | 触发时机 |
|---|---|---|
ADD | 'addfile' | 文件被添加到加载队列 |
START | 'start' | 加载器启动 |
PROGRESS | 'progress' | 总体进度更新(0-1) |
FILE_LOAD | 'load' | 单个文件加载完成 |
FILE_PROGRESS | 'fileprogress' | 单个文件进度 |
FILE_COMPLETE | 'filecomplete' | 单个文件处理完成 |
FILE_KEY_COMPLETE | 'filecomplete-' | 特定文件完成(追加 type-key) |
FILE_LOAD_ERROR | 'loaderror' | 文件加载失败 |
POST_PROCESS | 'postprocess' | 所有文件加载完成,进行后处理 |
COMPLETE | 'complete' | 所有加载完成 |
动画事件(Phaser.Animations.Events)
发射器:单个 sprite(每个 sprite)或 this.anims(全局 AnimationManager)
| 常量 | 字符串 | 触发时机 |
|---|---|---|
ADD_ANIMATION | 'add' | 动画被添加到管理器 |
REMOVE_ANIMATION | 'remove' | 动画从管理器移除 |
PAUSE_ALL | 'pauseall' | 所有动画暂停 |
RESUME_ALL | 'resumeall' | 所有动画恢复 |
ANIMATION_START | 'animationstart' | sprite 上的动画开始播放 |
ANIMATION_RESTART | 'animationrestart' | sprite 上的动画重新开始 |
ANIMATION_REPEAT | 'animationrepeat' | sprite 上的动画重复 |
ANIMATION_UPDATE | 'animationupdate' | sprite 上的动画帧变化 |
ANIMATION_COMPLETE | 'animationcomplete' | sprite 上的动画完成 |
ANIMATION_COMPLETE_KEY | 'animationcomplete-' | 特定动画完成(追加 key) |
ANIMATION_STOP | 'animationstop' | sprite 上的动画停止 |
摄像机事件(Phaser.Cameras.Scene2D.Events)
触发器:单个摄像机实例(例如 this.cameras.main)。每个摄像机效果都有一对 START 和 COMPLETE 事件。
DESTROY 'cameradestroy' | FADE_IN_START 'camerafadeinstart' | FADE_IN_COMPLETE 'camerafadeincomplete' | FADE_OUT_START 'camerafadeoutstart' | FADE_OUT_COMPLETE 'camerafadeoutcomplete' | FLASH_START 'cameraflashstart' | FLASH_COMPLETE 'cameraflashcomplete' | PAN_START 'camerapanstart' | PAN_COMPLETE 'camerapancomplete' | ROTATE_START 'camerarotatestart' | ROTATE_COMPLETE 'camerarotatecomplete' | SHAKE_START 'camerashakestart' | SHAKE_COMPLETE 'camerashakecomplete' | ZOOM_START 'camerazoomstart' | ZOOM_COMPLETE 'camerazoomcomplete' | FOLLOW_UPDATE 'followupdate' | PRE_RENDER 'prerender' | POST_RENDER 'postrender'
声音事件(Phaser.Sound.Events)
触发器:单个声音实例或 this.sound(SoundManager)
单个声音实例事件:
PLAY 'play' | PAUSE 'pause' | RESUME 'resume' | STOP 'stop' | COMPLETE 'complete' | LOOP 'loop' | LOOPED 'looped' | SEEK 'seek' | MUTE 'mute' | VOLUME 'volume' | RATE 'rate' | DETUNE 'detune' | PAN 'pan' | DECODED 'decoded' | DESTROY 'destroy'
SoundManager 级别事件(在 this.sound 上):
GLOBAL_MUTE 'mute' | GLOBAL_VOLUME 'volume' | GLOBAL_RATE 'rate' | GLOBAL_DETUNE 'detune' | PAUSE_ALL 'pauseall' | RESUME_ALL 'resumeall' | STOP_ALL 'stopall' | DECODED_ALL 'decodedall' | UNLOCKED 'unlocked'
补间事件(Phaser.Tweens.Events)
触发器:单个补间实例
| 常量 | 字符串 | 触发时机 |
|---|---|---|
TWEEN_ACTIVE | 'active' | 补间变为活动状态 |
TWEEN_START | 'start' | 补间开始首次播放 |
TWEEN_UPDATE | 'update' | 补间更新一个值 |
TWEEN_YOYO | 'yoyo' | 补间往复(反转方向) |
TWEEN_REPEAT | 'repeat' | 补间重复 |
TWEEN_LOOP | 'loop' | 补间循环 |
TWEEN_PAUSE | 'pause' | 补间已暂停 |
TWEEN_RESUME | 'resume' | 补间已恢复 |
TWEEN_COMPLETE | 'complete' | 补间完成 |
TWEEN_STOP | 'stop' | 补间被手动停止 |
Arcade 物理事件(Phaser.Physics.Arcade.Events)
触发器:this.physics.world
| 常量 | 字符串 | 触发时机 |
|---|---|---|
COLLIDE | 'collide' | 两个刚体碰撞 |
OVERLAP | 'overlap' | 两个刚体重叠 |
TILE_COLLIDE | 'tilecollide' | 刚体与瓦片碰撞 |
TILE_OVERLAP | 'tileoverlap' | 刚体与瓦片重叠 |
WORLD_BOUNDS | 'worldbounds' | 刚体触碰世界边界 |
WORLD_STEP | 'worldstep' | 物理世界完成一步 |
PAUSE | 'pause' | 物理世界已暂停 |
RESUME | 'resume' | 物理世界已恢复 |
纹理事件(Phaser.Textures.Events)
触发器:this.textures(TextureManager)
| 常量 | 字符串 | 触发时机 |
|---|---|---|
ADD | 'addtexture' | 添加任意纹理 |
ADD_KEY | 'addtexture-' | 添加特定纹理(追加键名) |
REMOVE | 'removetexture' | 移除任意纹理 |
REMOVE_KEY | 'removetexture-' | 移除特定纹理(追加键名) |
LOAD | 'onload' | 纹理源已加载 |
ERROR | 'onerror' | 纹理源加载错误 |
READY | 'ready' | 纹理管理器就绪 |
GameObject 事件(Phaser.GameObjects.Events)
触发器:单个 GameObject
| 常量 | 字符串 | 触发时机 |
|---|---|---|
ADDED_TO_SCENE | 'addedtoscene' | GameObject 被添加到场景 |
REMOVED_FROM_SCENE | 'removedfromscene' | GameObject 从场景中移除 |
DESTROY | 'destroy' | GameObject 被销毁 |
Video GameObject 事件(在 Video GameObject 上):
VIDEO_PLAY 'play' | VIDEO_PLAYING 'playing' | VIDEO_COMPLETE 'complete' | VIDEO_LOOP 'loop' | VIDEO_STOP 'stop' | VIDEO_CREATED 'created' | VIDEO_ERROR 'error' | VIDEO_LOCKED 'locked' | VIDEO_UNLOCKED 'unlocked' | VIDEO_METADATA 'metadata' | VIDEO_SEEKED 'seeked' | VIDEO_SEEKING 'seeking' | VIDEO_STALLED 'stalled' | VIDEO_TEXTURE 'textureready' | VIDEO_UNSUPPORTED 'unsupported'
时间事件(Phaser.Time.Events)
触发器:Phaser.Time.TimerEvent 实例
| 常量 | 字符串 | 触发时机 |
|---|---|---|
COMPLETE | 'complete' | TimerEvent 完成所有重复 |
事件移除的安全性
传给 off() 的函数引用和上下文/作用域必须与传给 on() 的完全相同。匿名函数或内联箭头函数无法被移除。
// BAD: arrow function cannot be removed later
this.events.on('update', () => { this.doStuff(); });
// GOOD: named method can be removed
this.events.on('update', this.onUpdate, this);
this.events.off('update', this.onUpdate, this);
once() 自动移除
once() 在首次触发后自动移除监听器。无需手动清理。
this.events.once(Phaser.Scenes.Events.CREATE, this.onFirstCreate, this);
实用方法
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
创建独立的 EventEmitter
const bus = new Phaser.Events.EventEmitter();
bus.on('inventory-changed', (items) => { console.log(items.length); });
bus.emit('inventory-changed', this.inventory);
场景事件 vs 游戏事件
this.events-- 场景专属。触发场景生命周期事件。场景被销毁时清理。this.game.events-- 全局。触发游戏级别事件(blur、focus、pause、resume)。跨场景重启持续存在 -- 在 SHUTDOWN 时清理。
create() {
this.events.on(Phaser.Scenes.Events.UPDATE, this.onUpdate, this);
this.game.events.on(Phaser.Core.Events.BLUR, this.onBlur, this);
this.events.once(Phaser.Scenes.Events.SHUTDOWN, () => {
this.game.events.off(Phaser.Core.Events.BLUR, this.onBlur, this);
});
}
常见陷阱
未移除监听器导致的内存泄漏
最常见的 bug 来源。如果场景使用了 on(),而场景通过 scene.restart() 重启,旧监听器会一直存在,因为 on() 不会自动移除。每次重启都会增加重复的监听器。
// BAD: leaks listeners on every scene restart
create() {
this.input.on('pointerdown', this.shoot, this);
}
// GOOD: clean up in shutdown
create() {
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 once
create() {
this.events.once(Phaser.Scenes.Events.CREATE, this.onFirstCreate, this);
}
shutdown vs destroy
SHUTDOWN在场景停止但稍后可能重启时触发。在此处清理监听器。DESTROY在场景被永久移除时触发。用于最终清理。- 场景重启会依次触发
SHUTDOWN、START、CREATE。它不会触发DESTROY。
上下文绑定
on/once 的第三个参数用于设置回调内部的 this。如果不传,this 默认为 emitter,而不是场景。使用普通函数时将 this 作为第三个参数传入,或使用箭头函数(箭头函数捕获词法 this)。
off() 需要精确的引用
off() 只有在传入与 on() 完全相同的函数引用(和上下文)时才能生效。匿名函数或箭头函数字面量无法被移除 -- 请保存引用或使用类方法。
输入事件层级
输入事件按顺序触发:(1) GameObject 上的 GAMEOBJECT_POINTER_DOWN,(2) this.input 上的 GAMEOBJECT_DOWN,(3) this.input 上的 POINTER_DOWN。更高层的处理器可以阻止传播。
游戏事件 vs 场景事件
this.game.events 和 this.events 是不同的 emitter。游戏事件在每次游戏循环 tick 中跨所有场景触发一次。场景事件按场景触发。game.events 上的监听器会在场景重启后持续存在 -- 务必在 SHUTDOWN 时清理:
create() {
this.game.events.on(Phaser.Core.Events.BLUR, this.onBlur, this);
this.events.once(Phaser.Scenes.Events.SHUTDOWN, () => {
this.game.events.off(Phaser.Core.Events.BLUR, this.onBlur, this);
});
}
Source File Map
| Path | Description |
|---|---|
src/events/EventEmitter.js | Base EventEmitter class (wraps eventemitter3) |
src/scene/events/ | Scene lifecycle events (22 events) |
src/core/events/ | Game-level events (16 events) |
src/input/events/ | Input/pointer/drag events (48 events) |
src/loader/events/ | Asset loading events (10 events) |
src/animations/events/ | Animation playback events (11 events) |
src/cameras/2d/events/ | Camera effect events (18 events) |
src/sound/events/ | Sound playback events (24 events) |
src/tweens/events/ | Tween lifecycle events (10 events) |
src/physics/arcade/events/ | Arcade physics events (8 events) |
src/textures/events/ | Texture manager events (7 events) |
src/gameobjects/events/ | GameObject lifecycle + Video events (18 events) |
src/time/events/ | TimerEvent events (1 event) |