Setting up a Phaser 3 Project

Here’s how I set up a Phaser 3 project from scratch.

At the time this was written, the project was set up using the following versions of software:

  • Phaser 3.54
  • Babel 7.13
  • Webpack 5.28

When I work on a Phaser 3 project, I use JavaScript and like using ES6 features like classes, arrow functions, and class properties, but some older browsers don’t support these features. In order to make sure the project runs on older browsers, the code has to be transpiled. This is where Webpack and Babel come in.

Installing packages

We’ll be using npm to install these packages. First, make a new folder or navigate to the folder that will be the root project folder. Then inside that folder, run the following:

npm init -y

Next, we need to install Webpack. If you’re working on a project with several files where some of them have dependencies on one another, it can be very tedious to manage. Webpack can bundle everything together nicely.

npm install --save-dev webpack webpack-cli

I also like to install webpack-dev-server, which lets you have a development server with live reloading when making changes to your code.

npm install --save-dev webpack-dev-server

Now we install Babel packages. The preset-env package is in charge of transpiling modern (ES6+) JavaScript into ES5. The plugin-proposal-class-properties package is needed to support class properties, which at the time of this writing isn’t handled by preset-env.

npm install --save-dev @babel/core babel-loader @babel/preset-env @babel/plugin-proposal-class-properties

There’s one more package I like to add, which is terser-webpack-plugin. This plugin can minify your code and has the option to remove comments, which is nice to have.

npm install --save-dev terser-webpack-plugin

Finally, we’ll install Phaser by running the following:

npm install phaser

Now all the necessary packages should be installed.

Files setup

In the newly generated package.json file, we add the following commands:

"scripts": {
    "start": "webpack serve --color --mode=development",
    "build": "webpack --mode=production"
}

Now we add a config file for Babel. I add a .babelrc.json file, but you can also add a babel.config.json file instead, which works as a project-wide configuration instead. More information can be found on the Babel page for config files. The file should have the following:

{
    "presets": ["@babel/preset-env"],
    "plugins": ["@babel/plugin-proposal-class-properties"]
}

To configure Webpack, we add a webpack.config.js file with the following:

const path = require("path");
const TerserPlugin = require("terser-webpack-plugin");

const DIR_DIST = path.join(__dirname, "dist");

const webpackConfig = {
    module: {
        rules: []
    },
    optimization: {
        minimizer: [
            new TerserPlugin({
                terserOptions: {
                    output: {
                        comments: false
                    }
                },
                extractComments: false
            })
        ]
    },
    devServer: {
        contentBase: DIR_DIST
    }
};

const babelConfig = {
    test: /\.(js|jsx)$/,
    exclude: /node_modules/,
    use: ["babel-loader"]
};
webpackConfig.module.rules.push(babelConfig);

module.exports = webpackConfig;

A few things to note here. To set up the Terser plugin for Webpack, I follow the instructions on the plugin page here. The webpack-dev-server has setup instructions here. Then for Babel, we create a new configuration object called babelConfig, and add it to the module rules in the webpack config object.

Project structure

The way we’ve set up Webpack assumes there’s a folder called “dist” in the root directory. Furthermore, we’ll want to add an index.html folder in this folder to actually have the game work. Make a new folder called dist, and in the folder add index.html with the following:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Game</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
</head>
<body>
    <div id="game-container"></div>
    <script src="main.js"></script>
</body>
</html>

Webpack by default will generate a main.js file when bundling your project, so we can reference that in the script element. The game-container div will be referenced later in the Phaser configuration.

One more step, let’s add a folder called src, and in the folder add an index.js file that will contain our actual Phaser code. Then add an assets folder inside dist for holding any game assets like sprites and sounds. Our project structure should look like this now:

.
├── dist
│   ├── assets
│   └── index.html
├── node_modules
├── src
│   └── index.js
├── .babelrc.json
├── package-lock.json
├── package.json
└── webpack.config.js

Since our entry point is now src/index.js, let’s update package.json‘s main property to the following:

"main": "src/index.js"

Phaser code setup

Once you’ve finished setting up the project, you’re free to start writing Phaser code in any way you want. I usually start by making index.js the place where the game configuration is set up, then make a src/js folder for game scenes. Here’s an example:

Inside index.js:

import "phaser";
import MainMenuScene from "./js/MainMenuScene";
import GameplayScene from "./js/GameplayScene";

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    parent: "game-container",
    scale: {
        mode: Phaser.Scale.FIT,
        autoCenter: Phaser.Scale.CENTER_HORIZONTALLY
    }
};

class Game extends Phaser.Game{
    constructor(){
        super(config);
        this.scene.add("MainMenu", MainMenuScene);
        this.scene.add("Gameplay", GameplayScene);
        this.scene.start("MainMenu");
    }
}

window.onload = function(){
    const game = new Game();
};

(Note: I set the config’s parent to “game-container”, which is the id of the div element in index.html)

Inside MainMenuScene.js:

import "phaser";

class MainMenuScene extends Phaser.Scene {

    constructor(){
        super("MainMenu");
    }

    preload(){
    }

    create(){
        this.startButton = this.add.text(this.cameras.main.centerX, this.cameras.main.centerY, "Start", {fontSize: 32});
        this.startButton.setOrigin(0.5);
        this.startButton.setInteractive().on("pointerdown", this.onStartButtonClicked);
    }

    onStartButtonClicked = (pointer, localX, localY, event) => {
        this.scene.start("Gameplay");
    }

    update(){
    }

}

export default MainMenuScene;

(Note: the onStartButtonClicked function is a class property, which is why I install @babel/plugin-proposal-class-properties)

Inside GameplayScene.js:

import "phaser";

class GameplayScene extends Phaser.Scene {

    constructor(){
        super("Gameplay");
    }

    create(){
    }

    update(){
    }

}

export default GameplayScene;

At the end, run npm run start to start a local dev server, then navigate to http://localhost:8080 to test the game. You should see a black canvas with a “Start” text in the middle, which you can click on to go to the Gameplay scene. Since we have webpack-dev-server installed and running, you can freely make changes to your code without having to constantly restart the local server.

In the end, your project structure should look like this:

.
├── dist
│   ├── assets
│   └── index.html
├── node_modules
├── src
│   ├── js
│   │   ├── GameplayScene.js
│   │   └── MainMenuScene.js
│   └── index.js
├── .babelrc.json
├── package-lock.json
├── package.json
└── webpack.config.js

Later on when adding sprites and other assets, assuming you use the structure above, you can reference them from inside any js file with the path assets/path/to/file. For example, if you have a sprites folder in assets, then in a scene’s preload() function, you can load a sprite like so: this.load.image("player", "assets/sprites/player.png");

Leave a Reply

Your email address will not be published. Required fields are marked *