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");