20. Physics.Arcade.Group

はじめに

敵や弾など、似たような挙動をする多数のスプライトを扱うとき、個々にコリジョンを設定するのではなく、Phaser.Physics.Arcade.Group を活用することでコードをスッキリさせつつ、効率的に物理演算を行うことができます。

グループを使ったコリジョン設定の利点

例えば、プレイヤーと敵が接触した際に処理を行いたい場合、次のようなコードになります。

// グループ作成
this.playerGroup = this.physics.add.group();
this.enemyGroup = this.physics.add.group();

// プレイヤーや敵をグループに追加
this.playerGroup.add(playerSprite);
this.enemyGroup.add(enemySprite);

// コリジョン(オーバーラップ)設定
this.physics.add.overlap(this.playerGroup, this.enemyGroup, this.handlePlayerEnemyOverlap, undefined, this);

このようにグループ同士で overlap を設定することで、すべてのスプライト間の接触を一括で処理できます。

注意点:グループに追加すると状態が初期化される

ただし注意すべき点があります。Arcade.Group にスプライトを追加したタイミングで、そのスプライトの物理プロパティ(特に velocity や acceleration)が初期化されます。 たとえば、以下のようにすると問題が起こる可能性があります。

enemySprite.setVelocity(100, 0); // ← ここで速度を設定
this.enemyGroup.add(enemySprite); // ← 追加した時点で velocity が(0,0)にリセットされる

これに気づかず、バグに悩まされました。

対策1:追加後にプロパティを設定する

グループに追加した後にプロパティを設定するようにします。 ECSの場合、グループに追加のあとにVelocityを変更するように、システムの設計を見直す必要があります。

this.enemyGroup.add(enemySprite);
enemySprite.setVelocity(100, 0);

対策2:グループ追加時の初期化を防ぐ方法

別の解決法として、group.defaults に明示的に空オブジェクトを指定することで、この初期化を無効化できます。

const enemyGroup = this.physics.add.group();
enemyGroup.defaults = {}; // 初期化されないように設定
enemyGroup.add(enemySprite);

この設定をしておけば、add() 時にスプライトのプロパティが意図せず書き換わるのを防げます。 ただし、オブジェクトプールでスプライトの使い回しする場合には、初期化しないことで意図せぬ問題が発生することもあります。 (後述のリサイクルパターン)

その他の補足ポイント

GameObjects.Group vs Physics.Arcade.Group の違い

GameObjects.Group は物理演算を持ちませんが、Physics.Arcade.Group は自動で物理ボディを付与します。 コリジョン設定する場合は、Arcade.Group を使います。

create()

group.create() の基本的な使い方

const enemyGroup = this.physics.add.group({
  defaultKey: 'enemy',
  defaultFrame: 0,
});

// 指定位置に新しいスプライトを生成し、グループに追加
const enemy = enemyGroup.create(100, 200);
enemy.setVelocity(100, 0);

ここでは defaultKey と defaultFrame を設定しているため、create(x, y) だけでスプライトを生成できます。 classType を設定することもできます。(独自のEnemyクラスを使う場合など)

createMultiple()

createMultiple() を使うと、同じ種類のスプライトをまとめてグループに生成・追加できます:

this.enemyGroup.createMultiple({
  key: 'enemy',
  repeat: 5,
  setXY: { x: 100, y: 100, stepX: 50 }
});

setXYのx,yは最初の座標です。stepX=50なので、xは100,150,200,250,300 と変化します。

getChildren()

getChildren() でグループ内のすべてのスプライトにアクセスし、まとめて設定を変更できます:

this.enemyGroup.getChildren().forEach((enemy: Phaser.GameObjects.Sprite) => {
  enemy.setAlpha(0.5);
});

get() とリサイクルパターン

毎回 new でスプライトを作成せず、非表示になったスプライトを再利用することで、パフォーマンスが向上します。

group.get() は使われていない(active = false)スプライトを再利用する仕組みです。大量のオブジェクトを繰り返し生成・破棄するのを避け、パフォーマンスの向上に繋がります。

使い終わったスプライトは setActive(false) と setVisible(false) にしておくことで、次回 get() 時に再利用されます。

const bullet = this.bulletGroup.get(x, y);
if (bullet) {
  bullet.setActive(true).setVisible(true);
  bullet.setVelocity(0, -300);
}

まとめ

Arcade.Groupについて整理しました。