Navigation

Let's check out other elements we can add with Phaser Editor and take a longer look at JavaScript

Published on 5th August 2024

Hey hi!

Welcome back! I hope you enjoyed the previous tutorial. I wanted to keep going and add something to make it an actual game or at least something that you can interact with.

For our game, Fruit Shoot, I am going to interpret the Shoot part very, very liberally. So liberally in fact that you might as well replace it with Drop. Except we are not going to do that because that would involve changing a bunch of things, so let's just agree between ourselves that shoot actually means drop.

Side-note: The phrase "naming things is hard" is very famous (and true) in software development and more often than not, projects will evolve in a way that, at some point, their original name won't reflect their contents anymore. But that's ok!

With that lengthy explainer out of the way, let's define our goals for this tutorial:

  • add new assets for a player character and an apple
  • create a level where the player can pop out from behind bushes
  • implement player behavior (the right arrow will make player pop out from the next bush)
  • create a system where fruit (the apple) is dropped from the top of the screen
  • detect when player "catches" the fruit

Let's get to it!

New assets

Say hello to our new assets, apple.png and hedgehog.png.

apple hedgehog

Feel free to download them (right click and Save image as) and then let's add them to the game. Once again, to add it to the game, we'll choose a directory (or folder) in the Files section. The previous assets lived in the assets directory so let's put these there too. Let's make sure it's selected, then right click, then select Upload files.

Upload files

In the next window, click Browse, find the files on your computer, and then select Upload 2 files. And remember, once they are added, click on Add to asset pack in the Inspector on the right.

You can now drag them into the scene to see how they look!

New assets

And with that, we can consider objective one complete. Now let's make the level.

Level layout

For the level, feel free to implement your own design. What I was thinking was to have 4 dark bushes on the ground and a few lighter bushes in the foreground. I'll copy and paste the dark_ground_blog game object 2 more times until I have four. Then I will adjust each of them until I get them to a position I'm happy with. Finally, I will position the lighter bushes in front of them. At the end, I was left with something like this:

My level

You will notice that I also deleted the clouds because I assume they might get into the way later. Step two done! It sure is nice to have small attainable goals. The next one however might be more of a challenge.

Creating the player movement

If you didn't drag the hedgehog image into the scene before, you can do so now. Also, we want to make it appear from behind the bushes, so we want to make sure its drawn first (and all the other things on top of it). To do that, select it in the Outline section and press PgDn (PageDown) until it's in the very bottom. Now let's look at the code side of things.

To get started, double click on FruitShootMain.ts in the Files section. In the last tutorial we added a tween effect for our clouds. It was at the bottom of the file. We don't actually need that so we can go ahead and delete it. The create function should now look like it did before:

    create() {

        this.editorCreate();

    }

Now that we have cleaned up a bit, let me explain what I want to do. I want to get all the 4 dark bushes in our scene, or rather I want their locations. I will then put the hedgehog game object on the location of the first dark bush. Then I will create some input detection so that when the right arrow is pressed, the hedgehog will appear behind the next bush.

To "get all the 4 dark bushes" in the scene, we need to find the variables that they have been assigned to. If you scroll up in the file, you'll find a bunch of statements like:

const dark_ground_blog = this.add.image(295, 535, "blob");

