Building a Game with Phaser
Tags: ionic, vue, game
Published: 2023-09-15 21:49:23

---
title: Building a Game with Phaser
published: true
description: If you're a web developer interested in building mobile games, here's how to use open source tools to get started! This is part two, focused on creating the gameplay with Phaser.
tags: ionic, vue, game
series: Building a Mobile Game with Phaser and Ionic Vue
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tajpynd0rxefhltdm9qb.png
# Use a ratio of 100:42 for best results.
# published_at: 2023-09-14 19:09 +0000
---

Welcome to Part Two of this four-part series on building a mobile game using open source technologies. We'll be using Phaser, along with [Ionic](https://ionicframework.com/), [Capacitor](https://capacitorjs.com/), and [Vue](https://v3.vuejs.org/).

> ❓**Are you interested in a video walkthrough version of this blog post?** Let me know in the comments! If there's enough interest I can put one together.

The [source code for this tutorial is here](https://github.com/ceceliacreates/phaser-tutorial), and I'll reference specific commits throughout so you can see the specific changes for each section.

This post is heavily influenced by the [tutorial from the official Phaser docs](https://phaser.io/tutorials/making-your-first-phaser-3-game/part1) (we'll even use the assets they provide as well). I recommend going through the tutorial if you want to learn more about Phaser concepts. We'll keep it pretty high level here for brevity.

In this post, we'll actually walk through making the Phaser game! It's a big section and there are a lot of new concepts to learn, so let's jump right in!

## Table of Contents

- [Creating Scenes](#creating-scenes)
- [Adding Game Objects](#adding-game-objects)
  - [Detour: Adjusting Game Window](#detour-adjusting-game-window)
  - [Preloading Assets](#preloading-assets)
  - [Displaying Images](#displaying-images)
  - [Animating Game Objects](#animating-game-objects)
- [Responding to Player Input](#responding-to-player-input)
  - [Setting Player Physics](#setting-player-physics)
  - [Adding Event Handlers](#adding-event-handlers)
  - [Handling movement in update method](#handling-movement-in-update-method)
- [Dynamically Generating Game Objects](#dynamically-generating-game-objects)
- [Adding Collision & Overlap Handlers](#adding-collision--overlap-handlers)
  - [Adding Colliders](#adding-colliders)
  - [Adding Overlaps](#adding-overlaps)
- [What's Next](#whats-next)

## Creating Scenes

We talked about scenes briefly last week. Scenes are a core concept in Phaser and other game dev frameworks. You can think of Scenes like Views in a web app, but with some additional flexibility. Your player will navigate between Scenes, and sometimes multiple Scenes can run simultaneously.

Right now we have a single MainScene in our app.

In your `game` directory, create a `PlayScene.js` and `ScoreScene.js` file. We'll work with the `PlayScene.js` file first, as this will contain the majority of our game.

Add the following code to your Play scene:

```js
// src/game/PlayScene.js

import { Scene } from "phaser";

export class PlayScene extends Scene {
    constructor () {
      super({ key: 'PlayScene' })
    }
    create () {
        this.add.text(100, 100, "PlayScene", {
            font: "24px Courier",
            fill: "#ffffff",
        });    
    }
  }
```

Then, in your `game.js` file, remove the `MainScene` class we defined in the last blog post. We'll need to update our import statement and config to include the new `PlayScene` instead.

```js
// src/game/game.js

import { Game, AUTO, Scale } from "phaser";
import { PlayScene } from "./PlayScene.js";

export function launch() {
    return new Game({
      type: AUTO,
      scale: {
        mode: Scale.RESIZE,
        width: window.innerWidth * window.devicePixelRatio,
        autoCenter: Scale.CENTER_BOTH,
        height: window.innerHeight * window.devicePixelRatio,
      },
      parent: "game",
      backgroundColor: "#201726",
      physics: {
        default: "arcade",
      },
      scene: PlayScene,
    });
  }
```

It's not much, but now we have separate files for our scenes, which will make it easier to work with them.

> [Here is the git commit](https://github.com/ceceliacreates/phaser-tutorial/commit/571e34a43f8895b251f53ceaa433745264585027) with the changes for this section.

## Adding Game Objects

When working with Phaser, almost everything we interact with is a Game Object. Game Objects can:

- Have Physics applied
- Be images, text, sprites, etc.
- Be Static or Dynamic
- Be categorized into Groups
- And much, much more.

For our game, we'll be working with the following objects:

- A [player](https://github.com/ceceliacreates/phaser-tutorial/blob/main/public/assets/player.png) sprite that moves left and right with animation
- A [platform](https://github.com/ceceliacreates/phaser-tutorial/blob/main/public/assets/platform.png) static object that our player walks on
- [Left](https://github.com/ceceliacreates/phaser-tutorial/blob/main/public/assets/leftarrow.png) and [right](https://github.com/ceceliacreates/phaser-tutorial/blob/main/public/assets/rightarrow.png) arrows for input to move our player
- A [star](https://github.com/ceceliacreates/phaser-tutorial/blob/main/public/assets/star.png) object that falls from the top of the screen and increases the score on collision with the player
- A [bomb](https://github.com/ceceliacreates/phaser-tutorial/blob/main/public/assets/bomb.png) object that falls from the top of the screen and ends the game on collision with the player

Within the `public` directory in the root of your project, create a new `assets` folder. Save the above linked images in this folder with the following file names:

- `player.png`
- `platform.png`
- `leftarrow.png`
- `rightarrow.png`
- `star.png`
- `bomb.png`

### Detour: Adjusting Game Window

Before we move onto adding Game Objects, we need to make a small fix to our app that I discovered while writing. Right now, we have a collapsible header on iPhone. This could affect our game window size. Update the Play, About, and Scores pages in your `/src/views` directory to **remove** `:fullscreen=true` from the `<ion-content>` component, and remove the entire `<ion-header>` component and children that is inside `<ion-content>`. Your updated Page template should look like this:

```js
// src/views/PlayPage.vue

<template>
  <ion-page>
    <ion-header>
      <ion-toolbar>
        <ion-title>Play</ion-title>
      </ion-toolbar>
    </ion-header>
    <ion-content >
        <PhaserContainer />
    </ion-content>
  </ion-page>
</template>
```

Now we can move on! 

> [Here is the git commit](https://github.com/ceceliacreates/phaser-tutorial/commit/312b2d0d74d7e22d1dd48d61e40c9159854286a9) with the changes for this section.

### Preloading Assets

Right now, inside our `PlayScene` class, we have a `constructor` and a `create ()` method. There are two other lifecycle methods provided by Phaser: `preload ()` and `update ()`.

The `preload ()` method is used to preload assets that will be used in our game.

Update your `PlayScene.js` file to preload the assets we just saved. Go ahead and also remove the text in the `create ()` method.

```js
// src/game/PlayScene.js

import { Scene } from "phaser";

export class PlayScene extends Scene {
    constructor () {
      super({ key: 'PlayScene' })
    }

    preload ()
{
    this.load.image('star', 'assets/star.png');
    this.load.image('bomb', 'assets/bomb.png');
    this.load.image('platform', 'assets/platform.png');
    this.load.image('leftArrow', 'assets/leftarrow.png');
    this.load.image('rightArrow', 'assets/rightarrow.png');
    this.load.spritesheet('player', 
        'assets/player.png',
        { frameWidth: 32, frameHeight: 48 }
    );
}
  
    create () {   
    }
  }

```
The star, bomb, platform, and arrow images are added with `this.load.image()`, where we pass a unique key and the path for the image file. 

The player is loaded using what's called a sprite sheet. A sprite sheet contains multiple poses of our player in the same image. This is helpful for animating our player. We use `this.load.spritesheet()`, again passing a key and path, but also setting the `frameWidth` and `frameHeight` of each individual pose (or "frame") in our image.

### Displaying Images

Our asset are preloaded, but nothing is showing on the screen! We need to create game objects using our image assets.

Phaser uses pixels for all positioning and scaling. Many examples simply provide hard-coded values, which can work fine on browsers in a desktop device, but not for mobile.

Because of this, we need to do some math to make our displayed images responsive to the user's device screen.

> **NOTE** I'd recommend playing around with these values based on the device screen. In [a more complex example here](https://github.com/ceceliacreates/ionitron-game/blob/main/src/game/scenes/PlayScene.js#L79), I am scaling everything and adding logic checks rather than hard-coding values. However, for simplicity, I'm hard-coding for an iPhone 12 Pro in this tutorial.

Phaser provides `this.scale.width` and `this.scale.height` that returns the current screen width and height values. We can use these to calculate the center of our screen and where we should place objects.

If we think about our game layout, we'll need to calculate the height of a game play area, as well as the height of a controls area. This will help us determine where on the Y axis we need to place our platform, player, and arrow controls.

![Mockup of game with designated areas](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j61azzxnnjnk7gghd6av.png)

> **NOTE:** We need to calculate the game size **within** our `create ()` method and not in our `gameState` because the game needs to be initiated first to access the screen size values.

Add the following code to your `create ()` method.

```js
//game/PlayScene.js
...
create () {
    // sets game values based on screen size
    this.screenWidth = this.scale.width;
    this.screenHeight = this.scale.height;
    this.screenCenterX = this.screenWidth / 2;
    this.controlsAreaHeight = this.screenHeight * 0.2;
    this.gameAreaHeight = this.screenHeight - this.controlsAreaHeight;

    // adds the player, platform, and controls
    this.platform = this.physics.add.staticImage(0, this.gameAreaHeight, 'platform').setOrigin(0, 0).refreshBody();
    this.player = this.physics.add.sprite(this.screenCenterX, this.gameAreaHeight - 24, 'player');
    this.leftArrow = this.add.image(this.screenWidth * 0.1, this.gameAreaHeight + 40, 'leftArrow').setOrigin(0, 0).setInteractive()
    this.rightArrow = this.add.image(this.screenWidth * 0.7, this.gameAreaHeight + 40, 'rightArrow').setOrigin(0, 0).setInteractive()
}
```

In the first section, we've calculated the center of the screen. We've also set a `controlsAreaHeight` to take up about 20% of the bottom of the screen, and the `gameAreaHeight` to be the difference.

To create a game object, we'll use the relevant `add` method, passing an X coordinate, Y coordinate, and image key.

Let's break these down individually.

```js
  this.platform = this.physics.add.staticImage(0, this.gameAreaHeight, 'platform').setOrigin(0, 0).refreshBody();
```

We've added the platform as a static image, which gives it physics properties so other game objects can interact with the platform. One thing to note here is `setOrigin()`. By default Phaser positions images based on the **center** of the image. By changing the origin to `(0, 0)`, I am telling Phaser to position starting from the bottom left corner instead. Whenever we change the position or scale of a static body, we need to tell Phaser to adjust for this change. That is what `refreshBody()` is doing here.

```js
 this.player = this.physics.add.sprite(this.screenCenterX, this.gameAreaHeight - 24, 'player');
```

For the player, I'm adding a sprite. For the Y coordinate, I'm doing some math and placing the player at the `gameAreaHeight`, but **subtracting half the player height**. This is because, again, Phaser positions from the **center** of the image. This results in the player standing nicely on top of the platform. 

If I wanted to, I could use `setOrigin()` to position from the bottom instead, but I wanted to demonstrate how you would position an item by default in Phaser as well.

```js
this.leftArrow = this.add.image(this.screenWidth * 0.1, this.gameAreaHeight + 40, 'leftArrow').setOrigin(0, 0).setInteractive()
this.rightArrow = this.add.image(this.screenWidth * 0.7, this.gameAreaHeight + 40, 'rightArrow').setOrigin(0, 0).setInteractive()
```

For the arrows, I'm sticking with `setOrigin(0, 0)` because it's easier for me to position based on the lower left corner with the math I'm doing. I'm positioning the arrows 40 pixels (half the size of the arrows) below the `gameAreaHeight`, the left arrow 10% of the way from the left and the right arrow 70% of the way from the left of the screen edge.

I'm using `setInteractive()` so that we can assign touch/click handlers to the arrows.

### Animating Game Objects

If you save what we have so far, you see our player standing on the platform and facing the left. This is because Phaser is using the first frame of our spritesheet by default.

![Screenshot of app so far](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nu6ditc6jr1dxs3bchdr.png)

Let's set up some animations so when our character moves, we can leverage all the poses in the spritesheet. This code comes mostly from [the official Phaser tutorial](https://phaser.io/tutorials/making-your-first-phaser-3-game/part5), so I won't dig into too much. I did add a logic check so it doesn't create a new animation if one already exists when the game restarts.

But you can see we are defining an animation key, providing an image key, stating what frames to loop through, and establishing a loop for each animation. The "turn" animation is the player simply facing forward.

```js
//game/PlayScene.js

create () {
...
     // adds animations for player
     if (!this.anims.exists('left')) {
        this.anims.create({
          key: "left",
          frames: this.anims.generateFrameNumbers('player', { start: 0, end: 3 }),
          frameRate: 10,
          repeat: -1,
        });
      }
  
      if (!this.anims.exists('turn')) {
        this.anims.create({
          key: "turn",
          frames: [{ key: 'player', frame: 4 }],
        });
      }
  
      if (!this.anims.exists('right')) {
        this.anims.create({
          key: "right",
          frames: this.anims.generateFrameNumbers('player', { start: 5, end: 8 }),
          frameRate: 10,
          repeat: -1,
        });
      }
}
```

Our player is still facing left because we'll leverage these animations once our player starts moving in the next section.

> [Here is the git commit](https://github.com/ceceliacreates/phaser-tutorial/commit/cbcf862523ef5f261dd42e4dadc37851dcc481cf) with the changes for this section.

## Responding to Player Input

In order for our player to animate, he needs to move! Let's add some event handlers for our arrow buttons next.

First, we need to make sure our player is ready for the physics of moving and interacting with our game world.

### Setting Player Physics

Add the following code inside your `create ()` method, underneath the animations we just added.

```js
// src/game/PlayScene.js

// sets player physics
this.player.body.setGravityY(300);
this.player.setCollideWorldBounds(true);
  
// adds collider between player and platforms
this.physics.add.collider(this.player, this.platform);
```

For gravity, we could set a default game-level gravity. However, we want our player to move differently than our stars and bombs, so we're setting it on the object directly instead. `setColliderWorldBounds()` to `true` means our player cannot go off screen, and adding a collider between the player and platform means the player will stay on top of the platform as he moves.

### Adding Event Handlers

Now we'll add event handlers for `pointerdown` and `pointerup` events. These will translate to touch events once we're on a mobile device.

Still in our `create ()` method, add the following:

```js
// src/game/PlayScene.js

// event handlers for arrow input
this.moveLeft = false;
this.moveRight = false;
  
this.leftArrow.on('pointerdown', () => {
this.moveLeft = true;
});

this.leftArrow.on('pointerup', () => {
this.moveLeft = false;
});
  
this.rightArrow.on('pointerdown', () => { 
this.moveRight = true;
});

this.rightArrow.on('pointerup', () => {
this.moveRight = false;
});
```
We are using two variables, `moveLeft` and `moveRight`, to track whether our player is in motion based on what arrows are being pressed.

However, we are not actually telling the player to move yet. Where do we do that? In our `update ()` method.

### Handling movement in update method

So far we have primarily been working in our `create ()` method, which is executed when the game is initialized. 

In comparison, the `update ()` method runs every frame of the game. This is where we can control actions that need to *update* once the game has already started.

Inside our `update ()` method, add the following:

```js
// src/game/PlayScene.js

update () {
  if (this.moveLeft && !this.moveRight) {
    this.player.setVelocityX(0 - 200);   
    this.player.anims.play('left', true);
  }
  
  else if (this.moveRight && !this.moveLeft) {
     this.player.setVelocityX(200);    
     this.player.anims.play('right', true);
  }
  
  else {
    this.player.setVelocityX(0);
    this.player.anims.play('turn');
  }
}
```

Here, when `moveLeft` is set to true by the `pointerdown` event handler, we set the player velocity to move left, and play the `left` animation. We handle the `moveRight` boolean change the same way.

By default, our player is not moving in either direction, with the `turn` animation playing, which results in the player facing forward.

If you save now, you can try it out for yourself!

> [Here is the git commit](https://github.com/ceceliacreates/phaser-tutorial/commit/06fca36d02182f918cd6e92e2ccccf1680bfc947) with the changes for this section.

## Dynamically Generating Game Objects

Our player can now move in the game world, but it's pretty boring. Let's give him something to do by generating stars for him to collect and bombs for him to avoid.

Add the following code to the end of your `create ()` method:

```js
// src/game/PlayScene.js
...
create () {
...
// Adds generated stars

this.stars = this.physics.add.group({
  gravityY: 300,
});

const createStar = () => {
  const x = Math.random() * this.screenWidth;
  const star = this.stars.create(x, 0, 'star');
}

const createStarLoop = this.time.addEvent({
  // random number between 1 and 1.2 seconds
  delay: Math.floor(Math.random() * (1200 - 1000 + 1)) + 1000,
  callback: createStar,
  callbackScope: this,
  loop: true,
});
}
```

Let's break down what's happening in each block.

First, we are creating a physics group called `stars`. This is helpful when we have a category of game objects and want the same physics applied to each individual game object in that group. In this case, we are applying a `gravityY` of 300.

Next, we are writing a function called `createStar` that creates a new `star` object within our `stars` group, placing it at a random X coordinate and the 0 Y coordinate (top of the screen) using the key for our star image.

Finally, we are using `this.time.addEvent` provided by Phaser to create a loop. This Phaser method works like `setTimeout`, in that you provide a delay in MS and a callback function. We are also referencing our `this` object for the `callbackScope` and setting `loop` to true so it repeats.

Once you save and refresh, you'll see stars falling from the sky at random X positions and at random intervals between 1 and 1.2 seconds.

We'll repeat the process for our bombs. However, we want our bombs to drop much faster but also appear less frequently.

Add the following code to `create ()`:

```js
 // src/game/PlayScene.js
...
create () {
...
// Adds generated bombs

this.bombs = this.physics.add.group({
  gravityY: 900,
});

const createBomb = () => {
  const x = Math.random() * this.screenWidth;
  const bomb = this.bombs.create(x, 0, 'bomb');
  bomb.setScale(2).refreshBody();
}

const createBombLoop = this.time.addEvent({
  // random number between 4.5 and 5 seconds
  delay: Math.floor(Math.random() * (5000 - 4500 + 1)) + 4500,
  callback: createBomb,
  callbackScope: this,
  loop: true,
});
```
Now we have bombs that drop every 4.5-5 seconds at a high speed. I also scaled up the bombs with `setScale(2).refreshBody()` to double the original image size so they are easier to see as they fall.

![Screenshot of stars and bombs falling](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qjkp46m521pcvtcz7xtn.png)

You should now see stars and bombs falling down on your player!

> [Here is the git commit](https://github.com/ceceliacreates/phaser-tutorial/commit/7c7f8fd3c16c4af335849b18f65df54204d5e58e) with the changes for this section.

## Adding Collision & Overlap Handlers

We are almost done! All we have to do now is handle what happens when our player interacts with the stars and bombs.

## Adding Colliders

You may remember that we added a collider earlier between the player and platform.

```js
this.physics.add.collider(this.player, this.platform);
```

When we add a collider in Phaser without a callback, the default behavior is for the two objects to simply block or push against each other upon collision, without passing through each other.

We can set a collider with a callback function to complete additional steps when two items collide.

For example, our stars and bombs should not go past the platform. Let's add a collider for these and destroy the star or bomb when it collides with the platform.

```js
 // src/game/PlayScene.js
...
create () {
...
// Adds colliders between stars and bombs with platform

this.physics.add.collider(this.stars, this.platform, function(object1, object2) {
  const star = (object1.key === 'star') ? object1 : object2;
  star.destroy();
});

this.physics.add.collider(this.bombs, this.platform, function(object1, object2) {
  const bomb = (object1.key === 'bomb') ? object1 : object2;
  bomb.destroy();
});
```

This code will trigger the callback function whenever the two objects collide, determine which object is the object we went to destroy, then call the Phaser-provided `destroy()` method on that object. Our stars and bombs should now disappear when they collide with the platform.

### Adding Overlaps

Finally, we need to add overlap handlers that:

- increase the game score when our player overlaps with a star
- ends the game when the player overlaps with a bomb

An overlap is different than a collider in that it only *checks* if two objects overlap, rather than *preventing* them from colliding. We'll use overlap for the interactions between our player with stars and bombs.

Add the following, again to your `create ()` method:

```js
// src/game/PlayScene.js
...
create () {
...

// Adds overlap between player and stars

this.score = 0;
this.scoreText = this.add.text(this.screenCenterX, this.gameAreaHeight + 16, 'Score: 0', { fontSize: '16px', fill: '#000' }).setOrigin(0.5, 0.5);

this.physics.add.overlap(this.player, this.stars, function(object1, object2) {
  const star = (object1.key === 'player') ? object1 : object2;
  star.destroy();
  this.score += 10;
  this.scoreText.setText('Score: ' + this.score);
}, null, this);
```

In the first block, we are creating a starting score of 0 and displaying that text on the screen over the platform. This should look familiar to how we created other game objects, passing an X and Y value and then the text to `this.add.text()`. We can also set CSS values in an object passed to the fourth parameter.

Then, we are creating an overlap between the player and the stars group with a callback function that does four things:
- Checks which overlap object is the star
- Destroys the star
- Increases the score by 10 points
- Resets the score text with `this.scoreText.setText()`

We need to pass the additional `null` and `this` parameters to `this.physics.add.overlap()` so that we have access to our `this` object inside the callback function.

We can repeat this for our bombs.

```js
// src/game/PlayScene.js
...
create () {
...
// Adds overlap between player and bombs

this.physics.add.overlap(this.player, this.bombs, function(object1, object2) {
  const bomb = (object1.key === 'player') ? object1 : object2;
  bomb.destroy();
  
  createStarLoop.destroy();
  createBombLoop.destroy();
  this.physics.pause();

  this.gameOverText = this.add.text(this.screenCenterX, this.screenHeight / 2, 'Game Over', { fontSize: '32px', fill: 'red' }).setOrigin(0.5, 0.5);

  this.input.on('pointerup', () => {
    this.score = 0;
    this.scene.restart();
  })
}, null, this);
```

This is similar to our star overlap in that we are checking which object is the bomb, then destroying it. However, after that, we have functionality that handles the end of the game.

```js
createStarLoop.destroy();
createBombLoop.destroy();
this.physics.pause();
```

These lines essentially stop our game world by destroy the loops that create new stars and bombs, as well as pausing all the physics taking place in the game. 

Then, we add "Game Over" text to the middle of the screen.

Finally, we are adding a `pointerup` event handler that lets the user restart the game. This resets the score to 0 and restarts the game on click/tap. It's important that this event handler is **inside** your overlap callback so it only fires after the player interacts with a bomb.

![Screenshot of Game Over screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0n86thjq3x3ugbxv5i3v.png)

Congratulations, you now have a game!

> [Here is the git commit](https://github.com/ceceliacreates/phaser-tutorial/commit/635f073b073838f2761bc05de94021bbd49ba06d) with the changes for this section.

## What's Next

Next week, we'll create our Score scene, talk about transitioning scenes, as well as how to interact between our Phaser game and the Ionic Vue app by exporting scores.

Stay tuned!