mcpField
G
groups-and-containers

groups-and-containers

github.com/phaserjs開発40.6k811 回インストール10 週間前に更新
リポジトリ

groups-and-containers について

Phaser 4 開発者向けのスキルガイドです。Group、Container、Layer の違いと使い分けを、オブジェクトプール、変換の継承、描画順を含めて解説します。

主な機能

  1. 01Group、Container、Layer の比較
  2. 02Group によるオブジェクトプール
  3. 03Container による変換の継承
  4. 04Layer による描画順の制御
  5. 05API リファレンスとコード例

活用シーン

  1. 01弾などのオブジェクトの再利用
  2. 02HUD 要素をまとめて移動
  3. 03オブジェクト群の描画順を設定
  4. 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
機能GroupContainerLayer
目的論理的なコレクション / プールトランスフォームを持つ視覚的な親レンダー順のバケット
ディスプレイリスト上いいえ (子が載る)はい (子をレンダリング)はい (子をレンダリング)
位置/回転/スケールいいえはい (子が継承)いいえ
子の格納children (Set)list (Array)List (Structs.List)
物理演算physics.add.group() 経由限定的 (0,0 以外にある場合はオフセット)いいえ
入力いいえ (子は可能)はい (ヒットエリア形状が必要)いいえ
オブジェクトプーリングはい (getFirstDead, kill)いいえいいえ
マスクいいえはい (Canvas では子ごとではない)はい
アルファ/ブレンド/可視性いいえ (setVisible による一括設定)はいはい
ネスト該当なしContainer 内の ContainerContainer には入れられない
継承元EventEmitterGameObjectList
ファクトリ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.

注意点

  1. Group はディスプレイリスト上にありません。 その子はシーンのディスプレイリストに個別に表示されます。Group を移動しても視覚的な変化はありません。そのためには Container を使用してください。

  2. Container にはパフォーマンスのオーバーヘッドがあります。 各子はフレームごとに追加の行列演算を必要とします。深いネストはこれを倍増させます。Group や Layer で十分な場合は Container を避けてください。

  3. Container の原点は常に 0,0 です。 トランスフォームポイントは変更できません。子は (0,0) を基準に配置してください。

  4. Container の子はシーンレベルの depth 制御を失います。 子の depth は Container 内でのみ順序付けします。Container 自体の depth がシーン内での位置を決めます。

  5. 物理 + Container は問題があります。 Container が (0,0) にない場合、子の物理ボディがオフセットされます。Container の子に物理ボディを設定することは避けてください。

  6. Container の子は Canvas レンダリングで個別にマスクできません。 マスクを設定できるのは Container 自体のみです。マスクはネストされた Container ではスタックしません。WebGL レンダリングではマスクはスタックします。

  7. Group.get() と Group.getFirst() は異なります。 get(x, y) は getFirst(false, true, x, y) の省略形です -- 最初の非アクティブなメンバーを探し、見つからなければ作成します。getFirst(state) は自動的に作成せずにデフォルトで active===false となります。

  8. Layer は Container の中に入れることができません。 Container は Layer に追加できますが、その逆はできません。

  9. Group の子 Set は順序付けされません。 インデックスベースのアクセスはできません。getChildren() を使用して配列のスナップショットを取得してください。

  10. killAndHide はグループから削除しません。 active=false と visible=false を設定するだけです。オブジェクトは再利用のためにグループ内に残ります。

  11. Container.setScrollFactor は自動的に伝播しません。 子も更新するには第 3 引数に true を渡してください: container.setScrollFactor(0, 0, true)。

  12. Group.create() はシーンのディスプレイリストに追加します。 しかし group.add() は第 2 引数に true を渡さない限り追加しません。

  13. Container には入力のために setSize() が必要です。 Container には暗黙のサイズがありません。ヒットエリアで setInteractive() が機能する前に container.setSize(width, height) を呼び出す必要があります。

ソースファイルマップ

ファイル説明
src/gameobjects/group/Group.jsGroup クラス -- プーリング、create、getFirst*、kill、バッチ操作
src/gameobjects/group/GroupFactory.jsthis.add.group() ファクトリの登録
src/gameobjects/group/typedefs/GroupConfig.jsGroupConfig 型定義(classType、maxSize、コールバック)
src/gameobjects/group/typedefs/GroupCreateConfig.jsGroupCreateConfig 型定義(key、quantity、setXY など)
src/gameobjects/container/Container.jsContainer クラス -- リスト管理、ネストされたトランスフォーム
src/gameobjects/container/ContainerFactory.jsthis.add.container() ファクトリの登録
src/gameobjects/container/ContainerRender.jsContainer の WebGL/Canvas レンダリング関数
src/gameobjects/layer/Layer.jsLayer クラス -- alpha/blend/mask を備えたディスプレイリストのバケット
src/gameobjects/layer/LayerFactory.jsthis.add.layer() ファクトリの登録
src/gameobjects/layer/LayerRender.jsLayer の WebGL/Canvas レンダリング関数
src/physics/arcade/ArcadePhysics.jsthis.physics.add.group() / staticGroup()

コメント