| name | physics-arcade |
| description | Use this skill when using Arcade Physics in Phaser 4. Covers enabling physics, velocity, acceleration, gravity, collisions, overlap, world bounds, physics groups, static bodies, and collision categories. Triggers on: physics, arcade, velocity, gravity, collide, overlap, bounce, physics body. |
Arcade Physics
Setting up and using Arcade Physics in Phaser 4 -- enabling physics in GameConfig, creating physics-enabled sprites/images/groups, velocity, acceleration, gravity, collisions (collide/overlap), world bounds, body properties, and collision categories.
Key source paths: src/physics/arcade/ArcadePhysics.js, src/physics/arcade/World.js, src/physics/arcade/Body.js, src/physics/arcade/StaticBody.js, src/physics/arcade/Factory.js, src/physics/arcade/components/, src/physics/arcade/events/
Related skills: ../game-setup-and-config/SKILL.md, ../sprites-and-images/SKILL.md, ../tilemaps/SKILL.md, ../groups-and-containers/SKILL.md
Quick Start
class GameScene extends Phaser.Scene {
create() {
this.player = this.physics.add.sprite(100, 300, 'player');
this.player.setCollideWorldBounds(true);
this.player.setBounce(0.2);
this.platforms = this.physics.add.staticGroup();
this.platforms.create(400, 568, 'ground').setScale(2).refreshBody();
this.physics.add.collider(this.player, this.platforms);
this.cursors = this.input.keyboard.createCursorKeys();
}
update() {
if (this.cursors.left.isDown) {
this.player.setVelocityX(-160);
} else if (this.cursors.right.isDown) {
this.player.setVelocityX(160);
} else {
this.player.setVelocityX(0);
}
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
physics: {
default: 'arcade',
arcade: {
gravity: { y: 300 },
debug: false
}
},
scene: GameScene
};
const game = new Phaser.Game(config);
Core Concepts
World (this.physics.world)
The Phaser.Physics.Arcade.World manages all bodies in the simulation. Accessed via this.physics.world in a Scene.
- gravity --
Vector2 applied to all dynamic bodies each step. Set via config or this.physics.world.gravity.y = 300.
- bounds --
Rectangle defining the world boundary. Defaults to game canvas size.
- bodies --
Set of all dynamic Body instances.
- staticBodies --
Set of all StaticBody instances.
- colliders --
ProcessQueue of registered Collider objects (from this.physics.add.collider / this.physics.add.overlap).
- fps -- Physics steps per second (default 60). Read-only; change via
world.setFPS(120).
- fixedStep --
true (default) uses fixed timestep; false syncs to render fps.
- timeScale -- Scaling factor: 1.0 = normal, 2.0 = half speed, 0.5 = double speed.
- isPaused -- When true, no bodies update and no colliders run. Toggle via
world.pause() / world.resume().
- useTree --
true (default) uses RTree spatial index for dynamic bodies. Disable for 5000+ bodies.
- drawDebug -- Enables debug rendering when
true. Set via config debug: true.
Bodies (Body)
A dynamic body is created automatically when you use this.physics.add.sprite() or this.physics.add.image(). Access it via gameObject.body.
Key properties (all on Body):
- velocity --
Vector2, pixels/sec
- acceleration --
Vector2, pixels/sec^2
- drag --
Vector2, deceleration (applied only when acceleration is zero)
- gravity --
Vector2, per-body gravity added to world gravity
- bounce --
Vector2, rebound factor relative to 1
- friction --
Vector2, default (1, 0) -- velocity imparted by immovable body to riding body
- maxVelocity --
Vector2, default (10000, 10000) | maxSpeed -- scalar cap, default -1 (none)
- mass -- default
1, affects collision momentum exchange
- immovable --
false; if true, never moved by collisions
- pushable --
true; if false, reflects velocity to colliding body
- moves --
true; if false, position/rotation not updated by physics
- enable --
true; false removes from simulation
- useDamping --
false; when true, drag is a multiplier (0-1) instead of linear
- collideWorldBounds --
false; onWorldBounds/onCollide/onOverlap -- false, set true to emit events
Shape: isCircle (set via setCircle), width/height, offset (Vector2), center (read-only midpoint).
Collision state (read-only, reset each step): touching / blocked / wasTouching -- { none, up, down, left, right }. embedded -- both overlapping with zero velocity.
Angular: angularVelocity (deg/sec), angularAcceleration, angularDrag, maxAngular (default 1000), allowRotation (default true).
Static Bodies (StaticBody)
A static body never moves and is not affected by gravity or velocity. It uses an optimized RTree for fast spatial lookups.
- Created via
this.physics.add.staticSprite(), this.physics.add.staticImage(), or this.physics.add.staticGroup().
- After changing the parent Game Object's position or scale, call
body.reset() or gameObject.refreshBody() to sync.
- Has
collisionCategory and collisionMask like dynamic bodies.
- Does not have velocity, acceleration, drag, or gravity properties.
Common Patterns
Enabling Physics on Existing Game Objects
this.physics.add.existing(mySprite);
this.physics.add.existing(mySprite, true);
this.physics.world.enable(mySprite);
this.physics.world.enable(mySprite, Phaser.Physics.Arcade.STATIC_BODY);
Creating Physics Sprites and Images
const player = this.physics.add.sprite(100, 200, 'player');
const coin = this.physics.add.image(300, 100, 'coin');
const wall = this.physics.add.staticImage(400, 300, 'wall');
const platform = this.physics.add.staticSprite(400, 500, 'plat');
const sensor = this.physics.add.body(200, 200, 32, 32);
const zone = this.physics.add.staticBody(100, 100, 64, 64);
Velocity and Gravity
player.setVelocity(200, -300);
player.setVelocityX(200);
player.body.velocity.set(200, -300);
player.setAcceleration(100, 0);
player.setMaxVelocity(300, 600);
player.body.gravity.y = 200;
player.body.allowGravity = false;
player.setDrag(300);
player.body.useDamping = true;
player.setDrag(0.05);
player.setBounce(0.5);
player.setBounceY(1);
Collide and Overlap
Two approaches: persistent Colliders (checked every frame automatically) or one-shot checks (called in update()).
const collider = this.physics.add.collider(player, platforms);
const overlap = this.physics.add.overlap(player, coins, collectCoin, null, this);
function collectCoin (player, coin) {
coin.disableBody(true, true);
}
collider.active = false;
collider.destroy();
this.physics.add.collider(player, enemies, onHit, canCollide, this);
function canCollide (player, enemy) {
return !player.getData('invincible');
}
this.physics.collide(player, platforms);
this.physics.overlap(player, coins, collectCoin, null, this);
this.physics.add.collider(enemies, enemies);
Groups
const bullets = this.physics.add.group({
classType: Phaser.Physics.Arcade.Sprite,
maxSize: 20,
collideWorldBounds: true,
bounceX: 1,
bounceY: 1,
velocityX: 200,
velocityY: 0,
allowGravity: false,
immovable: false
});
const platforms = this.physics.add.staticGroup();
platforms.create(400, 568, 'ground');
platforms.create(600, 400, 'ground');
platforms.create(400, 568, 'ground').setScale(2).refreshBody();
bullets.setVelocity(200, 0);
bullets.setVelocityX(200);
bullets.setVelocityY(0, 10);
PhysicsGroup config keys (applied as defaults to new members): collideWorldBounds, bounceX/Y, accelerationX/Y, dragX/Y, gravityX/Y, frictionX/Y, velocityX/Y, angularVelocity, angularAcceleration, angularDrag, maxVelocityX/Y, maxSpeed, mass, immovable, allowDrag, allowGravity, allowRotation, useDamping, enable.
World Bounds
this.physics.world.setBounds(0, 0, 1600, 1200, true, true, false, true);
this.physics.world.setBoundsCollision(true, true, false, true);
player.setCollideWorldBounds(true);
player.body.setBoundsRectangle(new Phaser.Geom.Rectangle(100, 100, 600, 400));
player.body.worldBounce = new Phaser.Math.Vector2(0.5, 0.5);
player.body.onWorldBounds = true;
this.physics.world.on('worldbounds', (body, up, down, left, right) => {
console.log('Hit edge:', { up, down, left, right });
});
Body Properties
player.body.setSize(24, 32, true);
player.body.setOffset(4, 0);
player.body.setCircle(16);
player.body.setCircle(16, 4, 4);
player.body.setImmovable(true);
player.body.setPushable(false);
player.body.slideFactor.set(0, 0);
player.body.setMass(2);
player.disableBody(true, true);
player.enableBody(true, x, y, true, true);
player.body.checkCollision.up = false;
player.body.syncBounds = true;
Collision Categories
Collision categories let you filter which bodies can collide with each other using bitmask values. Maximum 32 categories.
const CAT_PLAYER = this.physics.nextCategory();
const CAT_ENEMY = this.physics.nextCategory();
const CAT_BULLET = this.physics.nextCategory();
player.setCollisionCategory(CAT_PLAYER);
enemy.setCollisionCategory(CAT_ENEMY);
bullet.setCollisionCategory(CAT_BULLET);
player.setCollidesWith([CAT_ENEMY]);
enemy.setCollidesWith([CAT_PLAYER, CAT_BULLET]);
bullet.setCollidesWith([CAT_ENEMY]);
player.addCollidesWith(CAT_BULLET);
player.removeCollidesWith(CAT_ENEMY);
player.willCollideWith(CAT_ENEMY);
player.resetCollisionCategory();
enemies.setCollisionCategory(CAT_ENEMY);
enemies.setCollidesWith([CAT_PLAYER, CAT_BULLET]);
Default: all bodies have category 0x0001 and collisionMask 1. Everything still collides with everything because (1 & 1) !== 0. PhysicsGroup defaults to mask 2147483647 (all bits). Call resetCollisionCategory() to set the mask to all bits after changing categories.
Configuration Reference
ArcadeWorldConfig -- passed via physics.arcade in GameConfig or SceneConfig:
| Property | Default | Description |
|---|
fps | 60 | Physics steps per second |
fixedStep | true | Use fixed timestep vs render sync |
timeScale | 1 | Simulation speed multiplier |
gravity | { x: 0, y: 0 } | World gravity in px/sec |
x, y | 0 | World bounds origin |
width, height | game size | World bounds dimensions |
checkCollision | all true | { up, down, left, right } edge collision |
overlapBias | 4 | Overlap threshold for separation |
tileBias | 16 | Tile overlap threshold |
forceX | false | Separate horizontally first |
isPaused | false | Start simulation paused |
debug | false | Enable debug rendering |
debugShowBody / debugShowStaticBody / debugShowVelocity | true | Debug display toggles |
debugBodyColor / debugStaticBodyColor / debugVelocityColor | 0xff00ff / 0x0000ff / 0x00ff00 | Debug colors |
maxEntries | 16 | RTree items per node |
useTree | true | Use RTree for dynamic bodies |
customUpdate | false | If true, call world.update() yourself |
Scene-level config overrides game-level config (merged).
Events
All events are emitted on this.physics.world:
| Event | String | Condition | Callback Args |
|---|
COLLIDE | 'collide' | Two bodies collide and at least one has onCollide = true | (gameObject1, gameObject2, body1, body2) |
OVERLAP | 'overlap' | Two bodies overlap and at least one has onOverlap = true | (gameObject1, gameObject2, body1, body2) |
WORLD_BOUNDS | 'worldbounds' | Body hits world edge and has onWorldBounds = true | (body, up, down, left, right) |
TILE_COLLIDE | 'tilecollide' | Body collides with a tile | (gameObject, tile, body) |
TILE_OVERLAP | 'tileoverlap' | Body overlaps a tile | (gameObject, tile, body) |
WORLD_STEP | 'worldstep' | After each physics step | (delta) |
PAUSE | 'pause' | World paused | none |
RESUME | 'resume' | World resumed | none |
Events require opt-in per body: set body.onCollide, body.onOverlap, or body.onWorldBounds to true.
API Quick Reference
Scene Plugin (this.physics)
| Method | Description |
|---|
this.physics.add.sprite(x, y, key, frame) | Create sprite with dynamic body |
this.physics.add.image(x, y, key, frame) | Create image with dynamic body |
this.physics.add.staticSprite(x, y, key, frame) | Sprite with static body |
this.physics.add.staticImage(x, y, key, frame) | Image with static body |
this.physics.add.group(config) | Dynamic physics group |
this.physics.add.staticGroup(config) | Static physics group |
this.physics.add.existing(go, isStatic?) | Add body to existing Game Object |
this.physics.add.body(x, y, w?, h?) | Standalone dynamic Body (no GO) |
this.physics.add.staticBody(x, y, w?, h?) | Standalone static Body (no GO) |
this.physics.add.collider(a, b, cb?, proc?, ctx?) | Persistent collider |
this.physics.add.overlap(a, b, cb?, proc?, ctx?) | Persistent overlap |
this.physics.collide(a, b, cb?, proc?, ctx?) | One-shot collision check |
this.physics.overlap(a, b, cb?, proc?, ctx?) | One-shot overlap check |
this.physics.nextCategory() | Next collision category bitmask |
this.physics.pause() / resume() | Pause/resume simulation |
this.physics.accelerateTo(go, x, y, spd?, maxX?, maxY?) | Accelerate toward point |
this.physics.moveTo(go, x, y, spd?, maxTime?) | Move toward point at speed |
this.physics.velocityFromAngle(angle, speed?, vec2?) | Angle (deg) to velocity |
this.physics.velocityFromRotation(rot, speed?, vec2?) | Rotation (rad) to velocity |
this.physics.closest(source, targets?) | Find nearest body |
this.physics.furthest(source, targets?) | Find farthest body |
this.physics.overlapRect(x, y, w, h, dyn?, static?) | Query bodies in rectangle |
this.physics.overlapCirc(x, y, r, dyn?, static?) | Query bodies in circle |
Also: accelerateToObject, moveToObject, collideTiles, overlapTiles, enableUpdate, disableUpdate.
World (this.physics.world)
| Method | Description |
|---|
setBounds(x, y, w, h, left?, right?, up?, down?) | Set boundary + edge checks |
setBoundsCollision(left?, right?, up?, down?) | Set which edges collide |
setFPS(framerate) | Change physics step rate |
enable(object, bodyType?) | Enable physics on object/group/array |
disable(object) | Disable physics on object/group/array |
add(body) / remove(body) | Add/remove Body from simulation |
addCollider / addOverlap | Create Collider (same args as factory) |
removeCollider(collider) | Remove Collider |
pause() / resume() | Pause/resume simulation |
createDebugGraphic() | Create debug rendering graphic |
Body Methods
| Method | Description |
|---|
setVelocity(x, y) / setVelocityX / setVelocityY | Set velocity |
setAcceleration(x, y) / setAccelerationX / Y | Set acceleration |
setMaxVelocity(x, y) / setMaxSpeed(speed) | Velocity caps |
setBounce(x, y) / setDrag(x, y) | Bounce and drag |
setDamping(bool) | Enable damping mode |
setFriction(x, y) / setGravity(x, y) | Friction and per-body gravity |
setMass(val) / setImmovable(bool) / setPushable(bool) | Mass and collision behavior |
setSize(w, h, center?) / setOffset(x, y) | Resize/offset body |
setCircle(radius, offX?, offY?) | Switch to circular body |
setCollideWorldBounds(val, bX?, bY?) | World bounds collision |
setBoundsRectangle(rect) | Custom bounds rectangle |
setAllowGravity / setAllowDrag / setAllowRotation | Toggle physics features |
setEnable(val) | Enable/disable body |
setCollisionCategory / setCollidesWith / addCollidesWith / removeCollidesWith | Category filtering |
resetCollisionCategory() | Reset to default (all) |
reset(x, y) / stop() | Reset position or zero velocity |
Gotchas
-
debug: true in production -- Debug rendering is expensive. Always disable for release builds.
-
Static body transform changes -- After changing position, scale, or origin of a static body's Game Object, you must call body.reset() or gameObject.refreshBody(). Static bodies do not auto-sync.
-
collide vs overlap -- collide performs separation (pushes bodies apart). overlap only detects intersection without moving anything. Use overlap for triggers like pickups.
-
Persistent Collider vs one-shot -- this.physics.add.collider() creates a Collider checked every frame automatically. this.physics.collide() is a one-shot check that must be called each frame in update(). Prefer persistent Colliders.
-
Events require opt-in -- World events ('collide', 'overlap', 'worldbounds') only fire if the relevant body property (onCollide, onOverlap, onWorldBounds) is set to true.
-
Collision categories default -- By default all bodies have category 0x0001 and mask 1 (PhysicsGroup defaults to mask 2147483647). Call this.physics.nextCategory() to get new category values; max 32 categories total. After changing categories, use resetCollisionCategory() to set the mask to all bits.
-
Group defaults only apply at creation -- PhysicsGroup defaults (like velocityX, bounceY) are applied when a member is added/created. Changing defaults later does not retroactively update existing members.
-
customUpdate config -- Setting customUpdate: true in the arcade config stops the world from auto-updating on the Scene UPDATE event. You must call this.physics.world.update(time, delta) manually.
-
useDamping drag values -- When useDamping is true, drag values should be small (e.g., 0.05) as they act as a multiplier. A value of 0.05 means the body keeps 5% of its velocity per second.
-
Immovable vs pushable -- immovable = true means the body is never moved by collisions at all. pushable = false means the body reflects velocity back to the colliding body but can still be separated.
-
Overlap with TilemapLayer -- When using overlapOnly with a TilemapLayer, every tile is checked regardless of collision settings on individual tiles.
-
RTree threshold -- The RTree spatial index (enabled by default) becomes expensive to rebuild with very large numbers of dynamic bodies. Consider setting useTree: false for 5000+ dynamic bodies.
Source File Map
| File | Purpose |
|---|
src/physics/arcade/ArcadePhysics.js | Scene plugin (this.physics) -- collide, overlap, moveTo, accelerateTo, nextCategory |
src/physics/arcade/World.js | Physics world -- bodies, gravity, bounds, colliders, step loop, setBounds, addCollider |
src/physics/arcade/Body.js | Dynamic body -- velocity, acceleration, bounce, drag, gravity, mass, immovable, pushable |
src/physics/arcade/StaticBody.js | Static body -- immovable, no velocity, optimized RTree lookup |
src/physics/arcade/Factory.js | this.physics.add -- sprite, image, group, staticGroup, body, existing, collider, overlap |
src/physics/arcade/Collider.js | Collider object -- persistent collision/overlap check with callbacks |
src/physics/arcade/PhysicsGroup.js | Dynamic physics group with per-member defaults |
src/physics/arcade/StaticPhysicsGroup.js | Static physics group with auto-refresh |
src/physics/arcade/components/index.js | Body component mixins -- Acceleration, Angular, Bounce, Collision, Debug, Drag, Enable, Friction, Gravity, Immovable, Mass, Pushable, Size, Velocity |
src/physics/arcade/components/Collision.js | Collision category/mask methods -- setCollisionCategory, setCollidesWith, addCollidesWith, removeCollidesWith |
src/physics/arcade/events/ | Event constants -- COLLIDE, OVERLAP, WORLD_BOUNDS, TILE_COLLIDE, TILE_OVERLAP, WORLD_STEP, PAUSE, RESUME |
src/physics/arcade/typedefs/ArcadeWorldConfig.js | TypeDef for arcade physics config options |