The dark_ground_blog part will be the same as the name of the game object in the Outline section! My game objects happened to have these names (also notice how I mistyped blob as blog one time and I'm still stuck with it):

blobs!

This is the result of Phaser Editor converting what you see in the editor into code.

Another thing we need to do is to change the scope of these variables. Select all the dark bushes (hold ctrl to select multiple objects) and then change the Scope value to CLASS in the inspector.

classes!

This makes it so that these variables are on the instance of this class. This is important for us because we want to use them not only where they are defined by Phaser Editor, but also in our create function.

Now I will put these into something called an array. Arrays are a data structure in JavaScript which are used to hold a collection or group of items. We actually used arrays before as well, when we did the thing with the clouds! As a result, my create function looks like:

create() {

        this.editorCreate();

        const bushes = [this.dark_ground_blog, this.dark_ground_blog_1, this.dark_ground_blog_2, this.dark_ground_blog_3]

    }

Now I'll make a new and empty array

const positions = []

And finally we will iterate over the array of bushes and add a position for each of them.

const positions = []

bushes.forEach(b => {
    positions.push({x: b.x, y: b.y})
})

bushes.forEach takes every member of the array, one at a time, and calls a function with it. The function that it calls is in the parenthesis that come next. In that function, the current member of the bushes array will be the variable b. The bush has an x and a y which indicate their position in the game world. We want to store this position for later. To do that, we push to (add to the end of) the positions array an object with those properties. An object is like other variables, but instead of having a value, it has keys or properties. So in our case, the whole object is:

{x: b.x, y: b.y}

The x and y are properties which each have their own values. In this example, they are the x and y values of the bush (and yes, b is an object as well).

By the end of this, the positions array will hold all the positions of the bushes. Let's put this to the test. Let's take our hedgehog image (and make sure the Scope is set to CLASS as well) and set its position to the first position in the array.

The entirety of the create function now looks like:

create() {

        this.editorCreate();

        const bushes = [this.dark_ground_blog, this.dark_ground_blog_1, this.dark_ground_blog_2, this.dark_ground_blog_3]

        const positions = []

        bushes.forEach(b => {
            positions.push({x: b.x, y: b.y})
        })

        const first_position = positions[0]

        this.hedgehog.x = first_position.x
        this.hedgehog.y = first_position.y

    }

The first_position is the 0th element of the array because in programming you start counting from 0.

When you run the scene, no matter where the hedgehog was before, it should now be at the first bush.

behind the bush!

Add input

Now let's set up the input so that we can move the hedgehog. Phaser Editor can help us here as well. The Scene Editor is not only for things that appear in the game visually, it can also be used to set up physics, collisions, inputs, and more. Phaser Editor lets us set up some keys and then we can go into the code and associate those keys with events, like key presses.

To add a key, right click anywhere in the Scene Editor or the Outline section and hover over Add Object, then Input, then Keyboard.Key.

Adding keys

This will add a keyboard.key game object in the Outline section, under Input (as opposed to Scene). When you select, you'll see in the Inspector that there is a new sub-section, called Keyboard Key. Under that, you'll find Key Code which allows you to select which key on your keyboard this key (as in, the GameObject) corresponds to. It's a bit confusing because there is a physical key on your keyboard and a "virtual" key in Phaser Editor and they are both called the same thing, but I hope you are still with me.

Renaming the game object

As you'll see, I have already renamed my game object to RightKey and I have changed the Key Code to RIGHT, associating this game object with the right arrow on my keyboard.

I'll save (Ctrl S or Cmd S on Mac) and open the scene in the code editor. I scroll down a little bit to find a variable called rightKey.

The variable

That's not quite the same as what I had in the Scene editor, where it's called RightKey, but I can understand why Phaser Editor made this change. In JavaScript, variable names usually start with lowercase letters and capital case words are often used for classes instead. I won't get into what's a class right now, I just wanted to clear up any potential confusion.

I will now go to the create function that I was editing earlier and add this code:

        let current_position_index = 0

        this.rightKey.on('down', () => {
            current_position_index = current_position_index + 1
            if(current_position_index > positions.length - 1){
                current_position_index = 0
            }

            const new_hedgehog_position = positions[current_position_index]
            this.hedgehog.x = new_hedgehog_position.x
            this.hedgehog.y = new_hedgehog_position.y

        })

That's a lot but let's break it down. First, I create a variable called current_position_index and set it 0. This will keep track of where we are in the positions array, i.e which element we currently have selected.

Then we add an eventListener to our rightKey. That part where it says down means that it will react to the key being pressed down. Then there is a function which is a section of code that will be executed each time this event (key being pressed down) happens.

In this function, we set the current_position_index to be one more than what it was before. Then, we immediately check if the new value is within the range of possible positions. This is an if statement which means that if the condition in the parentheses is true, then the code in the next block (between the {} bits) will run. In the condition, you'll see that I compare the current_position_index to the length of the array positions (length meaning how many elements it includes), minus one. The minus one is important because when we are getting an element from the array, the convention in programming is to start counting from 0. But when we think about the length, we start counting from 1. So the length of the positions array is 4, but if I try to access this index (positions[4]) then I get an error. Because it would look for the fifth element since it starts from 0.

I hope that makes sense! If not, give it some time, that's ok.

After the if statement, I create a variable to get the relevant position from the positions array. And finally, I set the hedgehog's x and y to that position's x and y, like we did before.

And now if you play the scene, and you press the right arrow on your keyboard, the hedgehog should jump to the next position. And after the last one, it should start over. Yay! Just in case, here's a screenshot of what this part looks like for me.

inputs

Add a falling apple

Next, I want to add an apple that falls from the sky, precisely above one of the positions. For that, I'll need physics and physics requires a bit of setup. In my Files section, I'll find the main.ts file and open it. I'll scroll down to where it says const game and add some physics setup:

        physics: {
            default: 'arcade',
            arcade: {
                gravity: {
                    y: 98
                }
            }
        },

Here's what it looks like after these changes:

config

This should be enough! Let's go back to our scene and add an apple. From the Files section, I'll drag the apple asset to the Scene editor. I'll select it and move it to the top of the screen. Finally, I want to add a physics body. To do this, I need to have the apple game object selected and then right click on the Scene editor or on top of the item in the Outline section, go down to Arcade Physics and then select Add body.

Add body

It may look like nothing happened but if you look at the Inspector on the right, there's a whole bunch of new sections. So let's hit play and see what happens. If everything's alright, the apple should gently glide down the screen.

Now I want to position the apple at one of the positions that we already defined before. Before that, I need to do the familiar step of making the apple's scope be CLASS (Select the apple game object in the Outline section, then on the right in the Inspector, set Scope to CLASS). And then let's open the scene in the code editor.

In the same place I set the hedgehog's position, I'll set the apple's position, but with one difference. I'll change its x value to the value of the position's x value, but the y can be 0. 0 is the top of the screen because in computer graphics, 0, 0 is the top left of the screen (as opposed to the center, which may be what's intuitive for you).

