~3 min read

Arcade Physics

Fast AABB physics for platformers, shmups, and most arcade-style games — bodies, collisions, groups, and the pitfalls that trip new users.

Arcade is Phaser 4’s fast physics system. It models everything as axis-aligned bounding boxes (AABBs) or circles — no rotation, no rigid-body stacking, no joints. In exchange you get a system fast enough to step hundreds of bodies per frame on a phone.

When to use Arcade

Use Arcade when all of these are true:

  • You don’t need rotational physics (“a tilted box rests on a slope”).
  • You don’t need stacking (“boxes pile up correctly”).
  • You don’t need joints, constraints, or compound bodies.

Most platformers, top-down adventures, shmups, brick-breakers, and casual puzzle games fit. If you find yourself fighting Arcade for rigid-body behavior, switch to Matter — don’t try to fake it.

Enabling Arcade

Set it in the game config:

new Phaser.Game({
	type: Phaser.AUTO,
	width: 800,
	height: 600,
	physics: {
		default: 'arcade',
		arcade: {
			gravity: { y: 300 },
			debug: false,
		},
	},
	scene: { /* ... */ },
});

Inside a scene, this.physics is the world; this.physics.add is the factory.

Creating bodies

Two kinds: dynamic (move, collide, respond to gravity) and static (immovable; they exist to be collided with).

// Dynamic — has velocity, acceleration, gravity.
const player = this.physics.add.sprite(100, 100, 'player');

// Static — fixed in place. Cheap to collide against.
const platform = this.physics.add.staticImage(400, 568, 'ground');

Promote a regular game object after the fact with this.physics.add.existing(obj, isStatic).

Movement

Three knobs control motion:

player.setVelocity(120, 0);           // pixels per second
player.setAcceleration(0, 400);       // pixels per second^2
player.setDrag(100);                  // velocity decay per second
player.setMaxVelocity(300, 800);      // clamp

Prefer setVelocity over mutating x directly — direct position writes bypass collision resolution and you’ll tunnel through walls.

Collisions vs. overlaps

Two relationships, with different semantics:

  • Collider — Phaser separates the bodies and stops them from interpenetrating. Use for walls, floors, enemies that should physically push the player.
  • Overlap — Phaser detects the intersection but lets the bodies pass through. Use for pickups, triggers, hitboxes.
this.physics.add.collider(player, platforms);
this.physics.add.overlap(player, coins, (_p, coin) => coin.destroy());

Both take an optional process callback (return false to skip resolution for that pair) and a context.

Groups

A group is a typed pool of physics-enabled objects. Use one for any set of objects you’ll collide as a unit:

const enemies = this.physics.add.group({
	defaultKey: 'enemy',
	maxSize: 50,
	collideWorldBounds: true,
});

enemies.get(400, 0).setVelocityY(80);
this.physics.add.collider(enemies, platforms);
this.physics.add.overlap(player, enemies, this.onHit, undefined, this);

group.get() reuses an inactive member if one exists, or creates a new one up to maxSize. Cheap.

A live example

Three bouncing balls colliding with each other and the world bounds.

Arcade collisions Phaser 4 · sandboxed

Notice the setCircle call — Arcade defaults to a rectangular body even for round sprites. Calling setCircle after creation switches the body to a circle of the given radius. For circular sprites, this is almost always what you want.

Common pitfalls

Tunneling. A fast-moving body can skip past a thin wall in a single frame. Arcade physics has no continuous collision detection, so mitigations are geometric:

  • Make walls thicker than the body’s maximum per-frame travel (speed × delta).
  • Cap maximum velocity (setMaxVelocity) for objects that don’t need to be arbitrarily fast.
  • For bullet-like objects, replace the body with an overlap-with-process callback that ray-casts the bullet’s path each frame.

Sub-pixel jitter. Mixing setVelocity with manual x writes causes flicker. Pick one and stick to it within a body’s lifetime.

Body offset vs. sprite offset. The body’s bounding box may not match the sprite’s visible bounds (think: a character with a long ponytail). Use body.setSize(w, h) and body.setOffset(x, y) to tighten the box.

Group iteration during collision. Mutating a group from inside a collision callback (e.g. spawning a new member) is safe; iterating the group with a for loop while the same loop’s callback mutates it is not. Prefer group.children.iterate(...).

Debug rendering

Set arcade.debug: true in the config to draw body outlines and velocity vectors. Indispensable while you’re tuning collisions.