Phaser
40 minutes
linux
macos
windows

Physics and Collision

Implement physics-based movement, gravity, and collision detection in Phaser games.

What you'll accomplish

Create a platformer with physics-based movement and collision.

Prerequisites

  • Understanding of sprites and animations
  • Completed previous Phaser lessons

Physics Systems in Phaser

Phaser includes two physics systems:

SystemBest ForComplexity
ArcadeMost 2D games, platformersSimple
Matter.jsRealistic physics, complex shapesAdvanced

We'll focus on Arcade Physics - it handles most game scenarios.

Enabling Arcade Physics

Configure in game config:

typescript
const config: Phaser.Types.Core.GameConfig = {
  type: Phaser.AUTO,
  width: 800,
  height: 600,
  physics: {
    default: 'arcade',
    arcade: {
      gravity: { x: 0, y: 300 }, // World gravity
      debug: true, // Show collision boxes
    },
  },
  scene: [GameScene],
};

Physics Bodies

To use physics, objects need a physics body:

typescript
create(): void {
  // Static body (doesn't move)
  const ground = this.physics.add.staticImage(400, 580, 'ground');

  // Dynamic body (affected by physics)
  const player = this.physics.add.sprite(100, 300, 'player');

  // Or add physics to existing object
  const coin = this.add.image(200, 100, 'coin');
  this.physics.add.existing(coin);
}

Static vs Dynamic

TypeMovesAffected by GravityUse For
StaticNoNoGround, walls, platforms
DynamicYesYesPlayer, enemies, items

Body Properties

typescript
const player = this.physics.add.sprite(100, 300, 'player');
const body = player.body as Phaser.Physics.Arcade.Body;

// Velocity (current movement)
body.setVelocity(100, -200);
body.setVelocityX(100);
body.setVelocityY(-200);

// Max velocity (speed limit)
body.setMaxVelocity(300, 500);

// Acceleration
body.setAcceleration(50, 0);

// Drag (friction/slowdown)
body.setDrag(100, 0);

// Bounce (0-1, how bouncy)
player.setBounce(0.2);

// Gravity (per-body)
body.setGravityY(500);

// Mass (affects collisions)
body.setMass(1);

// Stay in bounds
player.setCollideWorldBounds(true);

// Immovable (won't be pushed)
body.setImmovable(true);

// Collision box size
body.setSize(24, 40);
body.setOffset(4, 8);

// Circular collision
body.setCircle(20);

Collision Detection

Collider (Physical Collision)

Objects bounce off each other:

typescript
create(): void {
  const player = this.physics.add.sprite(100, 300, 'player');
  const ground = this.physics.add.staticImage(400, 580, 'ground');

  // Player collides with ground
  this.physics.add.collider(player, ground);

  // With callback
  this.physics.add.collider(player, enemies, (player, enemy) => {
    // Called when they collide
    this.playerHit(enemy);
  });
}