Set apple position

If you play the scene, the apple should now appear on top of the hedgehog.

Configure the collider

The final step to make the hedgehog able to "catch" the apple is to set up collisions. Collision detection is the process of checking if one or more objects are overlapping in a given simulation, physics system, game, or any other such application. To do this, let's add a physics body to the hedgehog as well.

To do this, repeat the process that we did for the apple. Have the hedgehog game object selected, right click in the Scene Editor or on the item in the Outline section, hover over Arcade Physics, and then choose Add body. Then let's go to the Inspector, scroll down, and untick Moves. We don't actually want the physics system to move this game object around, we just want the physics system to know about its position in the game world. That's why we added the body.

Disables move

And finally, let's set up the collider itself.

In the Scene Editor or in the Outline section, right click, hover over Add Object, then Arcade, then Collider.

Add collider

In the Outline section, there will now be a Arcade subsection, with a collider object. Select this and turn your attention to the Inspector.

There are some familiar things (like having to set the Scope to CLASS) and a few new options. In the Collider sub section, set the Object 1 to hedgehog, Object 2 to apple and type in this.collide to the Collide Callback section. And also be sure to tick the box next to Overlap Only.

The final config

As a result of this, we have told Phaser that whenever the object that is set to Object 1 overlaps with the object specified in Object 2, call the function this.collide. A function is a block of code to be executed so this allows us to react to the collision and perform actions as a result.

Before we try to see what happens, we need to make sure we actually have a function called this.collide. In the FruitShootMain.ts code file, scroll down to after the create function, and add:

    collide = (a, b) => {}

Just in case, my entire user code looks like:


    /* START-USER-CODE */

    // Write your code here

    create() {

        this.editorCreate();

        const bushes = [this.dark_ground_blog, this.dark_ground_blog_1, this.dark_ground_blog_2, this.dark_ground_blog_3]

        const positions = []

        bushes.forEach(b => {
            positions.push({x: b.x, y: b.y})
        })

        const first_position = positions[0]

        this.hedgehog.x = first_position.x
        this.hedgehog.y = first_position.y

        this.apple.x = first_position.x
        this.apple.y = 0

        let current_position_index = 0

        this.rightKey.on('down', () => {
            current_position_index = current_position_index + 1
            if(current_position_index > positions.length - 1){
                current_position_index = 0
            }

            const new_hedgehog_position = positions[current_position_index]
            this.hedgehog.x = new_hedgehog_position.x
            this.hedgehog.y = new_hedgehog_position.y

        })

    }

    collide = (a, b) => {}

    /* END-USER-CODE */

You may be thinking what happened to the this. The this is quite a complicated topic in JavaScript, but for now, we can think of this as the "parent entity". So in our case it will be the scene that the code is in so it's basically implied that the this is there. Again, this is quite advanced so it's OK if you don't fully grasp this now.

Now the scene should run, but nothing will happen. That's because the function is empty. We have to decide what we want to do. I think the easiest and clearest way to show that the hedgehog "got" the apple is to create a score counter.

Add a score counter

Back in the Scene Editor, I'll right click anywhere, hover over Add Object, then go to String, then click on Text. This adds a text object to the scene. The reason that it's under the String sub-menu is that string is the way programs refer to text. The idea behind the name is that it's a string of characters.

