groups-and-containers について
Phaser 4 開発者向けのスキルガイドです。Group、Container、Layer の違いと使い分けを、オブジェクトプール、変換の継承、描画順を含めて解説します。
主な機能
- 01Group、Container、Layer の比較
- 02Group によるオブジェクトプール
- 03Container による変換の継承
- 04Layer による描画順の制御
- 05API リファレンスとコード例
活用シーン
- 01弾などのオブジェクトの再利用
- 02HUD 要素をまとめて移動
- 03オブジェクト群の描画順を設定
- 04ゲームオブジェクトのグループ化方式を選択
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ phaserjs/ phaser --skill groups-and-containersインストール後、Claude Code で/groups-and-containersを入力して読み込まれたか確認します。
詳細
グループとコンテナ
論理的なグループ化 (Group)、トランスフォーム継承を伴う視覚的なグループ化 (Container)、レンダーレイヤーのグループ化 (Layer)、オブジェクトプーリング、そして Phaser 4 でそれぞれをいつ使うべきか。
主要なソースパス: src/gameobjects/group/、src/gameobjects/container/、src/gameobjects/layer/
関連スキル: ../sprites-and-images/SKILL.md、../physics-arcade/SKILL.md
クイックスタート
// In a Scene's create() method:
// --- Group: logical collection, no transform, great for pooling ---
const enemies = this.add.group();
enemies.create(100, 200, 'enemy'); // creates Sprite at (100,200)
enemies.create(300, 200, 'enemy');
// --- Container: visual parent with inherited transform ---
const hud = this.add.container(10, 10);
const icon = this.add.image(0, 0, 'heart');
const label = this.add.text(20, 0, 'x3');
hud.add([icon, label]); // children move/scale/rotate with hud
// --- Layer: render-ordering bucket, no position/scale ---
const bgLayer = this.add.layer();
const fgLayer = this.add.layer();
bgLayer.add(this.add.image(400, 300, 'sky'));
fgLayer.add(this.add.sprite(400, 300, 'player'));
中核となる概念
Group vs Container vs Layer
| 機能 | Group | Container | Layer |
|---|---|---|---|
| 目的 | 論理的なコレクション / プール | トランスフォームを持つ視覚的な親 | レンダー順のバケット |
| ディスプレイリスト上 | いいえ (子が載る) | はい (子をレンダリング) | はい (子をレンダリング) |
| 位置/回転/スケール | いいえ | はい (子が継承) | いいえ |
| 子の格納 | children (Set) | list (Array) | List (Structs.List) |
| 物理演算 | physics.add.group() 経由 | 限定的 (0,0 以外にある場合はオフセット) | いいえ |
| 入力 | いいえ (子は可能) | はい (ヒットエリア形状が必要) | いいえ |
| オブジェクトプーリング | はい (getFirstDead, kill) | いいえ | いいえ |
| マスク | いいえ | はい (Canvas では子ごとではない) | はい |
| アルファ/ブレンド/可視性 | いいえ (setVisible による一括設定) | はい | はい |
| ネスト | 該当なし | Container 内の Container | Container には入れられない |
| 継承元 | EventEmitter | GameObject | List |
| ファクトリ | this.add.group() | this.add.container(x, y) | this.add.layer() |
それぞれをいつ使うか
Group: 類似オブジェクト (敵、弾、コイン) のコレクションの管理、active/inactive ライフサイクルによるオブジェクトプーリング、物理グループの衝突。共有される視覚的トランスフォームはありません。メンバーは同時に複数の Group に所属できます。
Container: 子は位置、回転、スケール、アルファを継承します。複合 UI 要素 (ヘルスバー、インベントリスロット)、クラスタを 1 つの単位として移動・回転、ネストされたトランスフォーム。デフォルトでは排他的です -- 子は 1 つの Container にしか所属できません (上書きするには setExclusive(false) を使用します)。
Layer: オブジェクトのバッチのレンダー順の制御、共有のアルファ/ブレンド/マスクの適用。位置/スケール/回転はありません。軽量なレンダーバケッティングです。
ひと目でわかる Container と Group
- Container には位置、回転、スケール、アルファがあります -- Group にはありません。子を 1 つの単位として移動・回転させる必要がある場合は、Container を使用します。
- Container はデフォルトで排他的です -- 子を追加すると、以前の Container から削除されます。Group は非排他的です。1 つのゲームオブジェクトを多数の Group に所属させられます。
- Container はディスプレイリスト上にあります -- 子をレンダリングします。Group はディスプレイリスト上にありません。その子は個別に Scene 上でレンダリングされます。
- Group はオブジェクトプーリングをサポートします -- getFirstDead、kill、killAndHide。Container はサポートしません。
- Container にはパフォーマンスコストがあります -- 各子はフレームごとに行列演算を必要とします。ネストが深いほどコストが増えます。トランスフォームが不要な場合は Group または Layer を優先してください。
一般的なパターン
Group の作成と追加
// Empty group, add existing objects
const gems = this.add.group();
gems.add(existingSprite);
gems.addMultiple([sprite1, sprite2, sprite3]);
// Group with config -- creates children automatically
const coins = this.add.group({
classType: Phaser.GameObjects.Sprite,
key: 'coin',
quantity: 10, // overrides frameQuantity
setXY: { x: 50, y: 300, stepX: 60 },
setScale: { x: 0.5, y: 0.5 }
});
// Custom class type with pool limit
const bullets = this.add.group({
classType: Bullet, // must accept (scene, x, y, key, frame)
maxSize: 30,
defaultKey: 'bullet',
runChildUpdate: true // calls child.update() each frame
});
getFirstDead によるオブジェクトプーリング
中核となるプーリングパターン: オブジェクトを破棄するのではなく非アクティブ化し、その後で非アクティブなものを再利用します。
// Setup pool
const bullets = this.add.group({
classType: Phaser.GameObjects.Sprite,
defaultKey: 'bullet',
maxSize: 30
});
// Fire a bullet -- get() finds first inactive member or creates one
function fireBullet(x, y) {
const bullet = bullets.get(x, y);
if (bullet) {
bullet.setActive(true);
bullet.setVisible(true);
bullet.body.velocity.y = -300; // if physics enabled
}
}
// Deactivate when off-screen or on hit
function killBullet(bullet) {
bullets.killAndHide(bullet); // sets active=false, visible=false
// If using physics, also reset the body:
// bullet.body.stop();
}
// Alternative: manual getFirst
const inactive = bullets.getFirst(false); // first where active===false
const active = bullets.getFirstAlive(); // first where active===true
const dead = bullets.getFirstDead(true, x, y); // first inactive, create if null
Group のプール用ヘルパーメソッド:
| メソッド | 説明 |
|---|---|
get(x, y, key, frame) | ショートカット: getFirst(false, true, ...) -- 非アクティブなものを探すか、作成します |
getFirst(state, createIfNull, x, y, key, frame) | active な state に一致する最初のメンバー |
getFirstAlive(createIfNull, x, y, key, frame) | active===true である最初のメンバー |
getFirstDead(createIfNull, x, y, key, frame) | active===false である最初のメンバー |
getLast(state, createIfNull, x, y, key, frame) | getFirst と同様ですが、後ろから前へ検索します |
kill(gameObject) | メンバーの active=false を設定します |
killAndHide(gameObject) | active=false と visible=false を設定します |
countActive(value) | active===value であるメンバーを数えます (デフォルトは true) |
getTotalUsed() | アクティブなメンバーの数 |
getTotalFree() | maxSize - アクティブ数 (プールの残り容量) |
isFull() | children.size >= maxSize の場合に true |
物理グループ
物理グループは、自動的なボディ割り当てでグループを拡張します。詳細は ../physics-arcade/SKILL.md を参照してください。
// Arcade Physics group -- every member gets a dynamic body
const enemies = this.physics.add.group({
key: 'enemy',
quantity: 5,
setXY: { x: 100, y: 100, stepX: 80 }
});
// Static physics group -- immovable bodies
const platforms = this.physics.add.staticGroup();
platforms.create(400, 568, 'ground');
// Collide physics groups with each other
this.physics.add.collider(player, platforms);
this.physics.add.overlap(bullets, enemies, onHit);
ネストされたトランスフォームを持つコンテナ
// HUD that follows camera
const hud = this.add.container(10, 10);
hud.setScrollFactor(0); // pinned to camera
const healthBar = this.add.rectangle(0, 0, 200, 20, 0x00ff00);
const healthText = this.add.text(210, -5, '100 HP');
hud.add([healthBar, healthText]);
// Move everything at once
hud.setPosition(50, 50);
// Scale and rotate propagate to children
hud.setScale(1.5);
hud.setRotation(0.1);
// Alpha affects all children
hud.setAlpha(0.8);
// Nested containers
const inventory = this.add.container(300, 500);
for (let i = 0; i < 5; i++) {
const slot = this.add.container(i * 55, 0);
slot.add([
this.add.rectangle(0, 0, 48, 48, 0x333333),
this.add.image(0, 0, `item-${i}`)
]);
inventory.add(slot); // Container inside Container
}
主要な Container メソッド:
| メソッド | 説明 |
|---|---|
add(child) / addAt(child, index) | ゲームオブジェクトを追加します。ディスプレイリストから削除されます |
remove(child, destroyChild) | 削除します。任意で破棄も可能です |
getAt(index) / getIndex(child) | インデックスでアクセスします |
getByName(name) / getFirst(prop, val) | 子をクエリします |
getAll(prop, val) / count(prop, val) | フィルタリングされたアクセスとカウント |
sort(property) / swap(a, b) / moveTo(child, idx) | 並べ替え |
each(cb, ctx) / iterate(cb, ctx) | イテレーション(iterate はインデックスを渡します) |
setScrollFactor(x, y, updateChildren) | true を渡すと子にも適用されます |
getBounds(output) | すべての子の境界矩形 |
pointToContainer(source, output) | ワールド座標からローカル空間へ変換 |
setExclusive(value) | false の場合、子は複数の場所に存在できます |
replace(oldChild, newChild) | ある子を別の子と入れ替えます |
setSize(width, height) | ヒットエリアのサイズを設定します(入力に必要) |
length | 読み取り専用の子の数 |
レンダリング順序のためのレイヤー
const bgLayer = this.add.layer();
const entityLayer = this.add.layer();
const uiLayer = this.add.layer();
// Add objects -- they render in layer order, then by depth within layer
bgLayer.add(this.add.image(400, 300, 'sky'));
entityLayer.add(player);
entityLayer.add(enemy);
uiLayer.add(scoreText);
// Control depth of layers themselves
bgLayer.setDepth(0);
entityLayer.setDepth(1);
uiLayer.setDepth(2);
// Children set depth within their layer
enemy.setDepth(5); // relative to entityLayer, not the Scene
// Apply shared effects to entire layer
entityLayer.setAlpha(0.5);
entityLayer.setVisible(false);
entityLayer.setBlendMode(Phaser.BlendModes.ADD);
createMultiple による一括作成
// createMultiple accepts a GroupCreateConfig or array of them
const coins = this.add.group();
coins.createMultiple({
key: 'coin',
quantity: 20,
setXY: { x: 50, y: 100, stepX: 40, stepY: 0 },
setScale: { x: 0.5, y: 0.5 },
setRotation: { value: 0, step: 0.1 }, // each rotated 0.1 more than previous
setAlpha: { value: 1 },
setOrigin: { x: 0.5, y: 0.5 },
setDepth: { value: 5 },
gridAlign: {
width: 5,
height: 4,
cellWidth: 48,
cellHeight: 48,
x: 100,
y: 200
}
});
// Multiple configs at once (creates two different sets)
coins.createMultiple([
{ key: 'gold-coin', quantity: 10, setXY: { x: 50, y: 100, stepX: 30 } },
{ key: 'silver-coin', quantity: 10, setXY: { x: 50, y: 200, stepX: 30 } }
]);
グループの反復処理とバッチ操作
const enemies = this.add.group();
// Get all children as array
const all = enemies.getChildren();
const active = enemies.getMatching('active', true);
// Batch property operations
enemies.setXY(200, 300);
enemies.incX(5); // add 5 to each member's x
enemies.setVisible(false);
enemies.propertyValueSet('tintTopLeft', 0xff0000);
enemies.playAnimation('walk');
// Stepping: apply incremental values across members
enemies.setX(100, 50); // first at x=100, next at 150, then 200...
enemies.setY(200, 30); // first at y=200, next at 230, then 260...
enemies.setXY(100, 200, 50, 30); // combined X and Y stepping
enemies.incXY(10, 5); // add 10 to each x, 5 to each y
enemies.angle(0, 15); // first at 0 deg, next at 15, then 30...
enemies.setAlpha(1, -0.1); // first at 1.0, next at 0.9, then 0.8...
enemies.setScale(1, 0, 0.1, 0); // scaleX: 1.0, 1.1, 1.2... (stepX=0.1)
enemies.setDepth(0, 1); // depth 0, 1, 2, 3...
enemies.setOrigin(0.5);
enemies.setBlendMode(Phaser.BlendModes.ADD);
enemies.setTint(0xff0000);
enemies.shuffle(); // randomize order in the group
API クイックリファレンス
Group (Phaser.GameObjects.Group)
Factory: this.add.group(children?, config?)
Config types:
GroupConfig -- classType, name, active, maxSize, defaultKey, defaultFrame,
runChildUpdate, createCallback, removeCallback
GroupCreateConfig -- key (required), classType, frame, quantity, visible, active,
repeat, yoyo, frameQuantity, max, setXY, setRotation,
setScale, setOrigin, setAlpha, setDepth, setScrollFactor,
hitArea, gridAlign
Key members: children (Set), classType, maxSize, defaultKey, defaultFrame,
active, runChildUpdate
Lifecycle: create, createMultiple, add, addMultiple, remove, clear, destroy
Queries: getFirst, getFirstAlive, getFirstDead, getLast, get, getChildren,
getLength, getMatching, contains, countActive, getTotalUsed,
getTotalFree, isFull
Pool: get, getFirstDead, kill, killAndHide
Bulk ops: setX/Y/XY, incX/Y/XY, setAlpha, setVisible, toggleVisible,
playAnimation, propertyValueSet, propertyValueInc, setOrigin,
setDepth, shuffle, setBlendMode, setTint
Container (Phaser.GameObjects.Container)
Factory: this.add.container(x?, y?, children?)
Extends: GameObject
Mixins: AlphaSingle, BlendMode, ComputedSize, Depth, Mask, Transform, Visible
Key members: list (Array), exclusive, maxSize, scrollFactorX/Y
Children: add, addAt, remove, removeAt, removeBetween, removeAll
Queries: getAt, getIndex, getByName, getFirst, getAll, getRandom, count
Ordering: sort, swap, moveTo, moveUp, moveDown, sendToBack, bringToTop,
moveAbove, moveBelow, reverse
Transform: pointToContainer, getBounds, getBoundsTransformMatrix
Iteration: each(cb, ctx), iterate(cb, ctx, ...args)
Config: setExclusive, setScrollFactor(x, y, updateChildren)
Property: length (read-only child count)
Layer (Phaser.GameObjects.Layer)
Factory: this.add.layer(children?)
Extends: Phaser.Structs.List
Mixins: AlphaSingle, BlendMode, Depth, Filters, Mask, RenderSteps, Visible
Key members: scene, displayList, sortChildrenFlag
Children: add, remove (inherited from List)
Settings: setAlpha, setBlendMode, setDepth, setVisible, setMask, setName,
setActive, setState, setData, getData
No position, rotation, scale, scroll factor, input, or physics.
Cannot be added to a Container. Containers can be added to Layers.
注意点
-
Group はディスプレイリスト上にありません。 その子はシーンのディスプレイリストに個別に表示されます。Group を移動しても視覚的な変化はありません。そのためには Container を使用してください。
-
Container にはパフォーマンスのオーバーヘッドがあります。 各子はフレームごとに追加の行列演算を必要とします。深いネストはこれを倍増させます。Group や Layer で十分な場合は Container を避けてください。
-
Container の原点は常に 0,0 です。 トランスフォームポイントは変更できません。子は (0,0) を基準に配置してください。
-
Container の子はシーンレベルの depth 制御を失います。 子の
depthは Container 内でのみ順序付けします。Container 自体の depth がシーン内での位置を決めます。 -
物理 + Container は問題があります。 Container が (0,0) にない場合、子の物理ボディがオフセットされます。Container の子に物理ボディを設定することは避けてください。
-
Container の子は Canvas レンダリングで個別にマスクできません。 マスクを設定できるのは Container 自体のみです。マスクはネストされた Container ではスタックしません。WebGL レンダリングではマスクはスタックします。
-
Group.get() と Group.getFirst() は異なります。
get(x, y)はgetFirst(false, true, x, y)の省略形です -- 最初の非アクティブなメンバーを探し、見つからなければ作成します。getFirst(state)は自動的に作成せずにデフォルトでactive===falseとなります。 -
Layer は Container の中に入れることができません。 Container は Layer に追加できますが、その逆はできません。
-
Group の子 Set は順序付けされません。 インデックスベースのアクセスはできません。
getChildren()を使用して配列のスナップショットを取得してください。 -
killAndHide はグループから削除しません。
active=falseとvisible=falseを設定するだけです。オブジェクトは再利用のためにグループ内に残ります。 -
Container.setScrollFactor は自動的に伝播しません。 子も更新するには第 3 引数に
trueを渡してください:container.setScrollFactor(0, 0, true)。 -
Group.create() はシーンのディスプレイリストに追加します。 しかし
group.add()は第 2 引数にtrueを渡さない限り追加しません。 -
Container には入力のために setSize() が必要です。 Container には暗黙のサイズがありません。ヒットエリアで
setInteractive()が機能する前にcontainer.setSize(width, height)を呼び出す必要があります。
ソースファイルマップ
| ファイル | 説明 |
|---|---|
src/gameobjects/group/Group.js | Group クラス -- プーリング、create、getFirst*、kill、バッチ操作 |
src/gameobjects/group/GroupFactory.js | this.add.group() ファクトリの登録 |
src/gameobjects/group/typedefs/GroupConfig.js | GroupConfig 型定義(classType、maxSize、コールバック) |
src/gameobjects/group/typedefs/GroupCreateConfig.js | GroupCreateConfig 型定義(key、quantity、setXY など) |
src/gameobjects/container/Container.js | Container クラス -- リスト管理、ネストされたトランスフォーム |
src/gameobjects/container/ContainerFactory.js | this.add.container() ファクトリの登録 |
src/gameobjects/container/ContainerRender.js | Container の WebGL/Canvas レンダリング関数 |
src/gameobjects/layer/Layer.js | Layer クラス -- alpha/blend/mask を備えたディスプレイリストのバケット |
src/gameobjects/layer/LayerFactory.js | this.add.layer() ファクトリの登録 |
src/gameobjects/layer/LayerRender.js | Layer の WebGL/Canvas レンダリング関数 |
src/physics/arcade/ArcadePhysics.js | this.physics.add.group() / staticGroup() |