Introduction
In this tutorial we’ll go through the process of building an HTML5 mobile game that uses the Device Orientation API and Vibration API in the gameplay and is built using the Phaser framework. Basic JavaScript knowledge is required to learn from the article below, and at the end of the tutorial we will have a fully functional demo game Cyber Orb.
Phaser framework
Phaser is a framework for building desktop and mobile HTML5 games. It’s quite new, but grows rapidly thanks to the passionate community involved in the development process. You can check it out on GitHub where it’s open sourced, read the online documentation and go through the big collection of examples. The Phaser framework provide you with the set of tools that will speed up development and help handle generic tasks needed to complete the game, so you can focus on the game idea itself.
Starting with the project
You can see the code of the project on GitHub. The folder structure is quite straightforward: the starting point is the index.html
file where we initialize the framework, set up the Canvas and play the game. You can click that file in your favourite browser to launch the game and try it. There are also three folders: img
, src
and audio
- the first one contains all the images that we will use in the game, the second one have the JavaScript files with all the source code of the game defined inside, and in the third folder you can find the sound files used in the game.
Setting up the Canvas
We will be rendering our game on Canvas, but we won't do it manually - it will be taken care of by the framework. Let’s set it up: our starting point is the index.html
file with the following content:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Cyber Orb demo</title> <style> body { margin: 0; background: #333; } </style> <script src="src/phaser-arcade-physics.2.2.2.min.js"></script> <script src="src/Boot.js"></script> <script src="src/Preloader.js"></script> <script src="src/MainMenu.js"></script> <script src="src/Howto.js"></script> <script src="src/Game.js"></script> </head> <body> <script> (function() { var game = new Phaser.Game(320, 480, Phaser.CANVAS, 'game'); game.state.add('Boot', Ball.Boot); game.state.add('Preloader', Ball.Preloader); game.state.add('MainMenu', Ball.MainMenu); game.state.add('Howto', Ball.Howto); game.state.add('Game', Ball.Game); game.state.start('Boot'); })(); </script> </body> </html>
It looks like a simple HTML website with some basic content in the <head>
section: charset, title, CSS styling and the inclusion of the JavaScript files. The <body>
contains initialization of the Phaser framework and the definitions of the game states. You can check the Building Monster Wants Candy article for the in-depth introduction to the basic Phaser-specific functions and methods.
Managing game states
The states in Phaser are a separate parts of the game logic, in our case we’re loading them from independent JavaScript files for better maintainability. The basic states used in this game are: Boot
, Preloader
, MainMenu
, Howto
and Game
. Boot
will take care of initializing a few settings, Preloader
will load all of the assets like graphics and audio, MainMenu
is the menu with the start button, Howto
shows how to play instructions and the Game
state lets you actually play the game. Let's quickly go though the content of those states.
Boot.js
The Boot
state is the first one in the game.
var Ball = { _WIDTH: 320, _HEIGHT: 480 }; Ball.Boot = function(game) {}; Ball.Boot.prototype = { preload: function() { this.load.image('preloaderBg', 'img/loading-bg.png'); this.load.image('preloaderBar', 'img/loading-bar.png'); }, create: function() { this.game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL; this.game.scale.pageAlignHorizontally = true; this.game.scale.pageAlignVertically = true; this.game.state.start('Preloader'); } };
The main Ball
object is defined and we're adding two variables called _WIDTH
and _HEIGHT
that are the width and the height of the game Canvas - they will help us position elements on the screen. We're loading two images first that will be used later in the Preload
state to show the progress of loading all the other assets. The create
function holds some basic configuration: we're setting up the scaling and alignment of the Canvas, and moving on to the Preload
state when everything's ready.
Preloader.js
The Preloader
state takes care of loading all the assets:
Ball.Preloader = function(game) {}; Ball.Preloader.prototype = { preload: function() { this.preloadBg = this.add.sprite((Ball._WIDTH-297)*0.5, (Ball._HEIGHT-145)*0.5, 'preloaderBg'); this.preloadBar = this.add.sprite((Ball._WIDTH-158)*0.5, (Ball._HEIGHT-50)*0.5, 'preloaderBar'); this.load.setPreloadSprite(this.preloadBar); this.load.image('ball', 'img/ball.png'); // ... this.load.spritesheet('button-start', 'img/button-start.png', 146, 51); // ... this.load.audio('audio-bounce', ['audio/bounce.ogg', 'audio/bounce.mp3', 'audio/bounce.m4a']); }, create: function() { this.game.state.start('MainMenu'); } };
There are single images, spritesheets and audio files loaded by the framework. In this state the preloadBar
is showing the progress on the screen. After all the assets are loaded, the MainMenu
state is launched.
Main menu
The MainMenu
state shows the main menu of the game where you can start playing by clicking the button.
Ball.MainMenu = function(game) {}; Ball.MainMenu.prototype = { create: function() { this.add.sprite(0, 0, 'screen-mainmenu'); this.gameTitle = this.add.sprite(Ball._WIDTH*0.5, 40, 'title'); this.gameTitle.anchor.set(0.5,0); this.startButton = this.add.button(Ball._WIDTH*0.5, 200, 'button-start', this.startGame, this, 2, 0, 1); this.startButton.anchor.set(0.5,0); this.startButton.input.useHandCursor = true; }, startGame: function() { this.game.state.start('Howto'); } };
Instead of jumping directly into the action, the game will show the screen with the information on how to play the game.
How to play
Ball.Howto = function(game) { }; Ball.Howto.prototype = { create: function() { this.buttonContinue = this.add.button(0, 0, 'screen-howtoplay', this.startGame, this); }, startGame: function() { this.game.state.start('Game'); } };
The Howto state is showing the gameplay instructions on the screen before starting the game. After clicking the screen the actual game is launched.
The Game
The Game
state from the Game.js
file is where all the magic happen. All the initialization is in the create()
function (launched once at the beginning of the game), some things will require adding them to the update()
function (executed at every frame), and we will also write our own functions to handle more complicated tasks.
Ball.Game = function(game) {}; Ball.Game.prototype = { create: function() {}, initLevels: function() {}, showLevel: function(level) {}, updateCounter: function() {}, managePause: function() {}, manageAudio: function() {}, update: function() {}, wallCollision: function() {}, handleOrientation: function(e) {}, finishLevel: function() {} };
The create
and update
functions are framework-specific, while others are our own creations: initLevels
initializes the level data, showLevel
prints the level data on the screen, updateCounter
updates the time spent on playing the game, managePause
pauses and resumes the game, manageAudio
turns the sounds on and off, wallCollision
is executed when the ball hits the walls or other objects, handleOrientation
is the function bound to the event responsible for the Device Orientation API and finishLevel
is loading a new level if there is any, or finishing the game.
Adding the ball and its motion mechanics
First, let’s go to the create
function, initialize the ball object itself and assign a few properties to it:
this.ball = this.add.sprite(this.ballStartPos.x, this.ballStartPos.y, 'ball'); this.ball.anchor.set(0.5); this.physics.enable(this.ball, Phaser.Physics.ARCADE); this.ball.body.setSize(18, 18); this.ball.body.bounce.set(0.3, 0.3);
We’re adding a sprite at the given place on the screen and using the 'ball'
image from the loaded graphic assets. We’re also setting the anchor of any physic calculations to the middle of the ball, then enabling the ball for the Arcade physics engine, and setting the size of the body for the collision detection. The bounce
property is used to set the bounciness of the ball when it hits the obstacles.
Controlling the ball
It’s cool to have the ball ready to be thrown around the play area, but it’s also important to be able to do it. Now we will add the ability to control the ball with the keyboard on the desktop devices, and then we will move to the implementation of the Device Orientation API. Let’s focus on the keyboard first:
this.keys = this.game.input.keyboard.createCursorKeys();
As you can see there’s a special Phaser function which will give us an object with the four arrow keys to play with: up, down, left and right. The this.keys
object will then be checked against player input, so the ball can react accordingly with the prefedined force:
if(this.keys.left.isDown) { this.ball.body.velocity.x -= this.movementForce; } else if(this.keys.right.isDown) { this.ball.body.velocity.x += this.movementForce; } if(this.keys.up.isDown) { this.ball.body.velocity.y -= this.movementForce; } else if(this.keys.down.isDown) { this.ball.body.velocity.y += this.movementForce; }
The code above is in the update
function, so it will be fired on every frame. That way we can check which key is pressed at the given frame and apply the defined force to the ball, thus increase the velocity in the proper direction.
Implementing the Device Orientation API
The interesting part of the game is that it uses Device Orientation API on mobile devices. Thanks to this you can play the game by tilting the device in the direction you want the ball to roll. Here’s the code responsible for this:
window.addEventListener("deviceorientation", this.handleOrientation, true);
We’re adding an event listener to the "deviceorientation"
event and binding the handleOrientation
function which looks like this:
handleOrientation: function(e) { var x = e.gamma; var y = e.beta; Ball._player.body.velocity.x += x; Ball._player.body.velocity.y += y; },
The more you tilt the device, the more force is applied to the ball and the velocity is higher.
Adding the hole
Our main purpose of the game is to move the ball from the starting position to the ending position which in our case will be a hole in the ground. Implementation looks very similar to the part where we created the ball:
this.hole = this.add.sprite(Ball._WIDTH*0.5, 90, 'hole'); this.physics.enable(this.hole, Phaser.Physics.ARCADE); this.hole.anchor.set(0.5); this.hole.body.setSize(2, 2);
The difference is that our hole’s body will not move when we hit it with the ball and will have the collision detection calculated (which will be discussed further in this article).
Building the block labyrinth
To make the game harder and more interesting we should build some obstacles on the way from the starting point to the end of the level, so let's build a labyrinth. We could use a level editor, but for the sake of this tutorial let's create something on our own.
We'll have a level data holding the information of the particular blocks - the top and left absolute positions in pixels and the type of the block - horizontal or vertical. Then, to load the level we need we will parse the data and show the blocks specific for that level. In the initLevels
function we have:
this.levelData = [ [ { x: 96, y: 224, t: 'w' } ], [ { x: 72, y: 320, t: 'w' }, { x: 200, y: 320, t: 'h' }, { x: 72, y: 150, t: 'w' } ], // ... ];
Every array element holds the collection of the blocks with the x
, y
position and t
type. In the loop we're creating the items using the framework:
for(var i=0; i<this.maxLevels; i++) { var newLevel = this.add.group(); newLevel.enableBody = true; newLevel.physicsBodyType = Phaser.Physics.ARCADE; for(var e=0; e<this.levelData[i].length; e++) { var item = this.levelData[i][e]; newLevel.create(item.x, item.y, 'element-'+item.t); } newLevel.setAll('body.immovable', true); newLevel.visible = false; this.levels.push(newLevel); }
Everything is stored in this.levels
array, and is by default invisible. Then, to load specific level we just have to make sure the previous level is hidden and show the current one:
showLevel: function(level) { var lvl = level | this.level; if(this.levels[lvl-2]) { this.levels[lvl-2].visible = false; } this.levels[lvl-1].visible = true; }
Thanks to that the game gives the player a challenge - now he have to roll the ball across the play area and guide it through the labyrinth built from the blocks. It's just an example of loading the levels, and there are only 5 of them just to showcase the idea, but you can work on expanding that on your own.
Collision detection
We have the ball that is controlled by the player, the hole to reach and the obstacles blocking the way. There’s a problem though – our game don’t have the collision detection yet, so nothing happens when the ball hits the blocks, it just goes through. That’s not the effect we wanted to achieve, so let’s work on it. The good news is that the framework will take care of calculating the collision detection, we only have to specify the colliding objects:
this.physics.arcade.collide(this.ball, this.borderGroup, this.wallCollision, null, this); this.physics.arcade.collide(this.ball, this.levels[this.level-1], this.wallCollision, null, this);
This will tell the framework to execute the wallCollision
function when the ball will hit any of the walls. We can use the wallCollision
function to add any functionality we want like playing the bounce sound or implementing the Vibration API.
Adding the sound
Among the preloaded assets there was an audio file (in various formats for browser compatibility) which we can use now. It have to be defined in the create
function first:
this.bounceSound = this.game.add.audio('audio-bounce');
Then, if the status of the audio is true
(so the sounds in the game are enabled), we can play it in the wallCollision
function:
if(this.audioStatus) { this.bounceSound.play(); }
That's all - loading and playing the sounds is easy with Phaser.
Implementing the Vibration API
When collision detection works as expected let's add some special effects with the help from the Vibration API. The best way to use it in our case is to vibrate the phone every time the ball hits the walls.
if("vibrate" in window.navigator) { window.navigator.vibrate(100); }
If the vibrate
method is supported by the browser and available in the window.navigator
object, vibrate the phone for 100 miliseconds. That's it!
Adding the elapsed time
To improve the replayability and give players the option to compete with each other we can introduce the elapsed time. Thanks to this the player can play the given levels again and again trying to improve his score. To implement that in the game we have to create a variable for storing the actual number of seconds elapsed from the start of the game, and to show it for the player in the game. Let’s define the variable first:
this.timer = 0; this.totalTimer = 0;
There are to variables to have the time elapsed in the current level and the time elapsed in the hole game. Then we can initialize the text objects:
this.timerText = this.game.add.text(15, 15, "Time: "+this.timer, this.fontBig); this.totalTimeText = this.game.add.text(120, 30, "Total time: "+this.totalTimer, this.fontSmall);
We’re defining the top and left positions of the text, the content that will be showed and we can also apply font styling to the text, so it looks better. We have this printed out on the screen, but it would be good to update the values every second:
this.time.events.loop(Phaser.Timer.SECOND, this.updateCounter, this);
This loop will execute the updateCounter
function in every single second from the beginning of the game, so we can apply the changes accordingly. This is how the updateCounter
function looks like:
updateCounter: function() { this.timer++; this.timerText.setText("Time: "+this.timer); this.totalTimeText.setText("Total time: "+(this.totalTimer+this.timer)); },
As you can see we’re incrementing this.timer
variable and updating the content of the text objects with the current values, so the player sees the elapsed time.
Finishing the level and the game
The ball is rolling on the screen, the time is running out, we have the hole that we have to reach, so let’s set up the possibility to actually finish the level. We will cover few aspects of the situation when the ball gets to the hole.
this.physics.arcade.overlap(this.ball, this.hole, this.finishLevel, null, this);
It works similar to the collide
method explained earlier. When the ball overlap with the hole (instead of colliding), the finishLevel
function is executed:
finishLevel: function() { if(this.level >= this.maxLevels) { this.totalTimer += this.timer; alert('Congratulations, game completed!\nTotal time of play: '+this.totalTimer+' seconds!'); this.game.state.start('MainMenu'); } else { alert('Congratulations, level '+this.level+' completed!'); this.totalTimer += this.timer; this.timer = 0; this.level++; this.timerText.setText("Time: "+this.timer); this.totalTimeText.setText("Total time: "+this.totalTimer); this.levelText.setText("Level: "+this.level+" / "+this.maxLevels); this.ball.body.x = this.ballStartPos.x; this.ball.body.y = this.ballStartPos.y; this.ball.body.velocity.x = 0; this.ball.body.velocity.y = 0; this.showLevel(); } },
If the current level is equal to the maximum number of levels (in this case 5), then the game is finished - you'll get the congratulations message along with the number of seconds elapsed through the whole game. After clicking the alert button you are taken to the main menu. If the current level is lower though, all the neccesary variables are being reset and the next level is loaded.
Ideas for new features
This is merely a working demo of a game that could have lots of other different features. We can for example add power-ups to collect along the way that will make our ball roll faster, other can stop the timer for a few seconds or give the ball special powers to go through obstacles. There’s also room for the traps which will slow the ball down or make it more difficult to reach the hole. You can create more levels with the different difficulty for each one. You can even implement achievements, leaderboards and medals for different actions in the game. There are endless possibilities – they only depend on your imagination.
Summary
I hope this tutorial will help you dive into game development with Phaser and inspire you to create awesome games on your own. You can play the demo game Cyber Orb and check out its source code on GitHub. The technology gives us tools, the frameworks are getting faster and better, so you just have to start your own journey and see if HTML5 game development is something that you want to spend your time on.