Phaser Sprites & Physics — Arcade Physics Explained (Step-by-Step Guide)
Phaser Sprites & Physics — Arcade Physics Explained (Step-by-Step Guide)
Master Arcade Physics in Phaser: understand sprites vs images, configure velocity and gravity, use collide vs overlap, manage physics groups, and build an interactive physics sandbox.
What You’ll Learn
- The difference between
ImageandSprite— and when to use each - How Arcade physics handles velocity, gravity, bounce, and drag
- When to use
collider()vsoverlap()for collision detection - How physics groups make managing many objects efficient
- Building a playable physics sandbox with sliders and drag-and-drop
Phaser games feel alive because of physics. Without it, you’d manually calculate every collision, bounce, and gravity effect. Arcade physics does the heavy lifting so you can focus on gameplay.
Why Sprites & Physics Matter
Think of a game without physics: characters walk through walls, coins hang in mid-air, and nothing ever falls. Physics is what makes a game world feel real.
In DodaTech’s security training simulators, Arcade physics powers collision zones that detect when a trainee enters a restricted area, triggers alerts on proximity to “infected” systems, and simulates physical access controls. The same collider and overlap concepts you learn here are used in production security training tools built by the team behind Doda Browser and Durga Antivirus Pro.
preload, create, update) and how to load assets. Basic JavaScript classes and objects assumed.Learning Path
flowchart LR
A[Getting Started] --> B[Sprites & Physics]
B --> C[Scenes & State]
C --> D[Input & Audio]
D --> E[Tilemaps & Level Design]
B -->|You are here| B
Sprite vs Image — The Paper Cutout Analogy
Imagine you’re making a stop-motion animation with paper cutouts:
- An Image is like a printed photo — it stays exactly as it is. You can place it on the board and move it, but it won’t fall, bounce, or collide with anything.
- A Sprite is like a cutout with magnets attached — it can stick to other things, fall when dropped, and animate through multiple poses.
| Feature | Image (this.add.image) | Sprite (this.physics.add.sprite) |
|---|---|---|
| Physics body | No | Yes |
| Animation | No | Yes |
| Collision detection | Manual | Automatic |
| Interactive events | Manual setup | Built-in |
| Memory usage | Lower | Higher |
| Best for | Backgrounds, UI, decorations | Characters, enemies, interactive objects |
// Image — static, no physics
const bg = this.add.image(400, 300, 'background');
// Sprite — physics-ready, can collide and animate
const player = this.physics.add.sprite(100, 400, 'player');When to use which? Use Image for anything that doesn’t need physics — backgrounds, decorative elements, UI panels. Use Sprite for any object that moves, collides, or animates.
Arcade Physics: What It Is and How It Works
Arcade is Phaser’s lightweight physics engine. It’s called “Arcade” because it handles the kind of physics found in classic arcade games — simple rectangular collisions, gravity, and velocity. It’s not a full physics simulator like Matter.js (which handles polygons, joints, and rotational forces). Arcade is simpler, faster, and perfect for platformers, top-down games, and puzzles.
const config = {
physics: {
default: 'arcade',
arcade: {
gravity: { y: 300 }, // 300 pixels/second² downward
debug: false // Set true to see body outlines
}
}
};Why set gravity.y in config? This applies gravity to ALL dynamic bodies in the game. You can override it per-sprite, but the global value sets the default. In most platformers, y: 300 to y: 800 feels natural. DodaTech’s security simulators use a lower gravity for top-down perspective training modules.
Arcade vs Matter.js: When to Use What
| Feature | Arcade | Matter.js |
|---|---|---|
| Body shapes | Rectangle only | Polygons, circles, compound |
| Rotation | Not supported (AABB only) | Full rotation support |
| Performance | Very fast | Moderate |
| Complexity | Simple API | More complex |
| Best for | Platformers, top-down, puzzles | Physics puzzles, ragdolls, realistic sims |
Stick with Arcade unless you need rotated bodies or complex physics interactions.
Physics Properties — The Invisible Forces
These are the invisible forces that control how sprites move and interact:
Gravity
Global gravity applies to all dynamic bodies. Override per-sprite:
this.player.body.setGravityY(500); // This sprite falls faster
How gravity works: Phaser adds gravity * delta to the sprite’s velocity every frame. So if gravity.y = 300, the sprite gains 300 pixels/second of downward velocity each second. This is why objects accelerate as they fall.
Velocity
Velocity is speed in a direction. Think of it as “pixels per second”:
this.player.setVelocityX(200); // Move right at 200 px/s
this.player.setVelocityY(-300); // Move up at 300 px/s (jump)
Why velocity instead of setting x directly? Velocity integrates with physics — gravity modifies it, collisions stop it, bounce reflects it. If you set player.x += 5 in update(), you bypass the physics system entirely.
Bounce
Bounce controls how much velocity is retained after a collision. A value of 0 means no bounce (like a rock hitting the ground). A value of 1 means full bounce (like a super-ball):
this.player.setBounce(0.6); // 60% velocity retained
this.player.body.setBounce(0.4, 0.8); // Different x and y bounce
Drag
Drag applies deceleration when no input is active. Higher values stop the sprite faster:
this.player.body.setDrag(500, 0); // Horizontal drag only
this.player.setDamping(true); // Percentage-based (vs. linear)
Damping vs linear drag: Linear drag subtracts a fixed amount each frame. Damping multiplies velocity by a factor each frame, which feels more natural for most games.
Acceleration
Instead of instant velocity changes, acceleration ramps up speed gradually:
this.player.body.setAcceleration(600, 0); // Speed builds over time
Use acceleration for vehicle controls, ice levels, or any movement that should feel slippery.
Physics Body Types
Arcade has three body types, each with a specific job:
Dynamic Body (Default)
Affected by gravity, collisions, and forces. Moves freely. This is what you want for most game objects:
const box = this.physics.add.sprite(400, 300, 'box');
// body.type === Phaser.Physics.Arcade.DYNAMIC_BODY
Static Body
Does not move and is not affected by gravity. Perfect for platforms, walls, and floors:
const ground = this.physics.add.staticImage(400, 580, 'ground');
// Static bodies don't need update() calls — they just sit there
Why use static bodies? Performance. Static bodies don’t need velocity calculations, gravity checks, or position updates. They just check collisions against dynamic bodies.
Immovable Body (Kinematic)
A dynamic body that doesn’t respond to collisions — it won’t be pushed. Use for moving platforms or enemies that shouldn’t be pushed by the player:
enemy.body.setImmovable(true);
enemy.body.allowGravity = false;Unlike static bodies, immovable bodies can still move programmatically (via setVelocity or tweens). They just ignore collision forces.
Physics Groups — Herding Cats
Imagine you have 50 coins in your game. Without groups, you’d need to write collision code for each one. With groups, you handle them all at once:
// Static group — for platforms, walls (never move)
this.platforms = this.physics.add.staticGroup();
// Dynamic group — for enemies, collectibles (can move)
this.enemies = this.physics.add.group();
// Add objects to groups
this.platforms.create(400, 580, 'ground');
this.enemies.create(200, 300, 'enemy');Why groups matter: A single this.physics.add.collider(player, this.platforms) checks collision against every member of the group. Without groups, you’d write 50 separate collision checks.
Group configuration shortcuts:
this.coins = this.physics.add.group({
key: 'coin',
repeat: 10, // Create 10 coins
setXY: { x: 50, y: 100, stepX: 70 } // Space them out
});Collisions vs Overlap — Bumping vs Touching
This is one of the most important distinctions in Phaser physics:
collide() — Objects Bounce
Use collider when objects should physically interact — walls stop the player, platforms support jumping, enemies push each other:
this.physics.add.collider(this.player, this.platforms);
this.physics.add.collider(this.enemies, this.platforms);overlap() — Objects Touch Without Bouncing
Use overlap when you only need to detect contact without a physical response. Perfect for collectibles, triggers, and checkpoints:
this.physics.add.overlap(this.player, this.coins, this.collectCoin, null, this);The mental model: collider = two magnets repelling. overlap = a motion sensor detecting presence.
| Aspect | collide() | overlap() |
|---|---|---|
| Physical response | Yes (bounce/stop) | No |
| Performance | Slightly heavier | Lighter |
| Use case | Walls, floors, enemies | Coins, power-ups, triggers |
Collider Callbacks
Both collider and overlap accept an optional callback:
collectCoin(player, coin) {
coin.destroy(); // Remove coin from game
this.score += 10;
}
// Optional processCallback — return false to skip collision
this.physics.add.overlap(
this.player, this.enemies,
this.onHitEnemy,
(player, enemy) => player.alpha > 0.5, // Only collide if visible
this
);Callback argument order: The callback receives (objectA, objectB) in the same order you passed them to collider/overlap. So this.physics.add.collider(player, enemy, callback) calls callback(player, enemy).
Body Size and Shape
By default, the physics body matches the texture size. This often feels unfair — players get hit when the sprite barely touches an enemy. Adjust the body for tighter collision:
// Shrink body — player feels "fair" hits
this.player.body.setSize(20, 44); // width, height
this.player.body.setOffset(6, 4); // offset from sprite origin
// Circle body (for circular sprites like balls)
this.player.body.setCircle(16); // radius
this.player.body.setOffset(0, 0);
// Debug: see all body outlines
this.physics.world.createDebugGraphic(); // Shows green outlines
Why shrink the body? Players perceive unfair hits as bad game design. A body slightly smaller than the visual sprite gives “benefit of the doubt” collision — they barely graze an enemy and survive.
You Might Be Wondering…
“Why is my sprite falling through the floor?” Either the floor’s body is too thin (make it at least 10px tall), or you’re using overlap instead of collider. Collide pushes objects apart; overlap just notifies you.
“Can I have different gravity for different objects?” Yes! Set global gravity in config, then override per-sprite with sprite.body.setGravityY(value).
“Do groups auto-clean when objects are destroyed?” No. Destroying a sprite removes it visually, but the group still tracks it. Use group.killAndHide() or manually remove dead objects.
Common Mistakes
Applying velocity every frame without resetting — Velocity is cumulative. Setting
player.setVelocityX(200)every frame inupdate()builds up speed indefinitely. Always zero it first:player.setVelocityX(0); if (key.right.isDown) player.setVelocityX(200);.Forgetting
setCollideWorldBounds(true)— Without this, sprites fly off the canvas and never come back. Always set it for player characters.Using
staticGroup.add()instead ofstaticGroup.create()—create()sets up physics automatically.add()requires manual body initialization and often leads to objects that don’t collide.Using physics on
Imageobjects —this.add.image()creates a display-only object. Usethis.physics.add.image()or add physics withthis.physics.world.enable(image).Modifying
bodyproperties before adding to world —bodyproperties don’t exist until the sprite is added to the physics world. Configure afterthis.physics.add.sprite().Confusing callback argument order — In
collider(a, b, fn), the callback receives(a, b)in that order. Always test with aconsole.logif you’re unsure.
Practice Questions
What is the difference between
collider()andoverlap()in Arcade physics?collider()creates a physical response — objects bounce or stop on contact.overlap()only detects touching without any physics response. Usecolliderfor walls and floors,overlapfor collectibles and triggers.Why should you reset velocity to zero each frame before applying input? Because velocity is cumulative. Without resetting, holding a key multiplies velocity each frame, causing exponential acceleration instead of constant speed.
What are the three physics body types in Arcade and when would you use each? Dynamic (affected by gravity/collisions — player, enemies), Static (unmoving — floors, walls), Immovable (moves programmatically but ignores collision forces — moving platforms, sliding doors).
How do you make a sprite’s collision body smaller than its visual texture? Use
sprite.body.setSize(width, height)to shrink the body andsprite.body.setOffset(x, y)to reposition it relative to the sprite origin.Challenge: Extend the Physics Sandbox below to add a “magnet” mode — when enabled, objects are attracted toward the mouse cursor. Use
this.physics.add.overlapor manually apply velocity toward the pointer position.
FAQ
Try It Yourself
This interactive physics sandbox lets you spawn falling objects, adjust gravity, toggle bounce, apply drag, and watch collisions in real time:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Physics Sandbox</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: #0f0f23;
font-family: monospace;
}
#game-container { position: relative; }
#controls {
position: absolute;
top: 10px; left: 10px;
z-index: 10;
background: rgba(0,0,0,0.75);
padding: 12px;
border-radius: 8px;
color: #fff;
font-size: 14px;
display: flex;
flex-direction: column;
gap: 8px;
min-width: 180px;
}
#controls label {
display: flex;
justify-content: space-between;
align-items: center;
}
#controls input[type="range"] { width: 100px; }
#controls button {
background: #ff6b35; border: none; color: #fff;
padding: 6px 12px; border-radius: 4px; cursor: pointer;
font-family: monospace; margin-top: 4px;
}
#controls button:hover { background: #e55a2b; }
.value { color: #ffd166; min-width: 30px; text-align: right; }
</style>
</head>
<body>
<div id="game-container">
<div id="controls">
<label>Gravity <span class="value" id="gravity-val">300</span><input type="range" id="gravity-slider" min="0" max="800" value="300"></label>
<label>Bounce <span class="value" id="bounce-val">0.5</span><input type="range" id="bounce-slider" min="0" max="100" value="50"></label>
<label>Drag <span class="value" id="drag-val">0</span><input type="range" id="drag-slider" min="0" max="500" value="0"></label>
<button id="spawn-btn">Spawn Object</button>
<button id="clear-btn">Clear All</button>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/phaser@3.80.1/dist/phaser.min.js"></script>
<script>
class SandboxScene extends Phaser.Scene {
constructor() { super({ key: 'SandboxScene' }); }
preload() { this.createPlaceholderAssets(); }
createPlaceholderAssets() {
const boxC = this.textures.createCanvas('box', 40, 40);
const bCtx = boxC.context;
bCtx.fillStyle = '#ff6b35'; bCtx.fillRect(0, 0, 40, 40);
bCtx.strokeStyle = '#e55a2b'; bCtx.lineWidth = 2; bCtx.strokeRect(1, 1, 38, 38);
boxC.refresh();
const ballC = this.textures.createCanvas('ball', 36, 36);
const blCtx = ballC.context;
blCtx.fillStyle = '#4ecdc4'; blCtx.beginPath(); blCtx.arc(18, 18, 16, 0, Math.PI * 2); blCtx.fill();
blCtx.strokeStyle = '#3bb8b0'; blCtx.lineWidth = 2; blCtx.beginPath(); blCtx.arc(18, 18, 16, 0, Math.PI * 2); blCtx.stroke();
ballC.refresh();
const gndC = this.textures.createCanvas('ground', 800, 16);
const gCtx = gndC.context;
gCtx.fillStyle = '#3a7d32'; gCtx.fillRect(0, 0, 800, 16);
gCtx.fillStyle = '#2d5a27'; gCtx.fillRect(0, 0, 800, 3);
gndC.refresh();
const wallC = this.textures.createCanvas('wall', 16, 600);
const wCtx = wallC.context;
wCtx.fillStyle = '#5a4a3a'; wCtx.fillRect(0, 0, 16, 600);
wallC.refresh();
}
create() {
this.physics.world.setBounds(0, 0, 800, 600);
this.ground = this.physics.add.staticImage(400, 592, 'ground');
this.wallLeft = this.physics.add.staticImage(8, 300, 'wall');
this.wallRight = this.physics.add.staticImage(792, 300, 'wall');
this.objects = this.physics.add.group();
this.physics.add.collider(this.objects, this.ground);
this.physics.add.collider(this.objects, this.wallLeft);
this.physics.add.collider(this.objects, this.wallRight);
this.physics.add.collider(this.objects, this.objects);
this.setupUI();
this.add.text(400, 24, 'Physics Sandbox — Adjust sliders and click "Spawn Object"', {
fontSize: '14px', fill: '#ffffff', fontFamily: 'monospace',
stroke: '#000', strokeThickness: 3
}).setOrigin(0.5);
}
setupUI() {
const gravitySlider = document.getElementById('gravity-slider');
const bounceSlider = document.getElementById('bounce-slider');
const dragSlider = document.getElementById('drag-slider');
const spawnBtn = document.getElementById('spawn-btn');
const clearBtn = document.getElementById('clear-btn');
gravitySlider.addEventListener('input', () => {
document.getElementById('gravity-val').textContent = gravitySlider.value;
this.physics.world.gravity.y = parseInt(gravitySlider.value);
});
bounceSlider.addEventListener('input', () => {
const val = parseFloat(bounceSlider.value) / 100;
document.getElementById('bounce-val').textContent = val.toFixed(2);
this.objects.children.each(obj => obj.body.setBounce(val));
});
dragSlider.addEventListener('input', () => {
const val = parseInt(dragSlider.value);
document.getElementById('drag-val').textContent = val;
this.objects.children.each(obj => obj.body.setDrag(val));
});
spawnBtn.addEventListener('click', () => this.spawnObject());
clearBtn.addEventListener('click', () => this.objects.clear(true, true));
}
spawnObject() {
const isBall = Math.random() > 0.5;
const x = Phaser.Math.Between(50, 750);
const y = Phaser.Math.Between(-200, -50);
const obj = this.physics.add.sprite(x, y, isBall ? 'ball' : 'box');
obj.body.setBounce(parseFloat(document.getElementById('bounce-slider').value) / 100);
obj.body.setDrag(parseInt(document.getElementById('drag-slider').value));
obj.body.setCollideWorldBounds(true);
obj.setInteractive({ draggable: true });
this.objects.add(obj);
this.input.setDraggable(obj);
obj.on('dragstart', () => { obj.body.moves = false; });
obj.on('drag', (pointer, dragX, dragY) => { obj.x = dragX; obj.y = dragY; });
obj.on('dragend', () => { obj.body.moves = true; obj.body.setVelocity(0, 0); });
}
}
const config = {
type: Phaser.AUTO, width: 800, height: 600,
backgroundColor: '#1a1a2e', parent: 'game-container',
physics: { default: 'arcade', arcade: { gravity: { y: 300 }, debug: false } },
scene: [SandboxScene]
};
new Phaser.Game(config);
</script>
</body>
</html>This sandbox demonstrates: dynamic physics groups, static bodies (walls/ground), object-object collision, gravity/bounce/drag controls via HTML UI, procedural texture generation, and drag-and-drop interaction.
What’s Next
| Topic | Description |
|---|---|
| Phaser Scenes & State | Multi-scene games, transitions, and data persistence with the registry |
| Phaser Input & Audio | Gamepad support, touch input, audio sprites, and spatial sound |
| Phaser Tilemaps | Level design with Tiled, tile layers, collision tiles, and procedural generation |
| Phaser Getting Started | Review the fundamentals if any physics concepts felt unfamiliar |
| JavaScript Classes | Review ES6 class syntax used throughout Phaser scenes |
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Physics-based collision detection is used in DodaTech security training simulators to monitor zone access and trigger proximity alerts.
What’s Next
Congratulations on completing this Phaser Sprites Physics tutorial! Here’s where to go from here:
- Practice daily — Consistency is more important than long study sessions
- Build a project — Apply what you learned by building something real
- Explore related topics — Check out other tutorials in the same category
- Join the community — Discuss with other learners and share your progress
Remember: every expert was once a beginner. Keep coding!
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro