• Skip to primary navigation
  • Skip to main content
  • Skip to primary sidebar
  • Skip to footer

The Coding Couple

Pair programming is a lifetime commitment.

  • Home
  • Categories
    • Arduino
    • JavaScript
    • Python
    • Raspberry Pi
  • About Us
    • Privacy Policy
  • Contact

Getting started with Phaser and ES2015

July 17, 2016 by Michael

If you are seeking an easy way to get started on a new Phaser game the ES2015 way, then this guide is for you.  In this guide we will first use jspm to quickly set up a new project with a module loader and an ES2015 transpiler.  We will then pull the latest Phaser package from the jspm registry and set up a basic game that highlights a few ways we can take advantage of ES2015 to make our code more modular. Finally, we will bundle the application so that it can be deployed as a static file.

The skeleton we create here is available on GitHub for you to use to start your own projects.

Install Node.js

If you don’t already have node.js then install it, because the next step requires npm.

Install jspm globally

jspm is a browser-side package manager that will not only help us download Phaser, but will provide us with a module loader polyfill integrated with a transpiler.  This means we will be able to take advantage of ES2015 features, even though browsers are still on ES5.  We will even be able to import Phaser objects just as if it was an ES2015 module!

npm install jspm -g

Create a new project

Let’s create a new project in a directory called phaser-es2015

cd phaser-es2015

Now we will install jspm as a development-only dependency of our project.  Installing globally gave us the command-line interface, but jspm recommends a local install as well.  This way our projects will be using the version of jspm they expect, even if it is older than the global version.

npm init

Say yes to the default options.

npm install jspm --save-dev
jspm init

Say yes to the default options.

Install Phaser

jspm install phaser

Create an entry point

Create a new index.html file at the root of the project.

<!doctype html>
<html>
    <head>
        <title>Phaser: The ES2015 Way</title>
    </head>
    <body>
        <main>

<div id="game-canvas"></div>

        </main>
    </body>
    
    <script src="jspm_packages/system.js"></script>
    <script src="config.js"></script>
    <script>
        System.import('src/main.js');
    </script>
</html>

The System.import() function dynamically loads and executes the main.js file. It becomes the entry point to our game.  From this point on, we can load modules statically using the import and export keywords.

Now, at the root of the project we create a src directory and add a main.js.

// src/main.js
import { Game } from './game';

new Game();

This file loads the module from src/game.js which is our Phaser game. If you wish to run other JavaScript on the page prior to starting the game, this is a good place to do it. Next, create the src/game.js file

Create the Game

// src/game.js

import Phaser from 'phaser';
import { Load } from './states/load';
import { Menu } from './states/menu';
import { Play } from './states/play';
import { GameOver } from './states/gameover';
import { Victory } from './states/victory';

export class Game extends Phaser.Game {
    constructor() {
        super(800, 600, Phaser.AUTO, 'phaser-canvas', { create: () => {
            this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;

            this.state.add('load', Load);
            this.state.add('menu', Menu);
            this.state.add('play', Play);
            this.state.add('gameover', GameOver);
            this.state.add('victory', Victory);

            this.state.start('load');
        }});
} 

The Game class extends Phaser.Game and is responsible for adding all of the states that the game will transition into. It also starts the initial state.  It is often a good idea to create a load state as your initial state and use it to load all of the required audio, images, and other media. While these things are loading the load state can display a loading message so the user knows that your game is not frozen. A typical game may also have a menu state, a state for game play, a victory state,
and a game over state.

Why does the Phaser import not use { }?

When a module exports something by name, we use this syntax to import it:


import { namedThing } from "myModule";

If we want to change the name, we can do it like this:


import { namedThing as myThing } from "myModule";

A module can also specify a nameless default export.  In that case, we use this syntax:


import myThing from "module";

There was a change made to the phaser jspm registry entry to set the Phaser object as the default export.  Before that change was made, SystemJS did export detection automatically, creating a named export called “Phaser”.  This was a very frustrating change because it broke all of my code that had been using the named export from Phaser 2.2.2 onward!

Special thanks to @vemek for noticing that the imports in the skeleton stopped working!

Create the States

All of our states extend Phaser.State and are in different files.

Load State

The load state described below is the first state and displays the message “Loading…” while the assets are loaded.  Until you add more assets, it is unlikely you will actually notice this screen when you start the game.  Once finished, it loads the menu state.

// src/states/load.js

import Phaser from 'phaser';

export class Load extends Phaser.State {
    preload() {
       let textStyle = {font: '45px Arial', alight: 'center', stroke: 'blue', fill: 'blue'};
        
       this.game.add.text(80, 150, 'loading...', textStyle);    
       this.game.load.spritesheet('wizard', '../assets/wizardsprite.png', 95, 123, 6);         
    }
    
