mcpField
D
data-manager

data-manager

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

关于 data-manager

介绍 Phaser DataManager 的开发技能,涵盖 GameObject、Scene 和全局 registry 层级的键值存储及变更事件。帮助开发者共享游戏状态,并让系统响应数据变化。

主要功能

  1. 01存储和读取键值数据
  2. 02通过通用及键专属事件追踪变更
  3. 03递增、切换、合并和删除值
  4. 04查询并遍历条目
  5. 05冻结修改或重置数据

适用场景

  1. 01分数变化时更新 HUD
  2. 02通过 registry 在场景间共享状态
  3. 03追踪对象的生命值或物品数据
  4. 04载入或合并已保存的游戏状态

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/phaserjs/phaser --skill data-manager

安装后在 Claude Code 中输入/data-manager确认已加载。

详细介绍

DataManager

Phaser 的 DataManager 提供键值存储,并支持事件驱动的变更跟踪。它在三个层级上运作:每个 GameObject(sprite.setData/getData)、每个 Scene(this.data)以及全局(this.registry)。每次 set/change/remove 操作都会发出事件,从而实现游戏系统之间的响应式数据绑定,而无需紧密耦合。

关键源码路径: src/data/DataManager.js、src/data/DataManagerPlugin.js、src/data/events/、src/gameobjects/GameObject.js(setData/getData/incData/toggleData) 相关技能: ../scenes/SKILL.md、../events-system/SKILL.md

快速开始

// Per-GameObject data (auto-creates DataManager on first use)
const gem = this.add.sprite(100, 100, 'gem');
gem.setData('value', 50);
gem.setData({ color: 'red', level: 2 });
gem.getData('value');            // 50
gem.getData(['value', 'color']); // [50, 'red']

// Increment / toggle helpers
gem.incData('value', 10);       // value is now 60
gem.incData('value', -5);       // value is now 55 (negative to decrement)
gem.toggleData('active');        // false -> true (starts from false if unset)

// Scene-level data (this.data is a DataManagerPlugin)
this.data.set('score', 0);
this.data.get('score');          // 0
this.data.values.score += 100;   // triggers changedata event

// Global registry (shared across ALL scenes)
this.registry.set('highScore', 9999);
// Any scene can read it:
this.registry.get('highScore');  // 9999

核心概念

DataManager(Phaser.Data.DataManager)

基类,将键值对存储在内部的 list 对象中。它提供:

  • set(key, value) —— 存储一个值;发出 setdata(新键)或 changedata + changedata-{key}(已存在的键)。可接受一个对象以一次设置多个键。
  • get(key) —— 获取一个值,或传入一个键数组以获取值数组。
  • inc(key, amount) —— 递增一个数值(默认为 +1)。如果键不存在,则从 0 开始创建。
  • toggle(key) —— 翻转一个布尔值。如果键不存在,则从 false 开始创建。
  • remove(key) —— 删除一个键;发出 removedata。可接受一个键数组。
  • has(key) —— 如果键存在则返回 true。
  • getAll() —— 以普通对象形式返回所有键值对的浅拷贝。
  • query(regex) —— 返回所有键匹配给定 RegExp 的条目。
  • each(callback, context, ...args) —— 遍历所有条目。回调签名:(parent, key, value, ...args)。
  • merge(data, overwrite) —— 从一个对象批量导入。overwrite 默认为 true;设为 false 可跳过已存在的键。
  • pop(key) —— 在一次调用中获取并删除一个键;发出 removedata。
  • reset() —— 清除所有数据并解冻。
  • freeze / setFreeze(bool) —— 冻结时,所有 set/remove/inc/toggle 操作都会静默地无操作。
  • count —— 只读属性,返回已存储条目的数量。

values 代理对象允许直接进行属性访问并发出事件:

// After set('gold', 100), you can do:
data.values.gold += 50; // emits changedata and changedata-gold
// But you MUST use set() to create a key first -- direct assignment
// to values for a new key will NOT set up the event proxy.
Scene 数据插件(Phaser.Data.DataManagerPlugin)

