data-manager について
GameObject、Scene、ゲーム全体の registry で使う、変更イベント付きキー・バリューストア DataManager を解説する Phaser 開発スキルです。ゲーム状態の共有と、変更に応じた処理の実装に役立ちます。
主な機能
- 01キー・バリュー形式のデータ保存と取得
- 02全体およびキー別イベントによる変更追跡
- 03値の加算、切り替え、マージ、削除
- 04エントリーの検索と反復処理
- 05変更の凍結とデータのリセット
活用シーン
- 01スコア変更に応じた HUD 更新
- 02registry を介したシーン間の状態共有
- 03オブジェクトごとの体力や所持品の管理
- 04保存済みゲーム状態の読み込みやマージ
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ phaserjs/ phaser --skill data-managerインストール後、Claude Code で/data-managerを入力して読み込まれたか確認します。
詳細
DataManager
Phaser の DataManager は、イベント駆動の変更追跡を伴うキーと値のストレージを提供します。これは 3 つのレベルで動作します: 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)-- 1 回の呼び出しでキーを取得して削除します。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 で 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);
});
このプラグインは、シーンのシャットダウン時に自動クリーンアップされ(自身のシャットダウンリスナーを削除)、シーンの破棄時に完全に破棄されます。
Registry(グローバルデータストア)
registry は Game オブジェクト上のプレーンな DataManager インスタンスです(game.registry)。専用の EventEmitter を持ちます(どのシーンとも共有されません)。すべてのシーンは、インジェクションマップを介して 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 のライフタイムにわたって持続します。シーンの再起動やシャットダウンで自動的にクリアされることはありません。
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);
});
クロスシーン状態のためのグローバル 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.sys.events、レジストリの場合は 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 引数は DataManager の所有者(GameObject、Scene、または Game インスタンス)です。
注意: CHANGE_DATA_KEY はプレフィックスです。実際のイベント文字列は 'changedata-' + キー名になります。たとえば、score というキーを設定すると 'changedata-score' が発火します。
API クイックリファレンス
GameObject の便利メソッド
| メソッド | 戻り値 | 説明 |
|---|---|---|
setDataEnabled() | this | DataManager を明示的に作成します(通常は自動的に作成されます) |
setData(key, value) | this | 1 つのキーを設定するか、複数の場合はオブジェクトを渡します |
getData(key) | * | 1 つの値を取得するか、複数の場合は配列を渡します |
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) | * | 1 回の呼び出しで取得と削除を行います |
has(key) | boolean | キーが存在するかどうかを確認します |
inc(key, amount?) | this | インクリメントします(デフォルトは +1) |
toggle(key) | this | 真偽値をトグルします |
setFreeze(bool) | this | 変更を凍結/凍結解除します |
reset() | this | すべてのデータをクリアし、凍結を解除します |
count | number | 読み取り専用のエントリ数 |
freeze | boolean | 凍結状態を取得/設定します |
values | object | プロパティに直接アクセスするためのプロキシオブジェクト |
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 の所有者 | イベントの発火先 | リスナーの例 |
|---|---|---|
| GameObject | GameObject 自体 | sprite.on('changedata-hp', ...) |
Scene (this.data) | シーンのイベントバス (this.events) | this.events.on('changedata-score', ...) |
Registry (this.registry) | Registry 自身のエミッター | this.registry.events.on('changedata-score', ...) |
キー固有のイベント 'changedata-{key}' はその正確なキーに対してのみ発火し、汎用の 'changedata' はすべてのキーの変更に対して発火します。パフォーマンスのためには、常にキー固有のリスナーを優先してください。
注意点
キーは大文字と小文字を区別します
'gold' と 'Gold' は 2 つの異なるキーです。命名規則を一貫させてください。
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() 呼び出しの途中でフリーズすると、バッチ内の後続の呼び出しはサイレントに no-op になります。フリーズはすべての書き込みが完了した後にのみ行ってください。
getAll() はスナップショットを返す
getAll() は毎回新しいプレーンオブジェクトを返します。これはシャローコピーです -- プリミティブ値は独立していますが、オブジェクト/配列の値は DataManager の内部ストレージと参照を共有したままです。
changedata-key コールバックの最初の引数
汎用の changedata イベントでは、コールバックは (parent, key, value, previousValue) を受け取ります。キー固有の changedata-{key} イベントでは、key 引数は省略されます: (parent, value, previousValue)。この違いは見落としがちです。
ソースファイルマップ
| Path | Description |
|---|---|
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.js | setData、getData、incData、toggleData、setDataEnabled のコンビニエンスメソッド |
src/core/Game.js | game.registry を作成します -- グローバルな DataManager インスタンス |
src/scene/Scene.js | this.registry(Game から注入)と this.data(DataManagerPlugin)を公開します |