mcpField
P
phaser-matter

phaser-matter

github.com/yakoub-ai開発29192 回インストール9 週間前に更新
リポジトリ

phaser-matter について

phaser-matterは、Matter.js物理エンジンを使うPhaser 4開発者向けのリファレンススキルです。Arcade Physicsでは難しい複雑な衝突形状、制約、センサー、衝突処理を解説します。

主な機能

  1. 01ポリゴンと複合剛体の作成
  2. 02剛体プロパティと力の設定
  3. 03物理ワールドの衝突イベント処理
  4. 04カテゴリによる衝突フィルタリング
  5. 05センサーと制約の利用

活用シーン

  1. 01非矩形衝突を使うゲームの構築
  2. 02ジョイントやばねで剛体を接続
  3. 03トリガー領域と衝突ルールの作成

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/yakoub-ai/phaser4-gamedev --skill phaser-matter

インストール後、Claude Code で/phaser-matterを入力して読み込まれたか確認します。

同梱ファイルreferences/matter-api.md

詳細

Phaser 4 Matter 物理演算

Matter.js は Phaser に内蔵された 2 つ目の物理演算エンジンです。Arcade 物理演算では要件を満たせない場合、特に非矩形の衝突形状、本格的な物理コンストレイント、またはラグドール風のシミュレーションが必要な場合に使用します。

Arcade と Matter — どちらを使うべきか

Arcade 物理演算(デフォルトの選択肢):

  • AABB 衝突のみ: 矩形と円
  • シンプルなヒットボックス、優れたパフォーマンス
  • 地面判定には body.blocked.down
  • 2D ゲームの 95% をカバー

Matter 物理演算(必要なとき):

  • 凸多角形の形状と複雑な凹型ボディ
  • 複合ボディ(複数の形状を溶接して 1 つにしたもの)
  • リアルなコンストレイント: ヒンジ、バネ、距離ロッド
  • ラグドール物理演算と破壊可能オブジェクト
  • 物理的に正確な衝突イベントを備えたセンサーゾーン

ルール: デフォルトは Arcade です。非矩形の衝突形状や本格的なジョイントが必要な場合にのみ Matter に切り替えてください。

Matter 物理演算の有効化

// In GameConfig:
const config: Phaser.Types.Core.GameConfig = {
  physics: {
    default: 'matter',
    matter: {
      gravity: { x: 0, y: 1 },  // normalized: 1 = earth-like downward pull
      debug: false,               // true renders wireframes — essential during development
    },
  },
};

開発中は debug: true を設定してください。Matter のワイヤーフレームオーバーレイは、各ボディがどのような形状を持ち、どのオフセットが適用され、コンストレイントがどこに接続されているかを正確に表示します。

ボディの作成

矩形(デフォルト)
// matter.add.image creates a MatterImage (static by default if you call .setStatic)
const box = this.matter.add.image(x, y, 'box');
円
const ball = this.matter.add.image(x, y, 'ball').setCircle(radius);
// Optional: pass options as second arg to setCircle
const ball2 = this.matter.add.image(x, y, 'ball').setCircle(32, { restitution: 0.8 });
凸多角形
const hex = this.matter.add.image(x, y, 'hex')
  .setBody({ type: 'polygon', sides: 6, radius: 30 });
カスタム頂点(fromVertices)
// Vertices defined as array of {x,y} relative to body center
const verts = [{ x: 0, y: -30 }, { x: 20, y: 10 }, { x: -20, y: 10 }];
const arrow = this.matter.add.image(x, y, 'arrow')
  .setBody({ type: 'fromVertices', verts: verts });

注意: Matter は poly-decomp を介して凹多角形を自動的に凸部分へ分解します。頂点の巻き方向が一貫していること(時計回りまたは反時計回り)を確認してください。

複合ボディ(複数の形状を 1 つのボディとして扱う)
// Use matter.bodies to build parts, then merge into a compound
const Body    = (this.matter as any).body as typeof MatterJS.Body;
const Bodies  = (this.matter as any).bodies as typeof MatterJS.Bodies;

const torso  = Bodies.rectangle(x, y, 30, 50);
const head   = Bodies.circle(x, y - 40, 15);
const compound = Body.create({ parts: [torso, head] });

const player = this.matter.add.image(x, y, 'player').setExistingBody(compound);
静的ボディ(移動不可)
const ground = this.matter.add.image(x, y, 'ground').setStatic(true);
// Or create a static rectangle directly:
this.matter.add.rectangle(x, y, width, height, { isStatic: true });

ボディのプロパティ

すべてのプロパティセッターはチェーン可能な MatterGameObject を返します。

const body = this.matter.add.image(x, y, 'crate');