继承自 DataManager。注册为 data Scene 插件,可在任何 Scene 中通过 this.data 访问。它使用 Scene 的事件发射器(scene.sys.events),因此数据事件会在 Scene 的事件总线上触发。

// In a Scene's create():
this.data.set('lives', 3);

// Listen on the scene's event emitter
this.events.on('changedata-lives', (scene, value, previousValue) => {
    console.log('Lives changed from', previousValue, 'to', value);
});

该插件在 Scene 关闭时自动清理(移除其 shutdown 监听器),并在 Scene 销毁时完全销毁。

Registry(全局数据存储)

registry 是 Game 对象上的一个普通 DataManager 实例(game.registry)。它有自己的专用 EventEmitter(不与任何 Scene 共享)。每个 Scene 都会通过注入映射获得一个引用,即 this.registry。

// Scene A sets global data
this.registry.set('currentLevel', 1);

// Scene B reads it
const level = this.registry.get('currentLevel');

// Listen for registry changes (note: events fire on registry.events, NOT this.events)
this.registry.events.on('changedata-currentLevel', (game, value, previousValue) => {
    console.log('Level changed to', value);
});

registry 在 Game 的整个生命周期内持续存在。它绝不会在 Scene 重启或关闭时被自动清除。

每个 GameObject 的数据

GameObject 默认没有 DataManager。它会在首次调用 setData()、getData()、incData() 或 toggleData() 时惰性创建。你也可以显式调用 setDataEnabled()。

DataManager 的事件发射器就是 GameObject 本身(它继承自 EventEmitter),因此数据事件会直接在 GameObject 上触发:

const player = this.add.sprite(0, 0, 'player');
player.setData('hp', 100);

// Listen directly on the game object
player.on('changedata-hp', (gameObject, value, previousValue) => {
    if (value <= 0) {
        gameObject.destroy();
    }
});

常见模式

设置和获取数据
// Single key
sprite.setData('speed', 200);
sprite.getData('speed'); // 200

// Multiple keys at once (object form)
sprite.setData({ speed: 200, direction: 'left', hp: 100 });

// Batch get with destructuring
const [speed, hp] = sprite.getData(['speed', 'hp']);

// Direct values access (read and write after initial set)
sprite.data.values.speed = 300; // emits changedata event
const s = sprite.data.values.speed; // 300
监听变更
// Listen for ANY data change on a game object
sprite.on('changedata', (gameObject, key, value, previousValue) => {
    console.log(key, 'changed to', value);
});

// Listen for a SPECIFIC key change (preferred -- more efficient)
sprite.on('changedata-hp', (gameObject, value, previousValue) => {
    this.hpBar.setValue(value);
});

// Listen for new data being set (first time only)
sprite.on('setdata', (gameObject, key, value) => {
    console.log('New data key:', key);
});

// Listen for data removal
sprite.on('removedata', (gameObject, key, value) => {
    console.log('Removed:', key, 'was', value);
});
用于跨 Scene 状态的全局 Registry
// HUD scene watches for score changes set by the Game scene
// In HUDScene.create():
this.registry.events.on('changedata-score', (game, value) => {
    this.scoreText.setText('Score: ' + value);
});
this.events.once('shutdown', () => {
    this.registry.events.off('changedata-score');
});

// In GameScene.update():
this.registry.set('score', this.score);
复杂数据和对象
// You can store any value type: numbers, strings, booleans, objects, arrays
sprite.setData('inventory', ['sword', 'shield']);
sprite.setData('stats', { str: 10, dex: 8, int: 12 });

// CAUTION: mutating a stored object/array does NOT trigger changedata
const inv = sprite.getData('inventory');
inv.push('potion');
// No event fired! The reference didn't change.

// To trigger the event, re-set the key:
sprite.setData('inventory', [...inv]); // new array reference triggers changedata
合并数据
// Merge defaults -- only sets keys that don't already exist
sprite.data.merge({ hp: 100, speed: 200, armor: 0 }, false);

// Merge and overwrite existing values
sprite.data.merge(savedState, true);
按模式查询数据
// Find all keys matching a regex
this.data.set('enemy_1_hp', 50);
this.data.set('enemy_2_hp', 80);
this.data.set('player_hp', 100);

