mcpField
E
events-system

events-system

github.com/phaserjs开发40.6k799 次安装10 周前更新
代码仓库

关于 events-system

面向 Phaser 开发者的事件系统技能指南,介绍框架各系统的事件监听、触发与清理方式,并列出常用事件命名空间。

主要功能

  1. 01监听与触发事件
  2. 02使用具名事件常量
  3. 03设置回调上下文
  4. 04管理场景生命周期监听
  5. 05查阅各系统事件目录

适用场景

  1. 01响应指针输入与拖放
  2. 02处理场景启动和关闭
  3. 03在场景之间传递事件
  4. 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

PathDescription
src/events/EventEmitter.jsBase 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)

评论

支持 Markdown