physics-arcade
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() {
// Physics sprite (dynamic body, affected by gravity)
this.player = this.physics.add.sprite(100, 300, 'player');
this.player.setCollideWorldBounds(true);
this.player.setBounce(0.2);
// Static group (immovable platforms)
this.platforms = this.physics.add.staticGroup();
this.platforms.create(400, 568, 'ground').setScale(2).refreshBody();
// Register a persistent collider (checked every frame automatically)
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);
}
}
}
// Enable Arcade Physics in game config
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 --
Vector2applied to all dynamic bodies each step. Set via config orthis.physics.world.gravity.y = 300. - bounds --
Rectangledefining the world boundary. Defaults to game canvas size. - bodies --
Setof all dynamicBodyinstances. - staticBodies --
Setof allStaticBodyinstances. - colliders --
ProcessQueueof registeredColliderobjects (fromthis.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;falsesyncs 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 configdebug: 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; iftrue, never moved by collisions - pushable --
true; iffalse, reflects velocity to colliding body - moves --
true; iffalse, position/rotation not updated by physics - enable --
true;falseremoves from simulation - useDamping --
false; whentrue, drag is a multiplier (0-1) instead of linear - collideWorldBounds --
false; onWorldBounds/onCollide/onOverlap --false, settrueto 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(), orthis.physics.add.staticGroup(). - After changing the parent Game Object's position or scale, call
body.reset()orgameObject.refreshBody()to sync. - Has
collisionCategoryandcollisionMasklike dynamic bodies. - Does not have velocity, acceleration, drag, or gravity properties.
Common Patterns
Enabling Physics on Existing Game Objects
// Add a physics body to any existing Game Object
this.physics.add.existing(mySprite); // dynamic body
this.physics.add.existing(mySprite, true); // static body
// Or enable via the world directly
this.physics.world.enable(mySprite); // dynamic
this.physics.world.enable(mySprite, Phaser.Physics.Arcade.STATIC_BODY);
Creating Physics Sprites and Images
// Via the physics factory (this.physics.add)
const player = this.physics.add.sprite(100, 200, 'player'); // dynamic body
const coin = this.physics.add.image(300, 100, 'coin'); // dynamic body
const wall = this.physics.add.staticImage(400, 300, 'wall'); // static body
const platform = this.physics.add.staticSprite(400, 500, 'plat'); // static body
// Standalone bodies (no Game Object)
const sensor = this.physics.add.body(200, 200, 32, 32); // dynamic Body
const zone = this.physics.add.staticBody(100, 100, 64, 64); // static Body
Velocity and Gravity
// Direct velocity
player.setVelocity(200, -300); // x=200 px/s, y=-300 px/s (upward)
player.setVelocityX(200);
player.body.velocity.set(200, -300); // equivalent via Vector2
// Acceleration + max velocity
player.setAcceleration(100, 0);
player.setMaxVelocity(300, 600);
// Per-body gravity (added to world gravity)
player.body.gravity.y = 200;
player.body.allowGravity = false; // exempt from world gravity
// Drag (applied only when acceleration is zero)
player.setDrag(300); // linear deceleration
player.body.useDamping = true;
player.setDrag(0.05); // damping mode: keeps 5% velocity/sec
// Bounce
player.setBounce(0.5); // both axes
player.setBounceY(1); // full vertical bounce
Collide and Overlap
Two approaches: persistent Colliders (checked every frame automatically) or one-shot checks (called in update()).
// --- Persistent Colliders (preferred) ---
// Created via this.physics.add.collider / this.physics.add.overlap
// Automatically checked every physics step
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); // disable physics and hide
}
// Collider management
collider.active = false; // temporarily disable
collider.destroy(); // remove permanently
// With processCallback (must return boolean to allow collision)
this.physics.add.collider(player, enemies, onHit, canCollide, this);
function canCollide (player, enemy) {
return !player.getData('invincible');
}
// --- One-shot checks (in update) ---
// Called manually each frame; no Collider object created
this.physics.collide(player, platforms);
this.physics.overlap(player, coins, collectCoin, null, this);
// Self-collision within a single group
this.physics.add.collider(enemies, enemies);
Groups
// Dynamic physics group -- members get dynamic bodies automatically
const bullets = this.physics.add.group({
classType: Phaser.Physics.Arcade.Sprite, // default: ArcadeSprite
maxSize: 20,
collideWorldBounds: true,
bounceX: 1,
bounceY: 1,
velocityX: 200,
velocityY: 0,
allowGravity: false,
immovable: false
});
// Static physics group -- members get static bodies
const platforms = this.physics.add.staticGroup();
platforms.create(400, 568, 'ground');
platforms.create(600, 400, 'ground');
// After modifying a static group member's transform, refresh:
platforms.create(400, 568, 'ground').setScale(2).refreshBody();
// Group velocity helpers
bullets.setVelocity(200, 0); // all members
bullets.setVelocityX(200);
bullets.setVelocityY(0, 10); // with step increment per member
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
// Set bounds size and which edges collide (left, right, up, down)
this.physics.world.setBounds(0, 0, 1600, 1200, true, true, false, true);
this.physics.world.setBoundsCollision(true, true, false, true); // edges only
// Per-body world bounds
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);
// Detect world bounds hit via event (requires opt-in)
player.body.onWorldBounds = true;
this.physics.world.on('worldbounds', (body, up, down, left, right) => {
console.log('Hit edge:', { up, down, left, right });
});
Body Properties
// Size and shape
player.body.setSize(24, 32, true); // width, height, re-center on GO
player.body.setOffset(4, 0); // offset from GO position
player.body.setCircle(16); // circular body, radius 16
player.body.setCircle(16, 4, 4); // circular with offset
// Collision behavior
player.body.setImmovable(true); // not moved by collisions
player.body.setPushable(false); // reflects velocity to collider
player.body.slideFactor.set(0, 0); // Sokoban-style: stops after push
player.body.setMass(2); // affects momentum exchange
// Enable / disable
player.disableBody(true, true); // disable body + hide Game Object
player.enableBody(true, x, y, true, true); // re-enable at position + show
// Per-direction collision check
player.body.checkCollision.up = false; // don't collide from above
player.body.syncBounds = true; // auto-sync size to texture
Collision Categories
Collision categories let you filter which bodies can collide with each other using bitmask values. Maximum 32 categories.
// Get unique category values from the physics plugin
const CAT_PLAYER = this.physics.nextCategory(); // 0x0002
const CAT_ENEMY = this.physics.nextCategory(); // 0x0004
const CAT_BULLET = this.physics.nextCategory(); // 0x0008
// Assign categories to bodies or groups
player.setCollisionCategory(CAT_PLAYER);
enemy.setCollisionCategory(CAT_ENEMY);
bullet.setCollisionCategory(CAT_BULLET);
// Set what each body collides with
player.setCollidesWith([CAT_ENEMY]); // player hits enemies only
enemy.setCollidesWith([CAT_PLAYER, CAT_BULLET]); // enemies hit player + bullets
bullet.setCollidesWith([CAT_ENEMY]); // bullets hit enemies only
// Add/remove individual categories from existing mask
player.addCollidesWith(CAT_BULLET);
player.removeCollidesWith(CAT_ENEMY);
// Check if a body will collide with a category
player.willCollideWith(CAT_ENEMY); // returns boolean
// Reset to default (collides with everything)
player.resetCollisionCategory();
// Works on Groups too
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: truein 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()orgameObject.refreshBody(). Static bodies do not auto-sync. -
collidevsoverlap--collideperforms separation (pushes bodies apart).overlaponly detects intersection without moving anything. Useoverlapfor 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 inupdate(). Prefer persistent Colliders. -
Events require opt-in -- World events (
'collide','overlap','worldbounds') only fire if the relevant body property (onCollide,onOverlap,onWorldBounds) is set totrue. -
Collision categories default -- By default all bodies have category
0x0001and mask1(PhysicsGroup defaults to mask2147483647). Callthis.physics.nextCategory()to get new category values; max 32 categories total. After changing categories, useresetCollisionCategory()to set the mask to all bits. -
Group defaults only apply at creation -- PhysicsGroup
defaults(likevelocityX,bounceY) are applied when a member is added/created. Changingdefaultslater does not retroactively update existing members. -
customUpdateconfig -- SettingcustomUpdate: truein the arcade config stops the world from auto-updating on the Scene UPDATE event. You must callthis.physics.world.update(time, delta)manually. -
useDampingdrag values -- WhenuseDampingistrue, drag values should be small (e.g.,0.05) as they act as a multiplier. A value of0.05means the body keeps 5% of its velocity per second. -
Immovable vs pushable --
immovable = truemeans the body is never moved by collisions at all.pushable = falsemeans the body reflects velocity back to the colliding body but can still be separated. -
Overlap with TilemapLayer -- When using
overlapOnlywith 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: falsefor 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 |