gamedev-skills/awesome-gamedev-agent-skillsCheck passed
SKILL DETAIL
phaser-arcade-physics
gamedev-skills/awesome-gamedev-agent-skills/phaser-arcade-physics
Use Phaser 4 Arcade Physics: enable the world, give sprites bodies, set velocity/acceleration/gravity, and resolve collisions with colliders, overlaps, groups, and world bounds. Use when a Phaser game needs movement or collisions — when the user mentions Arcade Physics, this.physics, setVelocity, collider, overlap, gravity, onFloor, or a platformer/top-down controller. For game config, scenes, and the loader use phaser-core.
Installs · 283View source
Installation
npx skills add https://github.com/gamedev-skills/awesome-gamedev-agent-skills --skill phaser-arcade-physics
Skill files
SKILL.md
Last synced · Oct 26, 2026
references/bodies-and-collision.md›
# Arcade body anatomy & collision detail (Phaser 4.2)
Depth on the Arcade `Body` and the collision system. The `SKILL.md` covers the
common path; reach for this when tuning feel, shaping hitboxes, or filtering
collisions.
## Dynamic vs static bodies
- **Dynamic** (`this.physics.add.sprite`, `image`, `group`): moved by velocity,
acceleration, drag, and gravity; pushed by collisions.
- **Static** (`staticImage`, `staticSprite`, `staticGroup`): never moves, never
pushed. Cheaper. Use for ground, walls, platforms. After moving its parent game
object, call `body.updateFromGameObject()` (or `gameObject.refreshBody()`) so the
body re-syncs — static bodies do **not** track their parent automatically.
Make a dynamic body behave like a wall with `body.setImmovable(true)`; it still
exists in the simulation but won't be displaced by collisions.
## Movement properties
| Property | Setter | Notes |
|----------|--------|-------|
| velocity | `setVelocity(x, y)` / `setVelocityX/Y` | pixels per second |
| acceleration | `setAcceleration(x, y)` | pixels/sec²; accumulates into velocity |
| gravity | `setGravity(x, y)` | added on top of world gravity |
| drag | `setDrag(x, y)` | px/sec² of slowdown, applied only while that axis has no acceleration; with `setDamping(true)` it is the share of velocity kept per second (e.g. `0.05`) |
| max velocity | `setMaxVelocity(x, y)` | clamp speed |
| bounce | `setBounce(x, y)` | 0 = stop on hit, 1 = full rebound |
| friction | `setFriction(x, y)` | share of motion passed to a riding body |
`body.stop()` zeroes velocity, acceleration, and speed in one call.
`body.reset(x, y)` teleports the body and clears motion.
Total gravity on a body = `world.gravity + body.gravity`. Disable world gravity
for a single body with `body.setAllowGravity(false)` (useful for flying enemies).
## Custom hitboxes
The body defaults to the sprite's frame size. Tighten it to the art:
```js
sprite.body.setSize(20, 36, true); // width, height, center on the frame
sprite.body.setOffset(6, 4); // nudge the box within the frame
sprite.body.setCircle(16); // circular body of radius 16
```
`setSize`'s third argument re-centers the body; pass `false` to keep the offset at
`(0, 0)` and position it yourself with `setOffset`.
## Colliders, overlaps, and callbacks
```js
const collider = this.physics.add.collider(
objectsA, objectsB,
collideCallback, // (objA, objB) => {} runs after separation
processCallback, // (objA, objB) => boolean return false to skip this pair
this // callback context
);
collider.active = false; // temporarily disable
this.physics.world.removeCollider(collider); // remove entirely
```
`overlap(...)` has the same signature but never separates the bodies — use it for
pickups, hurt-boxes, and triggers. A `processCallback` that returns `false`
discards the pair before the main callback (e.g. ignore collisions with allied
units).
`objectsA`/`objectsB` may each be a single game object, an array, or a physics
group, so one call can wire up "all bullets vs all enemies".
## Contact state
After the physics step, each body exposes which sides are touching:
```js
body.blocked.down // can't move down: bounds, tile, or a blocking body below
body.touching.left // touching another body on the left this step
body.onFloor() // shorthand for blocked.down
body.onWall() // blocked.left || blocked.right
body.wasTouching // touching state from the previous step (edge detection)
```
`touching` is set for any body contact. `blocked` means the body cannot move further
that way: the world bounds, a colliding tile, an immovable, static or non-pushable
(`sprite.setPushable(false)`) body, or a body that is itself blocked (a crate resting
on the floor). That is why `blocked.down`/`onFloor()` works as a ground check on a
`staticGroup` platform but stays `false` when landing on an ordinary dynamic body in
mid-air. Compare `touching` against `wasTouching` to detect the first frame of a hit
(e.g. play a sound once on landing).
## World bounds
```js
this.physics.world.setBounds(0, 0, 1600, 1200); // simulation bounds
body.setCollideWorldBounds(true); // clamp this body
body.onWorldBounds = true; // emit events at edges
this.physics.world.on('worldbounds', (b, up, down, left, right) => { /* ... */ });
```
## Collision categories & masks
Every body starts in category `1` with a mask of `1`, so everything collides with
everything. A pair is tested only when **each** side's mask contains the other side's
category. Moving a body into a new category therefore also cuts it off from every
body you have not touched, because their mask is still `1`:
```js
const WORLD = 1; // the default category
const PLAYER = this.physics.nextCategory(); // 2, 4, 8, ... (max 32 categories)
const ENEMY = this.physics.nextCategory();
player.setCollisionCategory(PLAYER);
player.setCollidesWith([WORLD, ENEMY]); // keep the untouched ground
// The ground still has mask 1, so it would now ignore PLAYER: widen it too.
ground.setCollidesWith([WORLD, PLAYER, ENEMY]); // or ground.resetCollisionCategory()
```
When one side of a collider is a group, the group's own category and mask are checked
**and** each member's. The group's values are not copied to its members, so set both:
```js
enemies.setCollisionCategory(ENEMY).setCollidesWith([WORLD, PLAYER]);
const enemy = enemies.create(600, 400, 'enemy'); // and every other member
enemy.setCollisionCategory(ENEMY).setCollidesWith([WORLD, PLAYER]);
```
A dynamic physics group starts with a mask of all bits; a static group and every
body start with mask `1`. `resetCollisionCategory()` sets category `1` and a mask of
all bits ("collides with everything"), which is wider than a new body's default.
If you only need to skip a few pairs, a `processCallback` that returns `false` is
simpler than categories.
## Debugging
While building, enable `arcade.debug: true` to render body rectangles, static-body
outlines, and velocity vectors. Per-body overrides:
```js
this.physics.world.defaults.debugShowVelocity = true;
gameObject.setDebug(true, true, 0x00ff00); // showBody, showVelocity, color
```
Turn debug off for release builds; the overlay has a real per-frame cost.
SKILL.md›
---
name: phaser-arcade-physics
description: >
Use Phaser 4 Arcade Physics: enable the world, give sprites bodies, set
velocity/acceleration/gravity, and resolve collisions with colliders, overlaps,
groups, and world bounds. Use when a Phaser game needs movement or collisions —
when the user mentions Arcade Physics, this.physics, setVelocity, collider,
overlap, gravity, onFloor, or a platformer/top-down controller. For game config,
scenes, and the loader use phaser-core.
---
# 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 an inactive bullet if available
if (!bullet) return; // pool is full: every bullet is active
bullet.enableBody(true, x, y, true, true);
bullet.setVelocityY(-500);
}
update() {
// Return off-screen bullets to the pool, or firing stops after 30 shots.
this.bullets.getChildren().forEach((bullet) => {
if (bullet.active && bullet.y < -32) bullet.disableBody(true, true);
});
}
```
## 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.
Standing on an ordinary dynamic body does not count as blocked; make platforms
static, immovable, or non-pushable (`platform.setPushable(false)`).
- **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`.
- **Fast bullets pass through thin walls** → Arcade has no swept collision; a body
that moves farther than a wall's thickness in one physics step skips it. Thicken
the wall's body, cap speed with `setMaxVelocity`, or raise the step rate
(`arcade: { fps: 120 }`, default 60) at extra CPU cost.
- **Bodies pass through each other after adding collision categories** → a pair only
collides when each side's mask includes the other's category, and untouched bodies
keep mask `1`. Widen the other side's mask too, and set groups and their members
separately (see the reference).
- **Drag does nothing while a key is held** → Arcade applies drag only on an axis
with zero acceleration; set the acceleration to 0 when input stops.
## References
- For body anatomy and tuning (drag, bounce, max velocity, custom `setSize`/
`setCircle`/`setOffset` hitboxes, collision categories/masks, and `worldbounds`
events), read `references/bodies-and-collision.md`.
## Related skills
- `phaser-core` — game config, scenes, loader, cameras (the prerequisite setup).
- `physics-tuning` — engine-agnostic feel (fixed timestep, tunneling, jitter).
- `platformer` / `tower-defense` — genres that compose this skill.
- `level-design` — laying out tile/platform geometry these bodies collide with.