body.setFriction(0.1);        // surface friction: 0 = ice, 1 = very sticky
body.setFrictionAir(0.05);    // air resistance / velocity damping per step
body.setFrictionStatic(0.5);  // static friction (resistance to starting motion)
body.setRestitution(0.8);     // bounciness: 0 = no bounce, 1 = perfect elastic
body.setMass(5);              // kilograms — affects momentum transfer
body.setDensity(0.002);       // alternative to setMass; mass derived from area
body.setIgnoreGravity(true);  // float independent of world gravity
body.setFixedRotation();      // prevent rotation (essential for player characters!)
body.setSensor(true);         // passes through objects but still fires collision events
body.setVelocity(vx, vy);     // teleport velocity (px/frame, not px/sec like Arcade)
body.setAngularVelocity(0.1); // spin speed in radians per step

プラットフォーマーゲームで重要: プレイヤーのボディには必ず setFixedRotation() を呼び出してください。これがないと、カプセル型のボディが表面に接触した際に転がったり倒れたりします。

空気摩擦の目安: 0.01 = 浮遊感/宇宙、0.05 = 通常、0.15 = 重い/鈍い。

力の適用

// Apply force at body center (world coordinates)
// Force is a vector in Matter units (very small numbers — tune empirically)
this.matter.applyForce(body.body as MatterJS.Body, { x: 0.01, y: -0.05 });

// Apply force at an offset point (creates torque)
this.matter.applyForceFromPosition(body.body as MatterJS.Body,
  { x: body.x + 10, y: body.y },
  { x: 0, y: -0.03 }
);

インパルス的な動き(ジャンプ、ノックバック)には、力を加えるのではなく速度を直接設定してください:

body.setVelocity(0, -10); // instant upward jump

衝突検出

Matter はボディごとのコールバックではなく、ワールド上のグローバルな衝突イベントを使用します。

// collisionstart fires once when two bodies begin touching
this.matter.world.on(
  'collisionstart',
  (event: Phaser.Physics.Matter.Events.CollisionStartEvent) => {
    event.pairs.forEach(pair => {
      const { bodyA, bodyB } = pair;
      const objA = bodyA.gameObject as Phaser.Physics.Matter.Image | null;
      const objB = bodyB.gameObject as Phaser.Physics.Matter.Image | null;
      if (objA && objB) {
        // Both bodies have Phaser game objects attached
        console.log('Collision:', objA.texture.key, 'hit', objB.texture.key);
      }
    });
  }
);

// collisionactive fires every step while bodies remain touching
this.matter.world.on('collisionactive', (event) => { /* ... */ });

// collisionend fires once when bodies separate
this.matter.world.on('collisionend', (event) => { /* ... */ });

複合ボディ: 各部分は個別の Matter ボディです。サブパーツでは bodyA.gameObject は null になります。gameObject 参照を持つのは複合ボディのルートボディのみです。

衝突フィルタリング(カテゴリとマスク)

ビットマスクカテゴリを使用して、どのオブジェクトがどのオブジェクトと衝突するかを制御します。

// Define categories as powers of 2 (bitmask flags)
const CAT_PLAYER = 0x0001;
const CAT_ENEMY  = 0x0002;
const CAT_BULLET = 0x0004;
const CAT_WALL   = 0x0008;
const CAT_PICKUP = 0x0010;

// Player: collides with enemies and walls; own bullets pass through
player.setCollisionCategory(CAT_PLAYER);
player.setCollidesWith([CAT_ENEMY, CAT_WALL]);

// Enemy: collides with player, walls, and bullets
enemy.setCollisionCategory(CAT_ENEMY);
enemy.setCollidesWith([CAT_PLAYER, CAT_BULLET, CAT_WALL]);

// Bullet: hits enemies and walls, not the player who fired it
bullet.setCollisionCategory(CAT_BULLET);
bullet.setCollidesWith([CAT_ENEMY, CAT_WALL]);

// Pickup (coin/health): only player can collect it
pickup.setCollisionCategory(CAT_PICKUP);
pickup.setCollidesWith([CAT_PLAYER]);

1 つのワールドにつき最大 32 カテゴリです。マスクに関係なく互いに決して衝突すべきでないオブジェクト(例: グループ内のすべての敵ボディが負のグループ ID を共有して自己衝突を回避する)には、衝突フィルタの group プロパティを使用してください。

センサー(トリガーゾーン)

センサーは物理応答を適用せずに重なりを検出します。チェックポイント、ダメージゾーン、敵対半径などに便利です。

// Sensor rectangle (static)
const zone = this.matter.add.rectangle(x, y, width, height, {
  isSensor: true,
  isStatic: true,
  label: 'checkpoint-zone',  // label helps identify bodies in collision events
});

