From phaser4-gamedev
Configures Arcade Physics in Phaser 4: gravity, collisions, velocity, bounce, hitbox sizing, and static/dynamic body creation.
How this skill is triggered — by the user, by Claude, or both
Slash command
/phaser4-gamedev:phaser-physicsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Phaser 4 uses Arcade Physics — fast AABB (axis-aligned bounding box) simulation. No real curves or rotation-based collisions, but excellent for most 2D games.
Phaser 4 uses Arcade Physics — fast AABB (axis-aligned bounding box) simulation. No real curves or rotation-based collisions, but excellent for most 2D games.
In main.ts GameConfig:
const config: Phaser.Types.Core.GameConfig = {
// ...
physics: {
default: 'arcade',
arcade: {
gravity: { x: 0, y: 300 }, // world gravity (adjust per genre)
debug: true, // set false for production
},
},
};
Gravity values by genre:
{ x: 0, y: 500 } to { x: 0, y: 800 }{ x: 0, y: 0 } (no gravity){ x: 0, y: 0 } (no gravity){ x: 0, y: 600 } with high bounce// Dynamic body — affected by gravity, velocity, collisions
const player = this.physics.add.sprite(x, y, 'player');
const enemy = this.physics.add.sprite(x, y, 'enemy');
const coin = this.physics.add.sprite(x, y, 'coin');
// Static body — never moves, others bounce off it
const ground = this.physics.add.staticSprite(x, y, 'platform');
const wall = this.physics.add.staticImage(x, y, 'wall');
// Static group — collection of immovable objects
const platforms = this.physics.add.staticGroup();
platforms.create(400, 568, 'ground');
platforms.create(100, 400, 'platform');
platforms.create(600, 300, 'platform');
// NOTE: After moving a static body, call staticGroup.refresh() to update physics
// Dynamic group with pooling
const enemies = this.physics.add.group({
classType: Enemy,
runChildUpdate: true,
});
const body = player.body as Phaser.Physics.Arcade.Body;
// Velocity — pixels per second
player.setVelocity(vx, vy);
player.setVelocityX(200);
player.setVelocityY(-400); // negative = up
// Acceleration — adds to velocity each second
body.setAccelerationX(100);
body.setAccelerationY(0);
// Maximum velocity cap
body.setMaxVelocity(300, 600); // max x and y
body.setMaxVelocityX(300);
// Deceleration (friction when no input)
body.setDrag(100, 0); // x drag, y drag
body.setDragX(200); // horizontal friction only
// Bounce (0 = no bounce, 1 = perfect bounce)
player.setBounce(0.2);
player.setBounceX(0);
player.setBounceY(0.4);
// Hitbox size and offset (often smaller than visual sprite)
body.setSize(24, 40); // hitbox width, height
body.setOffset(4, 8); // offset from top-left of sprite
// World boundary collision
player.setCollideWorldBounds(true);
// Note: world bounds default to canvas size; expand for big levels:
this.physics.world.setBounds(0, 0, worldWidth, worldHeight);
// Gravity override for specific body
body.setGravityY(-300); // counteract world gravity
body.setAllowGravity(false); // disable gravity for this body (float)
// Immovable (static-like but in a dynamic group)
body.setImmovable(true); // won't move when hit, but still detects
// Collider — objects physically collide (physics response)
this.physics.add.collider(player, platforms);
this.physics.add.collider(player, enemies, this.hitEnemy, undefined, this);
this.physics.add.collider(enemies, platforms);
// Overlap — trigger callback without physics bounce
this.physics.add.overlap(player, coins, this.collectCoin, undefined, this);
this.physics.add.overlap(player, powerups, this.collectPowerup, undefined, this);
// Collider/Overlap callback signature
private hitEnemy(
playerObj: Phaser.Types.Physics.Arcade.GameObjectWithBody,
enemyObj: Phaser.Types.Physics.Arcade.GameObjectWithBody
): void {
const player = playerObj as Phaser.Physics.Arcade.Sprite;
const enemy = enemyObj as Phaser.Physics.Arcade.Sprite;
this.playerHit(player, enemy);
}
// Conditional collider (process callback — return true to proceed)
this.physics.add.collider(
player,
onewayPlatforms,
undefined, // no callback
(playerObj, platformObj) => {
// Only collide if player is falling down through platform
const body = (playerObj as Phaser.Physics.Arcade.Sprite)
.body as Phaser.Physics.Arcade.Body;
return body.velocity.y > 0;
},
this
);
// GameConfig
physics: { default: 'arcade', arcade: { gravity: { y: 500 }, debug: true } }
// Player setup
this.player = this.physics.add.sprite(100, 400, 'player');
this.player.setBounce(0.1);
this.player.setCollideWorldBounds(true);
(this.player.body as Phaser.Physics.Arcade.Body)
.setSize(24, 40).setOffset(4, 8);
// In update():
const body = this.player.body as Phaser.Physics.Arcade.Body;
if (this.cursors.left.isDown) {
this.player.setVelocityX(-200);
this.player.flipX = true;
} else if (this.cursors.right.isDown) {
this.player.setVelocityX(200);
this.player.flipX = false;
} else {
// Decelerate
this.player.setVelocityX(this.player.body.velocity.x * 0.85);
}
// Jump — only when on the ground
if (this.cursors.up.isDown && body.blocked.down) {
this.player.setVelocityY(-550);
}
// Variable jump height — release early for lower jump
if (!this.cursors.up.isDown && body.velocity.y < -200) {
this.player.setVelocityY(this.player.body.velocity.y * 0.85);
}
// Coyote time (jump grace period after walking off ledge)
// Track lastGroundedTime and allow jump within 100ms window
// GameConfig — no gravity
physics: { default: 'arcade', arcade: { gravity: { y: 0 }, debug: true } }
// Player setup
this.player = this.physics.add.sprite(400, 300, 'player');
this.player.setCollideWorldBounds(true);
(this.player.body as Phaser.Physics.Arcade.Body).setDrag(1000, 1000);
// In update():
const speed = 200;
let vx = 0;
let vy = 0;
if (this.cursors.left.isDown) vx = -speed;
if (this.cursors.right.isDown) vx = speed;
if (this.cursors.up.isDown) vy = -speed;
if (this.cursors.down.isDown) vy = speed;
// Normalize diagonal movement
if (vx !== 0 && vy !== 0) {
vx *= 0.707;
vy *= 0.707;
}
this.player.setVelocity(vx, vy);
// GameConfig — no gravity
physics: { default: 'arcade', arcade: { gravity: { y: 0 } } }
// Velocity-based movement (ship feels floaty)
this.ship = this.physics.add.sprite(400, 500, 'ship');
this.ship.setMaxVelocity(300, 300);
(this.ship.body as Phaser.Physics.Arcade.Body).setDrag(300, 300);
// 8-directional movement with velocity
if (this.cursors.up.isDown) this.ship.body.velocity.y -= 400 * delta / 1000;
if (this.cursors.down.isDown) this.ship.body.velocity.y += 400 * delta / 1000;
// Angle-based bullet firing
private fireBullet(): void {
const bullet = this.bullets.get() as Phaser.Physics.Arcade.Sprite;
if (!bullet) return;
bullet.setActive(true).setVisible(true).setPosition(this.ship.x, this.ship.y - 20);
this.physics.velocityFromAngle(this.ship.angle - 90, 500, bullet.body.velocity);
}
// Define bullet class
export class Bullet extends Phaser.Physics.Arcade.Sprite {
constructor(scene: Phaser.Scene, x: number, y: number) {
super(scene, x, y, 'bullet');
}
fire(x: number, y: number, velocityY: number = -500): void {
this.setActive(true).setVisible(true).setPosition(x, y);
this.setVelocityY(velocityY);
}
preUpdate(time: number, delta: number): void {
super.preUpdate(time, delta);
// Recycle off-screen
if (this.y < -50 || this.y > this.scene.scale.height + 50) {
this.setActive(false).setVisible(false);
}
}
}
// In GameScene.create():
this.bullets = this.physics.add.group({
classType: Bullet,
maxSize: 30,
runChildUpdate: true,
});
this.physics.add.overlap(this.bullets, this.enemies, this.bulletHitEnemy, undefined, this);
// To shoot:
const b = this.bullets.get(this.player.x, this.player.y) as Bullet;
if (b) b.fire(this.player.x, this.player.y);
// Move body toward a point at speed
this.physics.moveTo(enemy, target.x, target.y, 150); // 150 px/s
// Velocity from angle (angle in degrees, 0=right, 90=down)
this.physics.velocityFromAngle(angle, speed, body.velocity);
// Velocity from rotation (radians)
this.physics.velocityFromRotation(rotation, speed, body.velocity);
// Distance between two game objects
const dist = Phaser.Math.Distance.Between(a.x, a.y, b.x, b.y);
// Angle from one object to another (degrees)
const angle = Phaser.Math.Angle.Between(a.x, a.y, b.x, b.y) * (180 / Math.PI);
Timers created with this.time.addEvent() for recurring AI patterns, ability cooldowns, or boss phase transitions accumulate and never self-clean across scene restarts. Store every timer reference and remove them in shutdown() (or the equivalent entity cleanup).
// In your entity class:
private activeTimers: Phaser.Time.TimerEvent[] = [];
startBossPhase(): void {
const t = this.scene.time.addEvent({
delay: 3000,
loop: true,
callback: this.fireVolley,
callbackScope: this,
});
this.activeTimers.push(t);
}
destroy(fromScene?: boolean): void {
for (const t of this.activeTimers) t.remove(false);
this.activeTimers = [];
super.destroy(fromScene);
}
Rule: Every time.addEvent call in an entity must have a corresponding t.remove() in destroy() or shutdown(). Anonymous fire-and-forget timers on looping entities are the fastest path to a timer leak.
Physics groups created for weapon evolutions, temporary spawn waves, or ability effects must be explicitly destroyed when the context ends. Stopping the parent scene does NOT automatically destroy child groups that were created dynamically.
// When creating a group for a weapon or phase effect:
const projectileGroup = this.physics.add.group({ classType: MyProjectile, maxSize: 20 });
// When that weapon is replaced or the phase ends:
projectileGroup.clear(true, true); // destroyChild=true, removeFromScene=true
projectileGroup.destroy();
Call clear(true, true) before destroy(). Without it, the children remain in the scene's display list as orphaned objects.
Enable during development, disable for release:
// In GameConfig:
arcade: { gravity: { y: 300 }, debug: true }
// Shows: body outlines (green=normal, red=blocked), velocity arrows, sleep states
// Toggle at runtime:
(this.physics.world as Phaser.Physics.Arcade.World).drawDebug = false;
references/arcade-physics-api.md — Complete Arcade Body property/method reference, physics world config, advanced patterns (knockback, conveyor belts, moving platforms)npx claudepluginhub yakoub-ai/phaser4-gamedev --plugin phaser4-gamedevAdds Arcade Physics movement, gravity, velocity, and collision/overlap resolution to Phaser 3 games. Use for platformers, top-down controllers, or any scene with this.physics.
Creates and configures sprites, images, text, graphics, and physics-enabled objects in Phaser 4 scenes. Covers `this.add.*` and `this.physics.add.*` usage.
Builds 2D browser games with Phaser 3 using scene-based architecture, TypeScript, Vite bundling, and centralized state. Use when creating new 2D games, adding game features, or working with Phaser sprites.