Skip to content
Phaser Sprites & Physics — Arcade Physics Explained (Step-by-Step Guide)

Phaser Sprites & Physics — Arcade Physics Explained (Step-by-Step Guide)

DodaTech Updated Jun 6, 2026 14 min read

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 Image and Sprite — and when to use each
  • How Arcade physics handles velocity, gravity, bounce, and drag
  • When to use collider() vs overlap() 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.

Prerequisites: Complete the Phaser tutorial first. You should understand scene lifecycle (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.
FeatureImage (this.add.image)Sprite (this.physics.add.sprite)
Physics bodyNoYes
AnimationNoYes
Collision detectionManualAutomatic
Interactive eventsManual setupBuilt-in
Memory usageLowerHigher
Best forBackgrounds, UI, decorationsCharacters, 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

FeatureArcadeMatter.js
Body shapesRectangle onlyPolygons, circles, compound
RotationNot supported (AABB only)Full rotation support
PerformanceVery fastModerate
ComplexitySimple APIMore complex
Best forPlatformers, top-down, puzzlesPhysics 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.

Aspectcollide()overlap()
Physical responseYes (bounce/stop)No
PerformanceSlightly heavierLighter
Use caseWalls, floors, enemiesCoins, 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

  1. Applying velocity every frame without resetting — Velocity is cumulative. Setting player.setVelocityX(200) every frame in update() builds up speed indefinitely. Always zero it first: player.setVelocityX(0); if (key.right.isDown) player.setVelocityX(200);.

  2. Forgetting setCollideWorldBounds(true) — Without this, sprites fly off the canvas and never come back. Always set it for player characters.

  3. Using staticGroup.add() instead of staticGroup.create()create() sets up physics automatically. add() requires manual body initialization and often leads to objects that don’t collide.

  4. Using physics on Image objectsthis.add.image() creates a display-only object. Use this.physics.add.image() or add physics with this.physics.world.enable(image).

  5. Modifying body properties before adding to worldbody properties don’t exist until the sprite is added to the physics world. Configure after this.physics.add.sprite().

  6. Confusing callback argument order — In collider(a, b, fn), the callback receives (a, b) in that order. Always test with a console.log if you’re unsure.

Practice Questions

  1. What is the difference between collider() and overlap() in Arcade physics? collider() creates a physical response — objects bounce or stop on contact. overlap() only detects touching without any physics response. Use collider for walls and floors, overlap for collectibles and triggers.

  2. 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.

  3. 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).

  4. How do you make a sprite’s collision body smaller than its visual texture? Use sprite.body.setSize(width, height) to shrink the body and sprite.body.setOffset(x, y) to reposition it relative to the sprite origin.

  5. Challenge: Extend the Physics Sandbox below to add a “magnet” mode — when enabled, objects are attracted toward the mouse cursor. Use this.physics.add.overlap or manually apply velocity toward the pointer position.

FAQ

What is the difference between collider and overlap?
collider creates a physical response — objects bounce or stop. overlap only detects touching without any physics response. Use collider for walls and floors, overlap for collectibles and triggers.
How do I make a platform that moves?
Create a dynamic sprite, set immovable to true, and update its position each frame or use tweens. Immovable objects can move programmatically but won’t be pushed by collisions.
Why is my sprite falling through the floor?
The floor’s physics body may be too thin. Make it at least 10px tall, use collider instead of overlap, and verify with debug: true in physics config.
How do I rotate a physics body?
Arcade physics only supports axis-aligned rectangles. For rotated bodies, use Matter.js physics (physics.default: 'matter') which supports polygons and full rotation.
Can I have multiple physics engines?
Yes. Phaser supports Arcade and Matter simultaneously. Configure each in physics and specify which engine per sprite.
How do I disable physics on a sprite?
Set sprite.body.enable = false. The sprite remains visible but won’t collide or be affected by gravity.

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

TopicDescription
Phaser Scenes & StateMulti-scene games, transitions, and data persistence with the registry
Phaser Input & AudioGamepad support, touch input, audio sprites, and spatial sound
Phaser TilemapsLevel design with Tiled, tile layers, collision tiles, and procedural generation
Phaser Getting StartedReview the fundamentals if any physics concepts felt unfamiliar
JavaScript ClassesReview 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