    create() {
        this.game.state.start('menu');             
    }   
}

Menu State

Most games will also want a menu screen.  For this skeleton the menu just has some instructions to read before pressing ‘s’ to start the game.  The object of the game is to press spacebar before time runs out.  Very exciting!

// src/states/menu.js

import Phaser from 'phaser';

export class Menu extends Phaser.State{
    create() {
        let textStyle = {font: '45px Arial', alight: 'center', stroke: 'blue', fill: 'blue'};
        
        let title = this.game.add.text(this.game.world.centerX, this.game.world.centerY - 100, 'ES2015 Wizard', textStyle);
        title.anchor.set(0.5);
        
        textStyle.font = '36px Arial';
        
        let instructions = this.game.add.text(this.game.world.centerX, this.game.world.centerY, '"s" key to start', textStyle);
        instructions.anchor.set(0.5);
         
        let controlMessage = this.game.add.text(this.game.world.centerX, this.game.world.centerY + 150, 'use arrow keys to move', textStyle);

        controlMessage.anchor.set(0.5);
        
        let muteMessage = this.game.add.text(this.game.world.centerX, this.game.world.centerY + 225, '"SPACEBAR" to win.', textStyle);
        muteMessage.anchor.set(0.5);
        
        let sKey = this.game.input.keyboard.addKey(Phaser.KeyCode.S);
        sKey.onDown.addOnce( () => this.game.state.start('play'));       
    }
}

Play State

Once ‘s’ is pressed from the menu state we transition to the play state.  Here we add a simple wizard sprite.  The logic related to the wizard’s movement will be covered later, as it is encapsulated in the wizard module.  If 20 seconds pass without pressing spacebar we transition to gameover state.  If spacebar is pressed in time we transition to the victory state.

// src/states/play.js

import Phaser from 'phaser';
import { Wizard } from '../sprites/wizard';;

export class Play extends Phaser.State {    
    create() {     
        this.game.physics.startSystem(Phaser.Physics.ARCADE); 
               
        this.wizard = new Wizard(this.game, 350, 300);
        this.game.add.existing(this.wizard);              
        
        this.cursors = this.game.input.keyboard.createCursorKeys();       
        this.game.stage.backgroundColor = "#FFFFFF";        
        this.game.input.keyboard.addKeyCapture([ Phaser.KeyCode.SPACEBAR ]);
    }
    
