Working with Scenes and Data in Phaser
Tags: phaser, ionic, vue, gamedev
Published: 2024-05-05 02:10:02

It’s been a long time since I returned to this Phaser tutorial, and for that I apologize! [A job role change](OutSystems blog post) caused this to fall through the cracks, but I wanted to make sure I wrapped things up for those following along.

As a disclaimer, some things have changed in the months since [Part 2](https://dev.to/ceceliacreates/building-a-mobile-game-with-phaser-and-ionic-vue-building-a-phaser-game-2f74), including [a major update of Capacitor to version 6](https://capacitorjs.com/docs/next/updating/6-0), as well as some cool things happening with the [new Phaser CLI.](https://phaser.io/news/2024/04/phaser-create-game) I’ll be digging more into these in future posts now that I’m back up and running. However, for the purposes of finishing this series, I’ll stay with the setup started in [Part 1](https://dev.to/ceceliacreates/building-a-mobile-game-with-phaser-and-ionic-vue-part-one-3e0o).

Now, enough time has passed already, so let’s get to it!

In this post in the series, we’ll create our `ScoreScene`  and set up the other views in our Vue app: About and Scores. This will involve using events to access the scores from the Phaser game in the parent Vue app to create a “High Scores” page.

## Table of Contents
- [Creating the Score Scene](#creating-the-score-scene)
- [Switching Scenes](#switching-scenes)
- [Passing Data Between Scenes](#passing-data-between-scenes)
- [Passing Score Data from Phaser to Vue](#passing-score-data-from-phaser-to-vue)
- [What’s Next](#whats-next)

## Creating the Score Scene

This scene will display the player’s final score and display text letting them know they can tap to restart.  We already created a `ScoreScene.js` file within our `game` directory in Part 2, and this is the file we’ll be using.

Right now, the file is empty. We’ll start by scaffolding out the basic structure of a Scene file.

```js

import { Scene } from "phaser";

export class ScoreScene extends Scene {
    constructor () {
      super({ key: 'ScoreScene' })
    }
    create () {
         
    }
  }
```

Then, we’ll set some values based on the screen size, like we did in the Play Scene. We’ll set the `screenWidth`, `screenHeight`, and the horizontal center of the screen with `screenCenterX`.

This will allow us to display text in the correct location on the screen. We’ll add “Game Over” text to the center like we had in our Play Scene using the `gameOverText` variable, as well as some animated text indicating the player should tap to restart with the `restartText` variable.

For the animation, we’ll use a tween that targets the `restartText` and repeatedly eases the text to 1.5 times larger and back to its original size on a loop. This will create a pulsing animation effect.

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

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

        // adds Tap to Restart text underneath Game Over text
        this.restartText = this.add.text(this.screenCenterX, this.screenHeight / 3 + 200, 'Tap to restart', {fontSize: '15px', fill: '#ffffff'}).setOrigin(0.5, 0.5);

        // adds pulsing animation to restart text

        this.tweens.add({
            targets: this.restartText,
            scaleX: 1.5, // Scale it to 150% of its original size
            scaleY: 1.5,
            duration: 1000, // Duration of one pulse
            ease: 'Sine.easeInOut', // Smooth easing
            yoyo: true, // Reverse the tween on completion, creating the "pulse" effect
            repeat: -1 // Repeat forever
        })

    }
```

We’ve created our scene, but it’s not connected to the rest of our game. Next, we’ll learn how to switch between our Play Scene and Score Scene in Phaser.

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

## Switching Scenes

Phaser has a Scene Manager that contains built-in methods for managing the scenes in your game, including starting, stopping, switching, sleeping, and more. You can view the documentation here to see a full list of the available methods. For our game, we want to do the following:

- Stop the Play Scene when the game is over, and start the Score Scene
- Stop the Score Scene when the player taps to restart, and start the Play Scene

First, let’s update our Play Scene. Right now, the logic to restart the game is inside the overlap we declared for when our player hits a bomb (which ends the game). It looks like this:

```js
// 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);
```

Instead of displaying the “Game Over” text and directly restarting in the same Scene, we’ll instead stop the Play Scene and start the Score Scene. Your overlap should now look like this:

```js
this.physics.add.overlap(this.player, this.bombs, function(object1, object2) {

    // Destroys bomb that triggered overlap
    const bomb = (object1.key === 'player') ? object1 : object2;
    bomb.destroy();

    // Stops game object generation, pauses physics, and resets score to zero
    createStarLoop.destroy();
    createBombLoop.destroy();
    this.physics.pause();
    this.score = 0;

    // stops Play Scene and starts Score Scene

    this.scene.stop('PlayScene')
    this.scene.start('ScoreScene');
    
  }, null, this);
```

However, our `game.js` file does not currently import the `ScoreScene`, so Phaser does not know about it. Update your `game.js` with an import statement for `ScoreScene`, and change the `scene` property in the `launch` function and pass an array with both scenes instead.

```js
import { Game, AUTO, Scale } from "phaser";
import { PlayScene } from "./PlayScene.js";
import { ScoreScene } from "./ScoreScene.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, ScoreScene]
    });
  }
```

If you save your changes and run the game, when your player is hit by a bomb, the scene will change to the Score scene! Let’s add similar logic to the Score scene to start a new game.

Put the following code inside the `create()` block in `ScoreScene`:

```js
// Add pointerdown handler to restart game
this.input.once('pointerdown', () => {
		this.scene.stop('ScoreScene')
		this.scene.start('PlayScene')
});
```

We can now seamlessly switch between scenes to end and start a new game!

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

## Passing Data Between Scenes

Right now, our Score Scene isn’t very exciting. Let’s make it more fun by displaying the final score, along with a rank for that score to let the player know how they did. To do this, we’ll need to pass the score from the `PlayScene` to the `ScoreScene`.

In Phaser, we can pass an object as the second parameter to the `start()` method to pass data to the new scene. Update the `start` method in the overlap of the `PlayScene` to add the score data.

**Note:** You’ll also need to REMOVE the line that reset the score to zero, otherwise you’ll just pass zero every time! Because we are now stopping the scene, the score will automatically be reset to zero when a new `PlayScene` is started again.

```js
   // stops Play Scene and starts Score Scene

    this.scene.stop('PlayScene')
    this.scene.start('ScoreScene', {score: this.score});
```

To make use of this data being passed to the `ScoreScene`, we’ll use an `init()` block in `ScoreScene` to capture and assign the score data.

Add the following block to your `ScoreScene` class, above the `create()` block:

```js
 init(data) {
		this.finalScore = data.score;
}
```

This takes the `score` passed to the `start()` method and assigns it to a new variable in the scope of our new scene, `this.finalScore`. We can now use this to display text with the score in the scene.

Let’s also calculate a rank based on the score and display that on the screen. Here is the scale I used, but feel free to have fun with yours!

The following should all be within the `create()` block on the `ScoreScene`.

```js
// calculates rank based on score
this.scoreRank = this.finalScore > 500 ? 'A: Prime Protocol!' : this.finalScore > 300 ? 'B: Efficient Engine' : this.finalScore > 100 ? 'C: Routine Maintenance' : 'F: Gears Jammed'

// adds final score text to screen
this.scoreText = this.add.text(this.screenCenterX, this.screenHeight / 2 - 100, 'Score: ' + this.finalScore, {fontSize: '20px', fill: 'green'}).setOrigin(0.5, 0.5);
        
// adds score rank text to screen
this.rankText = this.add.text(this.screenCenterX, this.screenHeight / 2 - 50, 'Rank ' + this.scoreRank, {fontSize: '20px', fill: '#ffffff'}).setOrigin(0.5, 0.5)
```

Now, our Score scene is a lot more interesting.


![Screenshot of score scene](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kvdipoz6fko687fvyve6.png)


You can use this method of passing data via the `start()` method and retrieving it in the `init()` block to transfer data between your scenes as needed. This is helpful if you are not using global state for your game, or only need to pass some data that does not need to be stored in global state.

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

## Passing Score Data from Phaser to Vue

So now we know how to pass data between scenes within Phaser, but what about getting the data out of our game and back to our Vue app? Let’s tackle that in this section.

First, let’s update our Phaser game to emit the score data. We can do this using a JavaScript `CustomEvent` constructor, which you can read more about [here](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent).

When the game ends, we’ll create a custom event containing the score data from our game, and dispatch it to the window. Because Phaser is based on JavaScript, we can use JavaScript constructors like `CustomEvent()` right within our game code. 

Add the custom event within the overlap for your bombs and player, in the same place where the rest of our game over logic is handled. Make sure it’s declared before you stop the scene. Your code should look like this:

```js
  this.physics.add.overlap(this.player, this.bombs, function(object1, object2) {

    // Destroys bomb that triggered overlap
    const bomb = (object1.key === 'player') ? object1 : object2;
    bomb.destroy();

    // Stops game object generation, pauses physics, and resets score to zero
    createStarLoop.destroy();
    createBombLoop.destroy();
    this.physics.pause();

    // When the game ends, dispatch the event with the score and rating
    const gameEndEvent = new CustomEvent("gameEnded", {
       detail: { score: this.score }
    });
      
    window.dispatchEvent(gameEndEvent);

    // stops Play Scene and starts Score Scene

    this.scene.stop('PlayScene')
    this.scene.start('ScoreScene', {score: this.score});

  }, null, this);
  
  }
```

We are now emitting an event called `gameEnded` that contains the score data. The next step is to update our Vue app to handle this event.

We are going to accomplish two tasks:

- Add functionality within our `PhaserContainer.vue` to handle the event emitted by Phaser
- Use Provide/Inject in Vue to update and display a list of scores

First, add the following code to the `<script>` tag in `PhaserContainer.vue` , after the `handleClickStart()` function:

```js
// adds score when event emitted from Phaser
function handleGameEnded(event: Event) {
  const customEvent = event as CustomEvent;
  addGameScore(customEvent.detail.score);
}

// adds event listener for gameEnded event
onMounted(() => {
  window.addEventListener("gameEnded", handleGameEnded);
});

// removes event listener for gameEnded event
onUnmounted(() => {
  window.removeEventListener("gameEnded", handleGameEnded);
});
```

You’ll need to add `onMounted` and `onUnmounted` to your Vue import statement as well:

```js
import { onMounted, onUnmounted, ref } from 'vue'
```

You’ll notice there is a `addGameScore()` function being called within the “gameEnded” event handler. This will be part of our Provide/Inject in Vue. Let’s move to the `App.vue` component to add this.

Within the `<script>` tag, add the following code:

```js
import { provide, ref} from 'vue';

const gameScores = ref<number[]>([])

const addGameScore = (score: number) => {
  gameScores.value.push(score)
}

provide('gameScores', {
  gameScores, addGameScore
})
```

This simply creates an array `ref` to store our scores, and defines a method to add new scores to the ref. We then provide both the `gameScores` ref and the `addGameScore` method to our app. 

Let’s also create a `types.ts` file in the `src` directory, and export an interface for our Provider.

```js
export interface GameScoresProvider {
    gameScores: number[];
    addGameScore: (score: number) => void;
  }
```

Back in our `PhaserContainer.vue`, we can now inject the `addGameScore` method. Make sure to add `inject`to your Vue import statement.

```js

import { GameScoresProvider } from '@/types'

// injects addGameScore method
const { addGameScore } = inject<GameScoresProvider>('gameScores')!;
```

Finally, in our `ScoresPage.vue` view, we’ll display the list of scores. Update the template in your file to the following:

```js
<template>
  <ion-page>
    <ion-header>
      <ion-toolbar>
        <ion-title>Scores</ion-title>
      </ion-toolbar>
    </ion-header>
    <ion-content>
      <h3 v-show="!gameScores.length">No scores yet!</h3>
      <ion-list>
        <ion-item v-for="(score, index) in gameScores" :key="index">
          <ion-label>
            <h2>Score: {{ score }} </h2>
          </ion-label>
        </ion-item>
      </ion-list>
    </ion-content>
  </ion-page>
</template>
```

And update the `<script>` tag to the following:

```js
<script setup lang="ts">
import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent, IonList, IonItem, IonLabel } from '@ionic/vue';
import { inject } from 'vue'
import { GameScoresProvider } from '@/types'

// injects gameScores array ref
const { gameScores } = inject<GameScoresProvider>('gameScores')!;

</script>
```

Now, as we play our game, we’ll see the list of scores update in the Scores tab!

![Screenshot of Scores tab](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b6sprsc2wuc6u38d2vop.png)


As a final step, add some text to your `AboutPage.vue`. It can be whatever you want!

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

## What’s Next

We now have ourselves a game app that launches your game, navigates between scenes within Phaser, and displays a list of your scores within the app. The next (and final) step is to add support for iOS and Android so you can play the game as intended — on a mobile device!

Let's go!