Welcome to part 3 and the final installment, of my Introduction to Phaser. In the first post, we configured our dev environment as well as the code we’ll need to run Phaser itself. In the second post, we covered preloading, displaying sprites and setting up the game modes. If you missed the two tutorials, please go back and follow those steps before moving on. In this tutorial we are going to cover the following:
Let’s get started.
Right now our game isn’t very fun or challenging. To help with this we’ll add walls with a small gap in the middle for our player to fly through. To get started, we’ll need to create two functions: one to spawn a wall and the other to put two walls together with the gap for the player to fly through.
Step 1. Add the following method to our state object:
spawnWall: function(y, flipped){ }
This function accepts the y position for the wall and if it needs to be flipped vertically. This is important since we will be using the same wall graphic for our top and bottom wall sprites. We’ll need a way to group them together into a single manageable game object.
Step 2. In our create() method, right under where we setup the background TileSprite we should add our wall group:
create: function(){ this.background = this.add.tileSprite(0,0, this.world.width, this.world.height, 'background'); this.walls = this.add.group();
It’s important to create this after our background so it shows up on top. Now we’ll be able to access each of the wall sprites in our game from a single group which we are calling walls. This will make a little more sense later on when we add in collision.
Step 3. Add the following to our spawnWall() method:
spawnWall: function(y, flipped){ var wall = this.walls.create( game.width, y + (flipped ? -OPENING : OPENING) / 2, 'wall' ) }
This adds a new wall sprite to our walls group. The staring x position is set to the width of the game so it starts off-screen and we also use the supplied flipped value to detect if the wall going to be displayed on the top or the bottom. To do this we take advantage of something called a Ternary Operator. Basically the way that this works is we have a condition to the left of the question mark and then the value if true followed by the value if the condition is false. Here is a representation of this:
condition ? true : false;
Step 4. Add the following constant to the top of our script:
var OPENING = 200;
Step 5. Add a few more lines of code to our spawnWall() function:
this.physics.arcade.enableBody(wall); wall.body.allowGravity = false; wall.scored = false; wall.body.immovable = true; wall.body.velocity.x = -SPEED; if(flipped){ wall.scale.y = -1; wall.body.offset.y = -wall.body.height; }
This disables gravity for the wall. Then, we set the y scale to -1 if it’s flipped, or 1 if it is staying the same. We also need to adjust the y offset value of the body if the wall is flipped to account for the scale change we made on the previous line. Finally we set the velocity.x of the wall to our SPEED constant. You’ll notice we make this value negative. That way it moves to the left of the screen giving the impression that the player is flying past it.
Step 6. Add the following line to the end of the spawnWall() function:
return wall;
This will allow us to get a reference to the wall instance being created every time we call the spawnWall() method. We need to test if this works, but we’ll need to add a little bit of debug code first.
Step 7. Add the following line of code to our create method:
this.spawnWall(300);
Step 8. Refresh the game in your browser and now you should see your first wall fly by below the player:
Step 9. To test creating a wall on the top of the screen, simply modify the wallSpawn() function to look like this:
this.spawnWall(300, true);
Step 10. With true being passed into wallSpawn() you can refresh the game to see the new wall on the top of the screen:
At this point we have the foundation in place for us to spawn the walls in our game. We’ll create a method to generate both a top and bottom wall as well as connect it up to a timer.
Step 1. Before moving on, make sure you comment out the spawn code we used to test with in the previous section:
//this.spawnWall(300, true);
Step 2. Now add new method called spawnWalls() with the following:
spawnWalls: function(){ var wallY = this.rnd.integerInRange(game.height *.3, game.height *.7); var botWall = this.spawnWall(wallY); var topWall = this.spawnWall(wallY, true); }
Take note that we added a s to this function’s name since our spawnWall() function only generates a single instance. In the spawnWalls() function we are taking advantage of Phaser’s RandomDataGenerator class. You can access it from the game object by calling this.rnd. This utility has a lot of useful methods for helping create random values in your game. We’ll be taking advantage of integerInRange() which requires a minimum and maximum value to return something in-between the two. Finally to keep the walls within the screen area we calculate the “safe” zone were we would want our walls to spawn by taking a percentage from the top and bottom of the visible area.
Step 3. Now we are ready to start a timer to spawn new walls. In our start() function, add the following code:
this.wallTimer = this.game.time.events.loop(Phaser.Timer.SECOND * SPAWN_RATE, this.spawnWalls, this); this.wallTimer.timer.start();
Step 4. We’ll also need to add a new constant at the top of our script:
var SPAWN_RATE = 1.25;
Step 5. Refresh your browser and you can now fly through an endless supply of walls being randomly generated at a set interval.
Everything is looking good but there is just one minor issue. While you may not see it, the walls that have moved off the screen are still in the game. We need to do a little bit of cleanup to make sure that we destroy any wall that is no longer visible.
Step 1. Add the following code to our update function right after we test if player is out of bounds:
if (this.player.body.bottom >= this.world.bounds.bottom) { this.setGameOver(); } this.walls.forEachAlive(function (wall) { if (wall.x + wall.width < game.world.bounds.left) { wall.kill(); } })
Remember back to when we set up our walls in the group? This forEachAlive loop allows us to easily iterate through all the wall instances inside of that group still active in the game. From there we setup a generic function to test if the instance’s x position is greater than the game world’s bounds.left value so we can destroy it. Calling kill() on an instance completely removes it from the game and from memory as well. Without this function, the game would have a memory leak which would cause performance issues and a poor user experience. Phaser also has some really great pooling classes but for this intro level tutorial we’ll just do this manually. The game is simple enough not to worry about the extra overhead we incur by creating new wall instances from scratch.
Step 2. In the reset() function of our state object add the following line:
this.walls.removeAll();
This ensures we get a clean slate when starting the game over. And while we are on the topic of starting over, you may have noticed that the walls still scroll by when the game is over?
Step 3. Next, we’ll need to stop the walls from moving when we end the game. Add the following to our setGameOver() function:
this.walls.forEachAlive(function (wall) { wall.body.velocity.x = wall.body.velocity.y = 0; }); this.wallTimer.timer.stop();
This function will iterate through each wall instance and set the velocity.x to 0 to stop it from moving. We also stop the wallTimer to make sure nothing else gets created while the game is over.
Step 4. Run the game and make sure that the walls stop when game over before continuing on.
Now that we have walls, we can define what happens when a player hits a wall. Luckily for us, collision detection is really easy to implement in Phaser.
Step 1. To get started we’ll need a way to detect the actual collision. To do this, we add one line of code to our update function right after where we test if the player hits the bottom of the screen:
if(!this.gameOver){ if(this.player.body.bottom >= this.world.bounds.bottom){ this.setGameOver(); } this.physics.arcade.collide(this.player, this.walls, this.setGameOver, null, this); }
This tells the physics engine to test for any collisions between two game objects or in this case a single object, the player, and the walls group. The next parameter is the callback the collision check should trigger if an overlap is detected. Finally, we need to pass the game’s scope to the callback parameter.
Step 2. At this point, everything else is already setup for us to build the game over state. Now if you test the game, you’ll see the game ends when the player touches the walls.
Step 3. When a collision occurs you may notice that the player actually bounces back. Add the following to the setGameOver() function to fix that:
this.player.body.velocity.x = 0;
Step 4. Run game and you’ll see the player no longer bounced back because we set the velocity.x to 0 when the game is over.
In this section, we learn how to add a function that increases the score when a player flies through the walls. To get started, we are going to need a way to determine if the player has flown past a wall, and then give them a score for it.
Step 1. We’ll need to add a new function called addScore() to handle the collision callback:
addScore: function (wall) { wall.scored = true; this.score += .5; this.scoreText.setText("SCORE\n" + this.score); }
Here you can see we are getting a reference to a wall instance, adding a score and updating the score text. One thing to keep in mind is that since we have 2 instances, a top and bottom, that make up a single wall we only add half a point to the score. As the player flies past each wall section they will add up to a single value of 1 and the player will not even notice.
Step 2. Now we need to add in the logic to call addScore(). In the forEachAlive loop within the update() function, add the following:
this.walls.forEachAlive(function (wall) { if (wall.x + wall.width < game.world.bounds.left) { wall.kill(); } else if(!wall.scored && wall.x <= state.player.x){ state.addScore(wall); } })
Step 3. Now you can refresh the browser and as you fly through each section of wall you’ll see the score increase by 1.
We now have a functional game. The only thing missing is fun – we need to add some sound effects to round out the playing experience.
Step 1. Before we can access any sound effects we’ll need to load them up. Add this to our preload method:
this.load.audio("jet", "/assets/jet.wav"); this.load.audio("score", "/assets/score.wav"); this.load.audio("hurt", "/assets/hurt.wav");
Step 2. Next we’ll create variables for each of our sound effects so we can access them easier. Add this to our create method:
this.jetSnd = this.add.audio('jet'); this.scoreSnd = this.add.audio('score'); this.hurtSnd = this.add.audio('hurt');
Step 3. Now it’s time to play our sound. In jet() function right after we set the player’s velocity.y to our jet constant add the following:
if(!this.gameOver){ this.player.body.velocity.y = -JET; this.jetSnd.play();
Step 4. Now if you run the game and make the player fly, you should hear the sound. One thing to keep in mind is that each browser supports specific sound codecs. If you have any issues running the sound effects, make sure you are in Chrome or a browser that supports .wav playback.
Step 5. Next we’ll want to let the player know when they score. To enable that, we need to add the following script to our addScore() function:
this.scoreSnd.play();
Step 6. Add this to the end of our setGameOver() method to let the player know when they hit a wall or go out of bounds:
this.hurtSnd.play();
Step 7. Now that we have the rest of our sound effects integrated, we can refresh the browser and test. You should now hear sounds when the player flies up, scores and dies.
At this point the game is basically done, and hopefully you’ve learned how some of the core mechanics of Phaser work.
To recap, we have covered importing assets, creating sprites, managing collision detection and playing sounds. There are a lot of really great resources out there to help you as you dig deeper into the Phaser framework. Make sure you check out the documentation as well as the samples included with the source code. Also, feel free to take my existing codepen project and fork it to create your own variation of it.
One last thing I wanted to highlight is just how easy Amazon’s Web App tools are to use, helping to make publishing an online HTML5 game in the Amazon Appstore simple. I’ll be talking about how to take any HTML5 game and submit to the Amazon Appstore in a future post but the process couldn’t be easier. Here are the steps:
To learn more about the process, check out the documentation on our developer portal and the following reference links:
Related links:
- Jesse Freeman (@jessefreeman)