Creating Mobile Games with Phaser 3 and Cordova

Find the source code for this example here.

Photon Storm’s Phaser is one of the most trusted frameworks for developing professional-quality 2D games in JavaScript. With Phaser, it’s feasible to make performant games that run smoothly in all major browsers, across all major systems, while only having to maintain one codebase. And now, the latest installment of Phaser has made its debut: Phaser 3.

But did you know that the same code that runs in your browser can be wrapped into a “native” mobile application? By combining Phaser 3 with Apache Cordova, we can produce games that not only run in the browser, but can also be published to the App Store and Google Play Store.

In this article, we’ll use Phaser to make a simple “game” that can be built for iOS, Android, and the Web. Though the result won’t truly be a “game” in the playable sense, the concepts it introduces will be referenced again in future tutorials where we will, indeed, create playable games.

Pre-requisites (things to install!)

We’ll be dealing with purely JavaScript (and tiny bit of HTML) through this tutorial, so all you need to install manually is Node.js. This will give you the node and npm executables on your system.

Once Node.js is installed, download the Apache Cordova command line by running this command in your terminal/command prompt:

npm i -g cordova

Learn Phaser 3 with our newest Mini-Degree

The Phaser Mini-Degree is now available for Pre-Order on Zenva Academy. Learn to code and make impressive games with JavaScript and Phaser 3!

Get Instant Early Access

Project Setup

Cordova includes boilerplate projects that make it much simpler to start off with a mobile-enabled project, so let’s take advantage of that, by running:

cordova create hello

This will create a directory named hello, and copy multiple files into the project.

Next, let’s install Phaser into our project. In our project directory, run the following:

cd www
npm init
npm i --save phaser

For those unfamiliar with npm, the above command downloads the source code of the Phaser framework and saves a local copy in the www/node_modules folder. This way, we have a copy of the code without having to download it from a CDN on every run. The reason we have to have a separate package.json file in the www directory is that static files are served from the www directory, and not from our project root. If Phaser were installed in the project root, we would never be able to access it in the browser.

Next, let’s add support for running our app in a browser. We’ll add support for running on iOS and Android later. Run this:

cordova platform add browser

Next, we’ll want to add support for live-reloading our changes when we edit our file. Otherwise, Cordova will only ever serve one version of our files, which will lead to a lot of head scratching and wondering why errors seemingly don’t vanish, even after changing your code! Run this command:

cordova plugin add cordova-plugin-browsersync

Now that we’ve set our project for development, let’s run the HTTP server:

cordova run browser --live-reload

Now, you should be able to visit http://localhost:8000/index.html to view your game in action.

Basic Game Code

First, let’s remove the starter code that Cordova gives us; we won’t be needing it anymore. Remove the <div id="app">...</div> code in index.html, and add a reference to our downloaded Phaser source, so that the file looks like the following, ignoring comments:

<html>
    <head>
        <meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *; img-src 'self' data: content:;">
        <meta name="format-detection" content="telephone=no">
        <meta name="msapplication-tap-highlight" content="no">
        <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">
        <link rel="stylesheet" type="text/css" href="css/index.css">
        <title>Hello World</title>
    </head>
    <body>
        <script type="text/javascript" src="cordova.js"></script>
        <script type="text/javascript" src="js/index.js"></script>
    </body>
</html>

Feel free to remove the Content-Security-Policy tag in the head. Though it’s a smart choice for security, if you are unfamiliar with CSP, it can be a rather confusing roadblock for such a simple tutorial.

Next, let’s add some code in js/index.js to start up a Phaser instance:

document.addEventListener('deviceready', function() {
    var config = {
        type: Phaser.WEBGL,
        parent: 'game',
        scene: {
            preload: preload,
            create: create
        }
    };
    
    var game = new Phaser.Game(config);
    
    function preload() {
    }
    
    function create() {
    }    
});

