Skip to main content
ClaudeWave
Install in Claude Code
Copy
git clone --depth 1 https://github.com/gamedev-skills/awesome-gamedev-agent-skills /tmp/phaser-arcade-physics && cp -r /tmp/phaser-arcade-physics/skills/web-engines/phaser-arcade-physics ~/.claude/skills/phaser-arcade-physics
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

# Phaser 4 Arcade Physics

Add movement and collision to a Phaser game with the lightweight **Arcade
Physics** engine (AABB rectangles and circles only). Targets **Phaser 4.2** for
new projects; inspect the installed major before editing an existing project.

## When to use

- Use for top-down or platformer movement, velocity/acceleration/gravity, bouncing,
  world bounds, and collision/overlap resolution between sprites, groups, and tiles.
- Use when the scene enables `physics: { default: 'arcade' }` and code calls
  `this.physics.add.*`, `body.setVelocity`, or `this.physics.add.collider`.

**When *not* to use:** the `Game` config, scene structure, asset loading, or
cameras → use `phaser-core`. Hinges, springs, complex polygons, or stacking
rigid bodies → use Matter physics (a different engine; Arcade and Matter bodies
do not interact). For engine-agnostic feel tuning see `physics-tuning`.

## Core workflow

1. **Enable the world.** Set `physics: { default: 'arcade', arcade: { gravity:
   {...}, debug: true } }` in the game or scene config. Turn `debug` on while
   building to see body outlines and velocity vectors.
2. **Give a sprite a body.** Create it with `this.physics.add.sprite(...)` (dynamic)
   or `this.physics.add.staticImage(...)` (static), or attach to an existing object
   with `this.physics.add.existing(obj)`.
3. **Drive it through the body, not by setting `x`/`y`.** Use `setVelocity`,
   `setAcceleration`, gravity, `setBounce`, and `setCollideWorldBounds`. The engine
   integrates position from velocity each step (already frame-rate independent).
4. **Resolve interactions.** `this.physics.add.collider(a, b)` separates bodies;
   `this.physics.add.overlap(a, b, cb)` detects without separating (pickups,
   triggers). Pass a callback to react.
5. **Group many objects.** Use `this.physics.add.group()` (dynamic) or
   `staticGroup()` (platforms) so one collider call handles all members.
6. **Check ground contact** with `body.onFloor()` / `body.blocked.down` before
   jumping. Run with `debug: true` and confirm bodies, contacts, and bounds.

## Patterns

### 1. Enable Arcade Physics (game config)

```js
const config = {
  type: Phaser.AUTO,
  width: 800, height: 600,
  physics: {
    default: 'arcade',
    arcade: {
      gravity: { x: 0, y: 600 },  // top-down? use { x: 0, y: 0 }
      debug: false                // true to draw bodies + velocity while building
    }
  },
  scene: [PlayScene]
};
new Phaser.Game(config);
```

### 2. Top-down movement (velocity from input)

```js
create() {
  this.player = this.physics.add.sprite(400, 300, 'player');
  this.player.setCollideWorldBounds(true);
  this.cursors = this.input.keyboard.createCursorKeys();
}

update() {
  const speed = 220;
  const body = this.player.body;
  body.setVelocity(0);                              // reset each frame
  if (this.cursors.left.isDown)  body.setVelocityX(-speed);
  if (this.cursors.right.isDown) body.setVelocityX(speed);
  if (this.cursors.up.isDown)    body.setVelocityY(-speed);
  if (this.cursors.down.isDown)  body.setVelocityY(speed);
  body.velocity.normalize().scale(speed);           // keep diagonals same speed
}
```

### 3. Platformer jump (gravity + ground check)

```js
create() {
  this.player = this.physics.add.sprite(100, 450, 'player');
  this.player.setCollideWorldBounds(true);

  // Static platforms: one body each, never moved by collisions.
  this.platforms = this.physics.add.staticGroup();
  this.platforms.create(400, 568, 'ground');
  this.physics.add.collider(this.player, this.platforms);

  this.cursors = this.input.keyboard.createCursorKeys();
}

update() {
  const onGround = this.player.body.blocked.down; // or this.player.body.onFloor()
  if (this.cursors.left.isDown)  this.player.setVelocityX(-160);
  else if (this.cursors.right.isDown) this.player.setVelocityX(160);
  else this.player.setVelocityX(0);

  if (this.cursors.up.isDown && onGround) this.player.setVelocityY(-450);
}
```

### 4. Colliders vs overlaps (separate vs detect)

```js
// Push apart and react: player vs enemies.
this.physics.add.collider(this.player, this.enemies, (player, enemy) => {
  this.handleHit(player, enemy);
});

// Detect without pushing: collect coins. The 4th arg is an optional
// process callback returning a boolean to filter pairs before the main callback.
this.physics.add.overlap(this.player, this.coins, (player, coin) => {
  coin.disableBody(true, true);              // deactivate + hide
  this.registry.inc('score', 10);
});
```

### 5. A group of moving objects

```js
this.bullets = this.physics.add.group({
  defaultKey: 'bullet',
  maxSize: 30                  // pool size; reuse instead of allocating
});

fire(x, y) {
  const bullet = this.bullets.get(x, y);     // reuses a dead bullet if available
  if (!bullet) return;
  bullet.enableBody(true, x, y, true, true);
  bullet.setVelocityY(-500);
}
```

## Pitfalls

- **Sprite ignores physics** → it was added with `this.add.sprite` instead of
  `this.physics.add.sprite` (or `this.physics.add.existing(obj)`), so it has no body.
- **Setting `sprite.x` directly fights the engine** → move dynamic bodies with
  `setVelocity`/`setAcceleration`. Direct position writes can tunnel through colliders.
- **Diagonal movement is faster** → independent X and Y velocities add up; normalise
  the velocity vector and rescale to the intended speed.
- **Platforms get pushed by the player** → use a `staticGroup`, or set
  `body.setImmovable(true)` on a dynamic platform.
- **`onFloor()` is always false** → the body needs something to collide with; add the
  `collider` against the ground/platforms before checking, and ensure gravity is on.
- **Moved a static body but collisions are stale** → static bodies don't auto-sync;
  call `body.updateFromGameObject()` (or `refreshBody()` on the game object).
- **Collider added every frame** → register `collider`/`overlap` once in `create`,
  not in `update`.

## References

- For body anatomy and tuning (drag, bounce, max