With the new text selected, I'll go to the Inspector and write Score: in the Text in the Text content sub-section. I also increased the font size but that's completely up to you.

I will then create another text object. This time, I will write 0 in the Text field and set the same font size.

Finally, I will move them to the top right of the screen and make sure the Scope value for both of them is set to CLASS. Finally, let's look at the Outline section and see which game object corresponds to the text that holds 0. In my case, it's called text_2.

Now we can go to the code. Let's make our collide function look like this:

    collide = (a, b) => {
        this.text_2.text = Number(this.text_2.text) + 1
        const random_index = Phaser.Math.Between(0, 3)
        const random_position = positions[random_index]
        b.x = random_position.x
        b.y = 0
    }

Here we want to modify the text value of text_2. Specifically, I want it to be 1 more than what it was before. This sounds simple, but there is one catch. In JavaScript, if I add (using the + sign) two strings then they are combined (in programming this is called concatenation). So 1 + 1 would become 11. If one part is a string and the other part is a number, the number is automatically converted into a string as well. This is what is happening for us. We need to make sure the current value of this.text_2 is also a number, that's why it's in this Number() wrapper.

Finally, we re-position the apple to a new position at the top of the screen. We want it to be a random position so let's choose a random number between 0 and 3. Thats done here:

    const random_index = Phaser.Math.Between(0, 3)

Then, we take a random element from the positions array that we made way back in the create function.

    const random_position = positions[random_index]

And finally we set the x of the b (which is the fruit) to that position and its y to 0.

But don't run the project yet! There is one more thing we have to change. The same positions array is currently only available in the "scope" of the create function. That means that anywhere outside the curly brackets of the create function, it doesn't exist. To make it available outside as well, we need to attach it to the scene that we are in. To do that, at the end of the create function we have to add:

  this.positions = positions

The this in this case is the scene so we are adding the positions property to it. And then, since the collide function is still looking for a thing called positions (which is not available), we have to change positions[random_index] to this.positions[random_index].

Just in case, the whole code that I added is here. Remember, the variable names might be different, but the logic is here.

    /* START-USER-CODE */

    // Write your code here

    create() {

        this.editorCreate();

        const bushes = [this.dark_ground_blog, this.dark_ground_blog_1, this.dark_ground_blog_2, this.dark_ground_blog_3]

        const positions = []

        bushes.forEach(b => {
            positions.push({x: b.x, y: b.y})
        })

        const first_position = positions[0]

        this.hedgehog.x = first_position.x
        this.hedgehog.y = first_position.y

        const random_index = Phaser.Math.Between(0, 3)
        const random_position = positions[random_index]

        this.apple.x = random_position.x
        this.apple.y = 0

        let current_position_index = 0

        this.rightKey.on('down', () => {
            current_position_index = current_position_index + 1
            if(current_position_index > positions.length - 1){
                current_position_index = 0
            }

            const new_hedgehog_position = positions[current_position_index]
            this.hedgehog.x = new_hedgehog_position.x
            this.hedgehog.y = new_hedgehog_position.y

        })

        this.positions = positions

    }

    collide = (a, b) => {
        this.text_2.text = Number(this.text_2.text) + 1
        const random_index = Phaser.Math.Between(0, 3)
        const random_position = this.positions[random_index]
        b.x = random_position.x
        b.y = 0
    }

    /* END-USER-CODE */

This leaves a funny bug where the fruit gets faster and faster each time. I leave it as a challenge to you to figure out why that is.

And that's it for now! You should really be proud for making it this far. Even though it might seem like "just following instructions", it takes a lot of focus and attention to get all the details right.

To really make this knowledge stick, try and experiment on your own and change things around! Or try to start from scratch and see if you can recreate this. When you get stuck, try to remember what we did. And if you are really stuck, it's OK to check back here.

If I were to give you a final point to consider: try and see the connection between the code and the scene editor. Notice how everything that we have modified using the scene editor (like the text values or the placement of objects) can be changed in code too. Keep this in mind when you continue to add functionality. You can set things up in the Scene Editor and when the game runs, the code takes over. So if you want to implement certain behavior, think about:

  • how can I get access to this game object in code
  • what is the property (the x, text, and so on) that I need to change

And finally, Phaser Editor has a part of the features available in the code, meaning that everything you can do in the editor, you can also do in code. And much more!

Thank you very much for reading and good luck on your game development journey!