const enemyData = this.data.query(/^enemy_/);
// { enemy_1_hp: 50, enemy_2_hp: 80 }
冻结数据
// Prevent all modifications (set, remove, inc, toggle all become no-ops)
sprite.data.freeze = true;
sprite.setData('hp', 0); // silently ignored
sprite.data.freeze = false;
sprite.setData('hp', 0); // works again

// Also available as a chainable method
sprite.data.setFreeze(true);
数据持久化模式
// Save to localStorage
function saveGame(scene) {
    const state = scene.data.getAll();
    localStorage.setItem('saveData', JSON.stringify(state));
}

// Restore from localStorage
function loadGame(scene) {
    const raw = localStorage.getItem('saveData');
    if (raw) {
        scene.data.merge(JSON.parse(raw), true);
    }
}

事件

所有数据事件都定义在 Phaser.Data.Events 中。事件发射器取决于上下文:对于 GameObject,它就是 GameObject 本身;对于 Scene 数据,它是 scene.sys.events;对于 registry,它是 registry.events。

常量字符串触发时机回调参数
SET_DATA'setdata'创建了新键(parent, key, value)
CHANGE_DATA'changedata'已有键的值发生变化(parent, key, value, previousValue)
CHANGE_DATA_KEY'changedata-'特定键发生变化(追加键名)(parent, value, previousValue)
REMOVE_DATA'removedata'移除了某个键(parent, key, value)
DESTROY'destroy'DataManager 的 parent 被销毁(无)

parent 参数是 DataManager 的所有者(即 GameObject、Scene 或 Game 实例)。

注意:CHANGE_DATA_KEY 是一个前缀。实际的事件字符串是 'changedata-' 加上键名。例如,设置一个名为 score 的键会触发 'changedata-score'。

API 快速参考

GameObject 便捷方法
方法返回值描述
setDataEnabled()this显式创建 DataManager(通常会自动创建)
setData(key, value)this设置单个键,或传入对象以设置多个
getData(key)*获取单个值,或传入数组以获取多个
incData(key, amount?)this递增数值(默认 +1,传负值可递减)
toggleData(key)this切换布尔值
DataManager 方法
方法返回值描述
set(key, value)this设置单个键,或传入键值对对象
get(key)*获取值——传入字符串或字符串数组
getAll()object所有条目的浅拷贝
query(regex)object键匹配该 RegExp 的所有条目
each(cb, ctx, ...args)this遍历所有条目
merge(data, overwrite?)this批量导入;overwrite 默认为 true
remove(key)this删除键——传入字符串或数组
pop(key)*在一次调用中获取并删除
has(key)boolean检查键是否存在
inc(key, amount?)this递增(默认 +1)
toggle(key)this切换布尔值
setFreeze(bool)this冻结/解冻修改
reset()this清除所有数据并解冻
countnumber只读的条目数量
freezeboolean获取/设置冻结状态
valuesobject用于直接属性访问的代理对象

get() 与 values——副本与实时引用

理解 get() 和 values 之间的区别对于正确更新数据至关重要:

// get() returns a copy for primitives -- modifying the local variable does NOT update the DataManager
let score = this.data.get('score');
score += 10;  // local copy only, DataManager still has the old value

// values provides a live proxy -- assignment triggers CHANGE_DATA event
this.data.values.score += 10;  // updates the DataManager AND emits changedata-score

// For objects, get() returns the stored reference (not a deep copy)
const stats = this.data.get('stats');
stats.hp -= 10; // mutates the stored object but does NOT trigger events
this.data.set('stats', { ...stats }); // re-set with new reference to trigger event

事件发射器路由

数据事件会根据 DataManager 所在的位置发射到不同的目标:

DataManager 所有者事件发射目标监听器示例
GameObjectGameObject 本身sprite.on('changedata-hp', ...)
Scene(this.data)Scene 事件总线(this.events)this.events.on('changedata-score', ...)
Registry(this.registry)Registry 自己的发射器this.registry.events.on('changedata-score', ...)