    update() {
        if (!this.startTime) {
            this.startTime = Date.now();
        }

        //20 seconds to win
        if ((Date.now() - this.startTime) > 20000) {
            this.startTime = 0;               
            this.game.state.start('gameover');
        }

        this.wizard.move(this.cursors);

        if (this.game.input.keyboard.isDown(Phaser.KeyCode.SPACEBAR)) {   
            this.startTime = 0;               
            this.game.state.start('victory');
        }            
    }  
}

Victory State

The victory state simply informs the user that she has won, and to press ‘s’ to play again.  Pressing ‘s’ transitions the game back into the play state.

// src/states/victory.js

import Phaser from 'phaser';

export class Victory extends Phaser.State{
    create() {
        let textStyle = {font: '45px Arial', alight: 'center', stroke: 'black', fill: 'red'};
        
        let title = this.game.add.text(this.game.world.centerX, this.game.world.centerY - 100, 'Victory!', textStyle);
        title.anchor.set(0.5);
        
        textStyle.font = '36px Arial';
        
        let instructions = this.game.add.text(this.game.world.centerX, this.game.world.centerY, '"s" key to play again', textStyle);
        instructions.anchor.set(0.5);
        
        let sKey = this.game.input.keyboard.addKey(Phaser.KeyCode.S);
        sKey.onDown.addOnce( () => this.game.state.start('play'));
    }
}

Gameover State

The gameover state is similar to the victory state, except it tells the user that she has lost.

// src/states/gameover.js

import Phaser from 'phaser';

export class GameOver extends Phaser.State{
    create() {
        let textStyle = {font: '45px Arial', alight: 'center', stroke: 'red', fill: 'red'};
        
        let title = this.game.add.text(this.game.world.centerX, this.game.world.centerY - 100, 'GAME OVER', textStyle);
        title.anchor.set(0.5);
        
        textStyle.font = '36px Arial';
        
        let instructions = this.game.add.text(this.game.world.centerX, this.game.world.centerY, '"s" key to play again', textStyle);
        instructions.anchor.set(0.5);
        
        let sKey = this.game.input.keyboard.addKey(Phaser.KeyCode.S);
        sKey.onDown.addOnce( () => this.game.state.start('play'));
    }
}

Create the Sprite

One advantage of using ES2015, is that it is easy to encapsulate each sprite’s logic into its own module.  In this skeleton we choose to put them in a sprites directory.  In this case we have a single wizard sprite module that sets up the animations and physics upon construction.  The wizard sprite defines a move method that can be called during the game update loop in order to enable keyboard movement.

// src/sprites/wizard.js

import Phaser from 'phaser';

export class Wizard extends Phaser.Sprite { 
    constructor(game, x, y) {
        super(game, x, y, 'wizard');
        this.anchor.setTo(0.5, 0.5);    
        this.scale.setTo(0.65, 0.65);
        this.animations.add('right', [0,1,2]);        
        this.animations.add('left', [3,4,5]);
        
        this.game.physics.enable(this, Phaser.Physics.ARCADE);
        
        this.body.drag.set(100);
        this.body.maxVelocity.set(500);  
        this.body.collideWorldBounds = true;        
        
        this.body.width -= 32;
        this.body.height -= 32;             
    }
    
    move(cursors) {     
        if (cursors.up.isDown) {
            this.body.acceleration.y = -300;                   
        } else if (cursors.down.isDown) {            
            this.body.acceleration.y = 300;           
        } else {
            this.body.acceleration.y = 0;
        }
        if (cursors.left.isDown) {       
            this.body.acceleration.x = -300;
            this.animations.play('left', 4, true);
        } else if (cursors.right.isDown) {          
            this.body.acceleration.x = 300;
            this.animations.play('right', 4, true);
        } else {            
            this.body.acceleration.x = 0;
        }        
    }       
}

Create the npm start script to run the game

Next, we modify the packages.json to add a start script that kicks of a local http server that will host our game.  The -o option tells it to open a browser window automatically, the c option disables caching by setting the cache time to -1.  This is to prevent the browser from holding on to old code after we make changes.

To start the game go to the project root directory an execute the command

npm start

{
  "name": "phaser-es2015",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "http-server -o -c-1"
  },
  "author": "",
  "license": "MIT",
  "devDependencies": {
    "jspm": "^0.16.34"
  },
  "jspm": {
    "dependencies": {
      "phaser": "github:photonstorm/phaser@^2.4.8"
    },
    "devDependencies": {
      "babel": "npm:babel-core@^5.8.24",
      "babel-runtime": "npm:babel-runtime@^5.8.24",
      "core-js": "npm:core-js@^1.1.4"
    }
  }
}

Create the npm build script to bundle the game

As the game gets larger, it will probably start to load too slowly.  Bundling the game into a single, minified, file will reduce the number and size of HTTP requests the browser must make. Bundling should make a huge difference.

jspm can do this for us with the following command from the root directory

jspm bundle src/main.js game-bundle.js --minify

This will create a new file called game-bundle.js that we must load after system.js/config.js, but before the first import.


<!doctype html>
<html>
<head>
<title>Phaser: The ES2015 Way</title>
</head>
<body>
<main>
    <div id="game-canvas"></div>
</main>
</body>

<script src="jspm_packages/system.js"></script>
<script src="config.js"></script>
<script src='./game-bundle.js'></script>;
<script>
System.import('src/main.js');
</script>
</html>

To save key strokes we can add the build command to packages.json as well.


"scripts": {
    "start": "http-server -o -c-1",
    "build": "jspm bundle src/main.js game-bundle.js --minify"
},

The start script is special in npm and works with the short-hand command

npm start

But this will not work for build, the command must be

npm run build

Conclusion

Although this setup appears to be longer in lines of code than many other single-file Phaser samples, I have found that with this modular approach, adding complexity to the game comes naturally and doesn’t require as much refactoring along the way.

