Your First PhaserJS Game

Game Physics

Learn to add gravity, collision detection, and physics interactions to enhance realism in your PhaserJS game.

In the last chapter, we designed the level, and now, it's time to bring some real-world logic into our game by adding gravity and collision detection. It's like giving the game characters a friendly reminder that they need to respect the laws of physics! ⚖️

Activating Physics (Let's Make Things Fall!) 🌍

Right now, our player is just chilling mid-air, defying gravity like a superhero. But we want them to fall down and stand on the ground, right? Let's add physics and gravity to make that happen!

First, we need to enable physics in our game. Open your config.js file and add the following lines in the config object to activate the arcade physics engine:

physics: {
    default: 'arcade',
    arcade: {
        gravity: { y: 600 },
        debug: false
    }
},

Explanation

  • default: 'arcade': We are using the arcade physics system for simplicity.
  • gravity: { y: 600 }: Gravity is pulling everything downwards at 600 units. 🌍
  • debug: false: If set to true, it will show visual guides for collisions and boundaries (great for testing).

Making the Player Obey Gravity 🏋️‍♂️

Now that we’ve enabled physics, let's update the player sprite to make it part of the physics world.

In the mainScene.js file, where we previously added the player sprite like this:

this.player = this.add.sprite(100, 700, "player_idle");

Replace that line with the following:

this.player = this.physics.add.sprite(100, 700, "player_idle").setDepth(2);

Explanation

  • this.physics.add.sprite: This turns our player sprite into a physics object, meaning gravity can now influence it.
  • setDepth(2): This ensures our player is rendered on top of other objects (like the ground), giving it visual priority. Think of it like layers in Photoshop!

Adding Some Bounce ⚡️

After adding the player, we want to make sure our character behaves a little more lifelike. Let’s add some bounce, so when the player falls on the ground, there's a slight "boing!" And we don’t want the player falling off the edge of the world, so we’ll set world boundaries as well.

After the player’s animation, add the following lines:

this.player.setBounce(0.2);
this.player.setCollideWorldBounds(true);

Explanation

  • setBounce(0.2): This gives the player a bounce effect when hitting a surface, like a rubber ball! 🏐
  • setCollideWorldBounds(true): This prevents the player from falling off the screen entirely. No one likes it when their character disappears into the void, right? 🌌

Try removing setCollideWorldBounds(true) just for fun, and watch your player free-fall into the abyss. 😜

Full Create Function 💻

So now, your create() function should look something like this:

create() {
    this.addBackground();
 
    this.player = this.physics.add.sprite(100, 700, "player_idle").setDepth(2);
    this.player.setScale(2)
    this.player.anims.play("player_idle_anim", true)
    this.player.setBounce(0.2);
    this.player.setCollideWorldBounds(true);
 
    this.addGrounds();
    this.addPlatforms();
    this.addCollectables();
    this.addTrampoline();
}

07

Collision Time ⚔️

You’ll notice that the player still passes right through the ground like a ghost 👻. That’s because we haven’t told the ground to interact with the player through physics.

Let’s fix that by giving the ground physics properties and enabling collision detection between the player and the ground.

Adding Physics to Grounds 🏞️

Instead of adding physics individually for each ground object, we’ll create a static group for our grounds. Static groups are non-movable, which is perfect for platforms and floors.

Update the addGrounds() function like this:

addGrounds() {
    this.grounds = this.physics.add.staticGroup();
    for(let i = 0; i < 18; i++) {
        const g = this.add.image(i * 46, this.game.config.height - 46, "ground").setOrigin(0, 0)
        this.grounds.add(g);
    }
 
    for(let i = 0; i < 8; i++) {
        const g = this.add.image(i * 46, 500, "ground").setOrigin(0, 0)
        this.grounds.add(g);
    }
 
    this.physics.add.collider(this.player, this.grounds);
}

Explanation

  • this.physics.add.staticGroup(): Creates a group of static objects (non-movable).
  • this.grounds.add(g): Adds each ground object to the physics group.
  • this.physics.add.collider(this.player, this.grounds): Enables collision between the player and the ground. Now, our player will stand on the ground instead of falling through it! 🎉

08

Debug Mode Fun 🔍

If you want to see the collision boxes around objects, change the debug: false to debug: true in the config.js file. This will highlight the areas where the objects collide, giving you a better idea of what's happening behind the scenes. 😎

09

Fixing Collision Boxes for the Player ⚙️

You might notice that the collision box around the player is a bit larger than the actual sprite. Let’s adjust it so the physics engine works with the actual player size.

Add the following lines after setting the player in the create function:

this.player.body.setSize(this.player.body.width - 10, this.player.body.height - 5);
this.player.body.setOffset(this.player.body.offset.x, this.player.body.offset.y + 2);

Explanation

  • setSize(): Shrinks the player's collision box slightly to better match the visual sprite. 🖌️
  • setOffset(): Moves the collision box to better align with the actual sprite.

Updated create() function

create() {
    this.addBackground();
 
    this.player = this.physics.add.sprite(100, 700, "player_idle").setDepth(2);
    this.player.setScale(2);
    this.player.anims.play("player_idle_anim", true);
    this.player.setBounce(0.2);
    this.player.setCollideWorldBounds(true);
    this.player.body.setSize(this.player.body.width - 10, this.player.body.height - 5);
    this.player.body.setOffset(this.player.body.offset.x, this.player.body.offset.y + 2);
 
    this.addGrounds();
    this.addPlatforms();
    this.addCollectables();
    this.addTrampoline();
}

10

Adding Colliders for Other Objects 💥

For other objects like platforms, collectables, and trampolines, we use similar physics and collider logic. Here’s the updated version of addPlatforms(), addCollectables(), and addTrampoline() functions with physics added:

Platforms 🪂

addPlatforms() {
    this.platforms = this.physics.add.staticGroup();
    const p1 = this.add.image(480, 580, "platform_brown").setScale(2.5);
    const p2 = this.add.image(630, 680, "platform_grey").setScale(2.5);
    const p3 = this.add.image(60, 400, "platform_grey").setScale(2.5);
 
    this.platforms.add(p1);
    this.platforms.add(p2);
    this.platforms.add(p3);
 
    p1.body.setSize(p1.body.width, p1.body.height - 7);
    p2.body.setSize(p2.body.width, p2.body.height - 7);
    p3.body.setSize(p3.body.width, p3.body.height - 7);
 
    this.physics.add.collider(this.player, this.platforms);
}

Previously, the platforms were simple sprites without any physics, which means the player could pass right through them, like walking on air! To fix this, we converted the platforms into static physics objects, which don't move but can collide with the player.

  • We added the platforms to a static group using this.physics.add.staticGroup(), which makes them part of the physics engine.
  • The player will now be able to land and stand on these platforms instead of falling through them.

To make the player collide with these platforms, we added a collider. Without this, the player would still fall right through the platforms even though they have physics.

Collectables 🍏

addCollectables() {
    this.collectables = this.physics.add.staticGroup();
    const apple1 = this.add.sprite(270, 180, "apple").setScale(2);
    apple1.anims.play("apple_anim", true);
    const apple2 = this.add.sprite(510, 180, "apple").setScale(2);
    apple2.anims.play("apple_anim", true);
    const apple3 = this.add.sprite(210, 470, "apple").setScale(2);
    apple3.anims.play("apple_anim", true);
 
    this.collectables.add(apple1);
    this.collectables.add(apple2);
    this.collectables.add(apple3);
 
    apple1.body.setSize(23, 23);
    apple2.body.setSize(23, 23);
    apple3.body.setSize(23, 23);
 
    this.physics.add.overlap(this.player, this.collectables);
}

Just like platforms, collectables were just sprites before, but now we've added them to a static physics group as well. This will make them interactive.

  • By adding the apples to a static group, they now have the potential to interact with other objects in the physics world, even though they won't move.

We added overlap instead of a collider because we want the player to "collect" the apples by touching them, not bounce off them like a wall! Overlap checks if two objects are touching each other and triggers an event without a physical collision.

  • Overlap allows the player to touch the apples and trigger an event, such as collecting them, without affecting the player's movement or making the apples act like solid objects. It's perfect for collectables! 🍎

Trampoline 🤸‍♂️

addTrampoline() {
    const trampoline = this.physics.add.staticSprite(765, 720, "trampoline_idle").setScale(2.5);
    trampoline.body.setSize(55, 30);
    trampoline.body.setOffset(trampoline.body.offset.x - 23, trampoline.body.offset.y);
 
    this.physics.add.collider(this.player, trampoline);
}

Before, the trampoline was just an idle sprite, but now we’ve made it a static physics object so the player can collide with it and potentially trigger a bounce effect.

  • This makes the trampoline part of the physics world, so it can interact with the player. You could later add more fun physics behaviors, like bouncing!

11


Recap 🔄

  • We've enabled physics and gravity to make the player move realistically.
  • We made the player interact with ground and platforms, added bounce, and ensured the player doesn't fall out of the world.
  • We've optimized collision boxes to ensure the player doesn't look like they're floating above the ground. ✨
  • Static Groups were added to make the platforms, collectables, and trampoline interact with the player via physics.
  • Colliders ensure the player lands on platforms, and overlap helps us handle non-collision-based interactions (like picking up collectables).

Now, let's move on to implementing some player controls in the next chapter. Your player is ready to jump, run, and collect goodies!


Complete Code 📜

If you missed anything or made some accidental changes, here’s the complete code for reference! 💻🔧


import Phaser from "phaser";
import GameScene from "./scenes/gameScene";
import LoadingScene from "./scenes/loadingScene";
import MainScene from "./scenes/mainScene";
 
const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 800,
    pixelArt: true,
    min: {
        width: 400,
        height: 400
    },
    scale: {
        mode: Phaser.Scale.FIT,
        autoCenter: Phaser.Scale.CENTER_BOTH
    },
    physics: {
        default: 'arcade',
        arcade: {
            gravity: { y: 600 },
            debug: true
        }
    },
    scene: [LoadingScene, MainScene, GameScene]
}
 
export default config;

Now you have everything! 🚀 Enjoy creating your awesome PhaserJS game!