特定键事件 'changedata-{key}' 仅在该确切键发生变化时触发,而通用事件 'changedata' 会在每次键变化时触发。出于性能考虑,应始终优先使用特定键监听器。

注意事项

键区分大小写

'gold' 和 'Gold' 是两个不同的键。请保持命名约定的一致性。

values 代理需要先调用 set()

在通过 data.values.key 修改某个键之前,必须先调用 set(key, value) 来创建该键。对一个全新的键直接给 values 赋值不会创建事件代理——它只会创建一个普通属性,不发射任何事件。

对象/数组的可变修改不会触发事件

就地修改已存储的对象或数组不会触发 changedata,因为引用没有发生变化。你必须用一个新引用重新设置该键才能触发事件。

注册表监听器在场景重启后仍然存在

注册表存在于 Game 对象上。添加到 this.registry.events 的监听器在场景重启时不会被清理。务必在 SHUTDOWN 时移除它们:

create() {
    const handler = (game, value) => { this.updateHUD(value); };
    this.registry.events.on('changedata-score', handler);
    this.events.once('shutdown', () => {
        this.registry.events.off('changedata-score', handler);
    });
}
冻结的 DataManager 会静默失败

当 freeze 为 true 时,所有写操作(set、remove、inc、toggle、pop、merge)都会静默地什么都不做。不会抛出错误,也不会触发事件。如果你忘记了自己冻结了数据,这可能会令人困惑。

场景数据插件:Shutdown 与 Destroy 的区别

DataManagerPlugin 在 shutdown 时会移除其 shutdown 监听器,但不会在 shutdown 时清除数据。如果场景重启,数据会继续保留。只有在场景 destroy 时才会完全重置。如果你需要在重启时获得干净的状态,请在 shutdown 监听器中手动重置:

this.events.once('shutdown', () => {
    this.data.reset();
});
对非数值使用 inc()

inc() 内部使用 + 运算符。对字符串它会进行拼接,而不是数值相加。对布尔值,它们会在递增前被强制转换为 0 或 1。只对确定存储数值的键使用 inc()。

reset() 不触发任何事件

调用 reset() 会静默清除所有数据——不会为被删除的键触发 removedata 事件。如果你需要为每个键触发移除事件,请改为逐个遍历并调用 remove()。

冻结可能中断批量操作

setFreeze(true) 会立即生效。如果你在一系列 set() 调用的中途冻结,该批次中后续的调用会静默地不执行任何操作。务必在所有写入完成后再冻结。

getAll() 返回的是快照

getAll() 每次都会返回一个新的普通对象。它是浅拷贝——原始值是独立的,但对象/数组值仍然与 DataManager 的内部存储共享引用。

changedata-key 回调中的第一个参数

对于通用的 changedata 事件,回调接收 (parent, key, value, previousValue)。对于键特定的 changedata-{key} 事件,key 参数会被省略:(parent, value, previousValue)。这个差异很容易被忽略。

源文件映射

路径描述
src/data/DataManager.js核心 DataManager 类——set、get、each、merge、remove、query、freeze、事件
src/data/DataManagerPlugin.js扩展 DataManager 的场景插件;注册为 this.data
src/data/events/index.js事件常量导出(CHANGE_DATA、SET_DATA、REMOVE_DATA、CHANGE_DATA_KEY、DESTROY)
src/data/events/SET_DATA_EVENT.js'setdata'——在创建新键时触发
src/data/events/CHANGE_DATA_EVENT.js'changedata'——在现有键的值发生变化时触发
src/data/events/CHANGE_DATA_KEY_EVENT.js'changedata-'——按键变更事件前缀
src/data/events/REMOVE_DATA_EVENT.js'removedata'——在键被移除时触发
src/data/events/DESTROY_EVENT.js'destroy'——DataManager 从其父级监听此事件
src/gameobjects/GameObject.jssetData、getData、incData、toggleData、setDataEnabled 便捷方法
src/core/Game.js创建 game.registry——全局 DataManager 实例
src/scene/Scene.js暴露 this.registry(从 Game 注入)和 this.data(DataManagerPlugin)

评论

支持 Markdown