Related Posts

  • Surviving the Hackathon:  Ludum Dare 35Surviving the Hackathon: Ludum Dare 35
  • Pokémon Color Picker | A web app built with HTML/CSS + JavaScriptPokémon Color Picker | A web app built with HTML/CSS + JavaScript
  • .mjs extension (A JavaScript module file) | Today I Learned.mjs extension (A JavaScript module file) | Today I Learned
  • Surviving the Hackathon:  Angular Attack 2016Surviving the Hackathon: Angular Attack 2016
  • There’s a name for that:  the Kebab CaseThere’s a name for that: the Kebab Case
  • 3 JavaScript Gotchas for the C# Developer3 JavaScript Gotchas for the C# Developer

Filed Under: JavaScript Tagged With: es2015, es6, game development, indie dev, javascript, jspm, phaser, skeleton, systemjs, tutorial

Previous Post: « How to watermark images with Python and Pillow (PIL)
Next Post: Creation Crate Month 3: An Arduino Powered Distance Detector »

Reader Interactions

Comments

  1. Billy says

    August 19, 2016 at 7:44 pm

    Thanks for the tutorial and code.

    For some reason I can’t get the http-server running. I assume that needs to be installed via npm?

    Thanks!

  2. Alexey Romanov says

    August 21, 2016 at 5:53 pm

    @Billy:
    Yes, you can install http-server globally:
    npm install -g http-server

Primary Sidebar

Social Media

  • GitHub
  • Instagram
  • Twitter
  • YouTube

Recent Posts

  • Pokémon Color Picker | A web app built with HTML/CSS + JavaScript
  • Pokéball Single DIV CSS Drawing | Tutorial
  • Error: [🍍]: “getActivePinia()” was called but there was no active Pinia
  • Trijam #261 Game Jam Diary: One Wrong Move
  • Using WSL on Corporate VPN

Recent Comments

  • Lizzy on Creation Crate Month 2: An Arduino Powered Memory Game
  • Ashley Grenon on Creation Crate Month 2: An Arduino Powered Memory Game
  • Lizzy on Creation Crate Month 2: An Arduino Powered Memory Game
  • kelly on Creation Crate Month 2: An Arduino Powered Memory Game
  • Ashley on Creation Crate Month 3: An Arduino Powered Distance Detector

Follow us on Instagram!

This error message is only visible to WordPress admins

Error: No feed found.

Please go to the Instagram Feed settings page to create a feed.

Categories

  • Arduino
  • Conferences
  • Debugging
  • Game Jams
  • HTML and CSS
  • JavaScript
  • Programming Languages
  • Python
  • Raspberry Pi
  • Today I Learned

Archives

  • May 2024
  • April 2024
  • March 2024
  • May 2022
  • December 2021
  • May 2021
  • March 2020
  • January 2020
  • December 2019
  • November 2019
  • October 2019
  • June 2019
  • April 2019
  • September 2017
  • April 2017
  • August 2016
  • July 2016
  • June 2016
  • May 2016
  • April 2016
  • March 2016
  • April 2015
  • January 2015

Tags

adafruit arduino brackets c# code smell codestock coding standards conventions creation crate debugging developer devspace electronics es6 es2015 game development game jam gotcha hackathon hoisting html html5 javascript led naming conventions nintendo phaser pluralsight pokemon programmer python raspberry pi retro retropie scope self improvement single div single div drawing subscription box TIL today I learned troubleshooting vue vuejs windbg

Footer

About Us

We are the Coding Couple.  Two people who met in college and decided they wanted to pair program for the rest of their ...

Read More »

Most Recent Posts

Pokémon Color Picker | A web app built with HTML/CSS + JavaScript

Pokéball Single DIV CSS Drawing | Tutorial

Error: [🍍]: “getActivePinia()” was called but there was no active Pinia

Trijam #261 Game Jam Diary: One Wrong Move

Social Media

  • GitHub
  • Instagram
  • Twitter
  • YouTube

Copyright Notice

© The Coding Couple, 2015 – 2023. Excerpts and links may be used, provided that full and clear credit is given to The Coding Couple with appropriate and specific direction to the original content.

Copyright © 2025 · Foodie Pro Theme by Shay Bocks · Built on the Genesis Framework · Powered by WordPress