Even in this relatively small code snippet, there’s quite a bit going on:

  1. We create a configuration object, which we pass to the Phaser.Game constructor to specify where the framework should call our code.
  2. We indicate to Phaser, via, type: Phaser.WEBGL, that we want to use the WebGL-based renderer, which is faster than plain Canvas 2D-based rendering.
  3. We tell Phaser to render into an existing <canvas> where id="game", or to create such a <canvas> element if one does not exist.
  4. We embed a scene object, which points to two functions Phaser will call at different points in the game lifecycle.

Now, let’s run our game! Type the following in your terminal:

cordova run browser -- --livereload

Next, to actually see the game in your browser, visit http://localhost:8000 in your browser. Check out the output!

Empty black screen for new Phaser based game

Beautiful! It’s an empty black screen!

Adding Game Objects

As you can probably imagine, nobody wants to just sit there and stare at an infinitely-blank screen. So let’s spruce it up and add something that moves!

Phaser has long been based on object-oriented programming, but Phaser 3 introduces the categorization of certain objects as game objects, which share common functionality while achieving different goals. The kind of game object we’ll be adding now is called a sprite, which in the 2D world means “image that represents an object.” Common examples of sprites include animated sprites, which are often used to represent characters in games.

Even though a sprite by itself is just an image, in the context of a game engine like Phaser, it is frequently associated with physics computations, and provides an abstraction through which a game can programmatically change what’s visible on screen in a logical way.

Using an asset from Kenney, let’s load an image and draw it to the screen.

File screen with sheet.png and sheet.xml selected

First, let’s download the Tappy Plane Asset Pack. Copy SpriteSheet/sheet.png and SpriteSheet/sheet.xml into the local www/img directory. We’ll load these into our game by modifying our preload and create functions as follows:

function preload() {
   this.load.atlas('sheet', 'img/sheet.png', 'img/sheet.json');
}
    
function create() {
    this.add.sprite(400, 300, 'sheet', 'planeBlue1.png');
}

Note that img/sheet.json is included in the project assets, and is based on the contents of img/sheet.xml.

Quite a bit happened in the lines above. First, we told Phaser that we have a texture file, img/sheet.png, and that the framework should use the contents of img/sheet.json to find the location of frames within the image. img/sheet.json contains a map of frames that map names (i.e. planeBlue1.png) to distinct locations and sizes within the texture file, which contains the entire Tappy Plane pack compressed into a single image.

Next, we created a sprite that references planeBlue1.png within our sheet asset. As mentioned earlier, though a Sprite is just an image, in Phaser, we can use it to perform physics computations and another complex transformations.

Refresh the page to see the current game:

Phaser 3 game with airplane object

Animating the Sprite

The blue plane sprite included in Kenney’s Tappy Plane pack includes three frames, which can be combined to create an animation. Change the create function as follows:

function create() {
    this.anims.create({
        key: 'plane',
        repeat: -1,
        frameRate: 10,
        frames: this.anims.generateFrameNames('sheet', { start: 1,  end: 3, prefix: 'planeBlue', suffix: '.png' })
    });
        
    var plane = this.add.sprite(400, 300, 'sheet').play('plane');
}

Firstly, we register an animation configuration in Phaser’s AnimationManager. Using the generateFrameNames helper, we specify that the animation contains the frames planeBlue1.png, planeBlue2.png, and planeBlue3.png. The animation we create, named plane, can be applied to any sprite; however, for this example, we will only apply it to our plane sprite.

Next, we add .play('plane') to our instantiation of the plane sprite. Reload the page, and you’ll see the plane’s turbine spinning infinitely!

Note that the plane key corresponds to the key of the animation object we created.

Adding a Background

Of course, a game with a boring, empty background is (usually) a boring, empty game. Let’s add a background sprite, right before creating our plane. create should look like this:

function create() {
    this.anims.create({ ... });
    this.add.image(0, 0, 'sheet', 'background.png').setOrigin(0);
    var plane = this.add.sprite(400, 300, 'sheet').play('plane');
}

Note the setOrigin(0), which tells the image to position itself according to its top-left corner, rather than the middle (the default origin is 0.5).

Let’s take a look at the game now:

Phaser 3 game with airplane sprite and mountain background