Overlap (Detect but Don't Collide)

Objects pass through each other:

typescript
create(): void {
  const player = this.physics.add.sprite(100, 300, 'player');
  const coins = this.physics.add.group();

  // Detect overlap
  this.physics.add.overlap(player, coins, (player, coin) => {
    coin.destroy();
    this.score += 10;
  });
}

Groups

Manage multiple similar objects:

typescript
create(): void {
  // Create a group
  this.coins = this.physics.add.group();

  // Add objects to group
  for (let i = 0; i < 10; i++) {
    const coin = this.coins.create(100 + i * 70, 100, 'coin');
    coin.setBounce(0.5);
  }

  // Static group (platforms, ground)
  this.platforms = this.physics.add.staticGroup();
  this.platforms.create(400, 580, 'ground');
  this.platforms.create(200, 400, 'platform');

  // Collision with group
  this.physics.add.collider(this.player, this.platforms);
  this.physics.add.overlap(this.player, this.coins, this.collectCoin, null, this);
}

collectCoin(player: Phaser.Physics.Arcade.Sprite, coin: Phaser.Physics.Arcade.Sprite): void {
  coin.disableBody(true, true); // Disable and hide
  this.score += 10;
}

Platformer Example

A complete platformer scene:

typescript
import Phaser from 'phaser';

export class PlatformerScene extends Phaser.Scene {
  private player!: Phaser.Physics.Arcade.Sprite;
  private platforms!: Phaser.Physics.Arcade.StaticGroup;
  private cursors!: Phaser.Types.Input.Keyboard.CursorKeys;

  constructor() {
    super('PlatformerScene');
  }

  preload(): void {
    // For testing, create simple rectangles
    this.load.image('ground', 'assets/ground.png');
    this.load.spritesheet('player', 'assets/player.png', {
      frameWidth: 32,
      frameHeight: 48,
    });
  }

  create(): void {
    // Create platforms
    this.platforms = this.physics.add.staticGroup();

    // Ground
    this.platforms.create(400, 584, 'ground').setScale(2).refreshBody();

    // Floating platforms
    this.platforms.create(600, 400, 'ground');
    this.platforms.create(50, 250, 'ground');
    this.platforms.create(750, 220, 'ground');

    // Create player
    this.player = this.physics.add.sprite(100, 450, 'player');
    this.player.setBounce(0.1);
    this.player.setCollideWorldBounds(true);

    // Player animations
    this.anims.create({
      key: 'left',
      frames: this.anims.generateFrameNumbers('player', { start: 0, end: 3 }),
      frameRate: 10,
      repeat: -1,
    });

    this.anims.create({
      key: 'idle',
      frames: [{ key: 'player', frame: 4 }],
      frameRate: 20,
    });

    this.anims.create({
      key: 'right',
      frames: this.anims.generateFrameNumbers('player', { start: 5, end: 8 }),
      frameRate: 10,
      repeat: -1,
    });

    // Collision
    this.physics.add.collider(this.player, this.platforms);

    // Input
    this.cursors = this.input.keyboard!.createCursorKeys();
  }

  update(): void {
    const body = this.player.body as Phaser.Physics.Arcade.Body;

    if (this.cursors.left.isDown) {
      body.setVelocityX(-160);
      this.player.anims.play('left', true);
    } else if (this.cursors.right.isDown) {
      body.setVelocityX(160);
      this.player.anims.play('right', true);
    } else {
      body.setVelocityX(0);
      this.player.anims.play('idle');
    }

    // Jump only when touching ground
    if (this.cursors.up.isDown && body.blocked.down) {
      body.setVelocityY(-400);
    }
  }
}

Checking Collision State

typescript
update(): void {
  const body = this.player.body as Phaser.Physics.Arcade.Body;

  // Check what the body is touching
  if (body.blocked.down) {
    // Standing on something
  }

  if (body.blocked.left || body.blocked.right) {
    // Touching a wall
  }

  if (body.touching.none) {
    // In the air
  }

  // Is body moving?
  if (body.velocity.x !== 0 || body.velocity.y !== 0) {
    // Moving
  }
}

World Bounds

typescript
create(): void {
  // Set world size (can be larger than screen)
  this.physics.world.setBounds(0, 0, 1600, 600);

  // Player stays in bounds
  this.player.setCollideWorldBounds(true);

  // Camera follows player
  this.cameras.main.setBounds(0, 0, 1600, 600);
  this.cameras.main.startFollow(this.player);

  // World bounds collision events
  this.physics.world.on('worldbounds', (body: Phaser.Physics.Arcade.Body) => {
    console.log('Hit world bounds!');
  });

  // Enable worldbounds event on body
  this.player.body.onWorldBounds = true;
}

Tilemaps with Physics

For level design, use tilemaps:

typescript
preload(): void {
  this.load.image('tiles', 'assets/tileset.png');
  this.load.tilemapTiledJSON('level1', 'assets/level1.json');
}

create(): void {
  // Create tilemap
  const map = this.make.tilemap({ key: 'level1' });
  const tileset = map.addTilesetImage('tileset', 'tiles');

  // Create layers
  const backgroundLayer = map.createLayer('Background', tileset, 0, 0);
  const groundLayer = map.createLayer('Ground', tileset, 0, 0);

  // Enable collision for ground layer
  groundLayer.setCollisionByProperty({ collides: true });

  // Or by tile index
  groundLayer.setCollisionBetween(1, 10);

  // Player collides with ground layer
  this.physics.add.collider(this.player, groundLayer);
}
Tip

Use Tiled Map Editor (free) to create tilemaps visually.

Common Issues

Frequently Asked Questions

  1. Make sure platforms are staticGroup or have setImmovable(true)
  2. Check that collider is set up: this.physics.add.collider(player, platforms)
  3. Ensure sprites are loaded correctly

Check if player is on ground before jumping:

typescript
if (this.cursors.up.isDown && body.blocked.down) {
  body.setVelocityY(-400);
}

Adjust the body size and offset:

typescript
body.setSize(24, 40);
body.setOffset(4, 8);

Use debug: true to visualize collision boxes.

Common causes:

  • Overlapping static bodies
  • Too high gravity with low collision response
  • Try increasing friction/drag

Check callback signature - Phaser passes objects, not just sprites:

typescript
this.physics.add.overlap(player, coins, (p, c) => {
  // p and c are the colliding objects
}, null, this);

Verify Your Understanding

You've completed this lesson if you can:

0/5

Next: Add sound effects and music to your game.