// Detect entries
this.matter.world.on('collisionstart', (event) => {
  event.pairs.forEach(pair => {
    const labels = [pair.bodyA.label, pair.bodyB.label];
    if (labels.includes('checkpoint-zone')) {
      const other = pair.bodyA.label === 'checkpoint-zone' ? pair.bodyB : pair.bodyA;
      if (other.gameObject) {
        // Player entered the zone
      }
    }
  });
});

コンストレイント(ジョイント)

コンストレイントは、ロッド、バネ、またはヒンジで 2 つのボディ(またはボディと固定されたワールド上の点)を接続します。

// Distance constraint — rigid rod between two bodies
const rod = this.matter.add.constraint(
  bodyA.body as MatterJS.Body,
  bodyB.body as MatterJS.Body,
  100,    // target length (pixels)
  0.9     // stiffness: 1 = perfectly rigid, 0.01 = very springy
);

// Spring constraint — same but low stiffness
const spring = this.matter.add.constraint(
  bodyA.body as MatterJS.Body,
  bodyB.body as MatterJS.Body,
  80,     // rest length
  0.02    // stiffness (spring-like)
  // Note: damping defaults to 0; set options.damping to prevent oscillation
);

// Pin constraint — hinge a body to a fixed world point
const hinge = this.matter.add.worldConstraint(
  pendulum.body as MatterJS.Body,
  0,     // length 0 = pivot at the anchor point
  1,     // stiffness
  {
    pointA: { x: 400, y: 100 },  // fixed world position
    pointB: { x: 0, y: -40 },    // offset on the body (pivot attachment)
  }
);
コンストレイントのオプション
interface MatterConstraintConfig {
  bodyA?: MatterJS.Body;
  bodyB?: MatterJS.Body;
  pointA?: { x: number; y: number };  // attachment offset on bodyA
  pointB?: { x: number; y: number };  // attachment offset on bodyB
  length?: number;       // rest length; null = current distance
  stiffness?: number;    // 0.01–1
  damping?: number;      // 0–1, reduces oscillation; 0.1 is a good starting point
  label?: string;
}

ボディとコンストレイントの削除

// Destroy a Matter game object (removes body from world automatically)
sprite.destroy();

// Remove a constraint
this.matter.world.removeConstraint(rod);

// Remove a raw MatterJS body (not a game object)
this.matter.world.remove(rawBody);

Matter によるプラットフォーマーゲーム

Matter のプラットフォーマーでは、Matter に body.blocked.down が存在しないため、代わりに衝突イベントを使用する必要があり、特別な注意が必要です。

class Player extends Phaser.Physics.Matter.Sprite {
  private onGround = false;

  constructor(scene: Phaser.Scene, x: number, y: number) {
    super(scene.matter.world, x, y, 'player');
    scene.add.existing(this);
    this.setFixedRotation();        // prevent capsule rolling
    this.setFrictionAir(0.05);
    this.setCollisionCategory(0x0001);

    // Track ground contact via sensor at feet
    const sensor = (scene.matter as any).bodies.rectangle(0, 20, 20, 4, { isSensor: true });
    const compound = (scene.matter as any).body.create({
      parts: [this.body, sensor],
    });
    this.setExistingBody(compound, true);

    scene.matter.world.on('collisionstart', (ev: any) => {
      ev.pairs.forEach((p: any) => {
        if (p.bodyA === sensor || p.bodyB === sensor) this.onGround = true;
      });
    });
    scene.matter.world.on('collisionend', (ev: any) => {
      ev.pairs.forEach((p: any) => {
        if (p.bodyA === sensor || p.bodyB === sensor) this.onGround = false;
      });
    });
  }

  jump(): void {
    if (this.onGround) this.setVelocityY(-10);
  }
}

ワールドの設定

// Pause/resume simulation
this.matter.world.pause();
this.matter.world.resume();

// Change gravity at runtime
this.matter.world.setGravity(0, 2);  // doubled gravity
this.matter.world.setGravity(0, 0);  // zero-g

// Run one manual step (useful for deterministic replays)
this.matter.world.step(16.666);  // ms per step

// Set world bounds (creates static boundary bodies)
this.matter.world.setBounds(0, 0, worldWidth, worldHeight);

デバッグレンダリング

// Enable debug wireframes in config (recommended during development):
matter: { debug: true }

// Or toggle at runtime:
this.matter.world.drawDebug = !this.matter.world.drawDebug;
// Re-create the debug renderer if toggling on after init:
this.matter.world.createDebugGraphic();

追加リソース

リファレンスファイル
  • references/matter-api.md — MatterPhysics プラグインの全メソッド、MatterGameObject ミックスイン、MatterJS.Body の静的メンバー、コンストレイントオプション、衝突イベントの構造、CollisionFilter インターフェース

コメント