Scaling the Game

As you’ve likely noticed, there’s still a lot of dead space. We can eliminate this by explicitly sizing the game canvas to the size of our background image, 800x480.

Modify the game configuration like so:

var config = {
    type: Phaser.WEBGL,
    parent: 'game',
    width: 800,
    height: 480,
    scene: {
        preload: preload,
        create: create
    }
};

And voila, empty space gone!

Phaser 3 airplane game with appropriate scaling

One caveat to mobile development is that there is no guarantee of the size of a screen. Even within the context of one device, a resize of the window or orientation switch can throw the scaling of your game completely off-balance. However, with some simple math, we can responsively resize our game. Change your create function as follows (source):

function create() {
    window.addEventListener('resize', resize);
    resize();

    // Earlier code omitted
}

Next, implement the resize function that auto-resize our game canvas:

function resize() {
    var canvas = game.canvas, width = window.innerWidth, height = window.innerHeight;
    var wratio = width / height, ratio = canvas.width / canvas.height;

    if (wratio < ratio) {
        canvas.style.width = width + "px";
        canvas.style.height = (width / ratio) + "px";
    } else {
        canvas.style.width = (height * ratio) + "px";
        canvas.style.height = height + "px";
    }
}

Now, the game will resize automatically when the window does!

Infinite Scrolling

As is the nature of Tappy Plane and Flappy Bird, our background should scroll infinitely. Fortunately, it’s simple to implement this in Phaser. First, let’s update our game configuration to point to an update function we will create. This function will be called once per frame.

var config = {
    type: Phaser.WEBGL,
    parent: 'game',
    width: 800,
    height: 480,
    scene: {
        preload: preload,
        create: create,
        update: update
    }
};

Next, let’s change our create function to declare the background image as a tile sprite instead. A tile sprite is a special sprite that can update its position relative to the camera, to create parallax/infinite scrolling phenomena.

this.bg = this.add.tileSprite(0, 0, 800, 480, 'sheet', 'background.png').setOrigin(0);

Lastly, let’s actually implement that update function:

function update() {
    this.bg.tilePositionX += 5;
}

 

Refresh the game, and you’ll see the background continuously scrolling, repeating itself once it reaches the end of the screen.

Note that though we are adding to tilePositionX, it looks as though the background is moving to the left. It helps to think of tile sprites as an endless wallpaper, that we are only viewing a certain piece of at a time. When tilePositionX increases, this is analogous to the viewer’s eyes moving to the right, which creates a parallax effect moving to the left.

Building the Game for iOS and Android

Now for the fun part: Running our game on mobile! First off, add the ios and android platforms to the codebase:

cordova platform add ios android

If this errors, you may need to delete package-lock.json.

Next, we can use cordova to run our app in an emulator:

cordova emulate ios

This will bring our app up in the iOS Simulator (granted, you’ll need to install that, which comes bundled with XCode, first):

iOS simulator running airplane flying Phaser 3 game

Distributing the Game

Lastly, we eventually will want to publish and distribute our game. Whether through the App Store, the Google Play Store, or another app distribution platform, our game needs to be downloadable and easily accessible in order for our users to get their hands on it.

Firstly, you’ll need to build your app in release mode. Try the following for iOS:

cordova build --release ios

The steps to build a properly signed application for iOS are quite intensive, and include the following steps:

  • Sign the application
  • Register an app ID
  • Make a provisioning profile
  • Modify app configuration
  • Submit to the App Store

This process is well summarized in this article; for the sake of not reinventing the wheel, I’ve linked to it, rather than rewriting content.

After that, you’re done! Good work.

Conclusion

Apache Cordova is a great solution for running applications originally aimed at web browsers on mobile devices. By wrapping such applications in webview wrappers, Cordova lets us produce a “native” (notice the quotes) experience across multiple platforms. This is great when writing games, because it eliminates the need for rewriting code to target different platforms.

With Phaser, we can create games with Cordova that run in the browser, iOS, and Android.

Stick around for the next tutorial, where we continue with the Tappy Plane Pack and turn this into a real game!