1 Comment

Making Your First Game in Phaser 3 with TypeScript

I recently got a chance to use Phaser 3 and TypeScript to build Root Maker with my Ludum Dare team. It worked out great. Phaser is one of the best frameworks around for HTML5 game development, and it’s definitely worth checking out if you haven’t.

I wanted to write this tutorial to help others get their Phaser 3 and TypeScript projects up and running as quickly as possible. If you’d rather just see the code, you can find a link to the repository at the end of the post.

1. Setting Up your Environment

IDE

Phaser is compatible with any editor or IDE. Visual Studio Code tends to be one of the better options because of its built-in TypeScript support, but feel free to pick your preferred environment.

Node

For this tutorial, we’ll be using Node.js with Yarn as a package manager, but this method will also work with npm. We’ll need to use Node since we’ll be developing in TypeScript, and using Yarn will help us manage and install all of the dependencies we’ll need for our game.

Setup

First, you’ll want to create a new directory for your game. Once you have a new folder, type yarn init to go through the process of creating a package.json file. This will contain all of the information about your project and its dependencies. You can answer the init questions however you want – you can edit all of your answers later.

Next, we’ll need to get all of our dependencies. We’re going to be using webpack to bundle our files and run our test server, and we’re also going to need to grab the Phaser and TypeScript modules. (I’m assuming some familiarity with the Node ecosystem here, but if all of this sounds like another language to you, keep reading; I’ll explain everything you need to do as we go).

To get all of these, run these two commands:

yarn add --dev copy-webpack-plugin ts-loader typescript webpack webpack-cli webpack-dev-server
yarn add phaser

The first command will install all of the modules we need to run and test our code locally. It will also bundle them into files we can eventually host online for people to play. All of those modules are added to our development dependencies. One important note is that we’re using webpack-dev-server to run our local server.

The second command installs Phaser as a dependency, and it’s one that we’ll need outside of just a development environment. The Phaser repository, as of version 3.17, now includes the type definitions by default. Because of that, installing Phaser via Yarn means we’ll automatically have types for Phaser.

Configuring webpack

We’ll need to tell webpack how it should compile our game. We added a few dependencies to help do this already, like the ts-loader plugin and the copy-webpack-plugin. However, webpack also requires a webpack.config.js file to understand how to compile our project.

webpack config files are almost a separate language unto themselves, and they can be pretty confusing for someone who hasn’t worked with them before. Rather than go through each line of what we need, copy the contents of this file from the starter kit I wrote into a file called webpack.config.js in your root directory.

As a brief overview, the first part of the file (from the entry to the output property) is just telling webpack where our project starts, where the compiled version should be output, and how to resolve TypeScript files. The latter part, under plugins, defines some of the webpack additions it needs in order to work with Phaser.

Configuring TypeScript

Similar to webpack, TypeScript also needs a tsconfig.json file to help the TypeScript compiler understand how to handle your project. Create a new file in your root directory with that file name, and type this:

{
  "compilerOptions": {
    "target": "es5",
    "sourceMap": true
  },
  "include": [
    "**/*.ts"
  ]
}

This tells TypeScript which files it should try to compile and which version of JavaScript to shoot for.

Adding a Script to Run our Game

At this point, our project infrastructure should be pretty much set up. All we need is a way to actually run our game. Primarily, we need to tell webpack when to compile our code and host it via a local server.

To do this, add this code to your package.json file before the dependencies section:

"scripts": { 
  "build": "webpack", 
  "dev": "webpack-dev-server" 
},

Now all we have to do is type yarn dev, and webpack will automatically compile our code and host a server for us. You can even keep this running while you work–it will automatically rebuild whenever it detects a code change.

2. Creating Your Game

Now that our project infrastructure is all good to go, we can start actually coding. It’s definitely worth it to spend some time combing through Phaser’s documentation and examples, but I’ll give a brief overview of how Phaser works here.

At a high level, every game in Phaser has a single Game object that contains information about the game and how it should run. A Game object has a list of Scene objects that make up a game.

You can think of a scene as the thing on your screen at any given time, whether that be the main menu, a certain level, or a “Game Over” message. However, scenes are versatile. Not only can small things be their own scene, but you can have multiple scenes visible at the same time (we won’t cover that in this tutorial, though).

Creating a HTML Container

Let’s start by creating an HTML file where our game can live. Create a new file called index.html, and add this code to it:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Sample Project</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
    <meta name="HandheldFriendly" content="True">
    <meta name="MobileOptimized" content="320">
    <meta http-equiv="cleartype" content="on">
    <style>
      html,
      body,
      canvas {
        margin: 0 !important;
        padding: 0 !important;
        overflow: hidden !important;
      }
    </style>
  </head>
  <body>
    <div id="content"></div>
    <script src="vendors.app.bundle.js"></script>
    <script src="app.bundle.js"></script>
  </body>
</html>

This code creates a container div where our game will live and also adds some styling information to help format our game.

You’ll notice there are two script tags. If you go back to our webpack.config.js file, you’ll see that there is an optimization section at the bottom where we tell webpack to build our node_modules directory separate from the code that we actually write.

We do this for performance reasons. Whenever we change our code, webpack will recognize that it doesn’t have to re-bundle all of our dependencies. This becomes especially useful as you add more dependencies to a project later on.

Creating our Game Object

Now it’s time to actually write some code. Create a new directory in your project called src, and create a file in that directory called main.ts. This is where we’ll define our Game object.

Add this code to the file:

import * as Phaser from 'phaser';

const gameConfig: Phaser.Types.Core.GameConfig = {
  title: 'Sample',
 
  type: Phaser.AUTO,
 
  scale: {
    width: window.innerWidth,
    height: window.innerHeight,
  },
 
  physics: {
    default: 'arcade',
    arcade: {
      debug: true,
    },
  },
 
  parent: 'game',
  backgroundColor: '#000000',
};
 
export const game = new Phaser.Game(gameConfig);

In this code, we’re creating a configuration object, and then passing that in to the constructor for Phaser.Game (the aforementioned Game object). This creates a game that’s the same size as the window where you run it. If you run your game now by typing yarn dev into your terminal and then going to localhost:8080 in your browser, you should see a black screen. While it isn’t very appealing, it does mean that your game is running!

3. Your First Scene

A Game object may be the thing that keeps track of everything going on with your game, but individual Scene objects are what make your game actually feel like a game.

So, we need to create a scene. In your main.ts file, add this code above your gameConfig (but below where you import Phaser):

const sceneConfig: Phaser.Types.Scenes.SettingsConfig = {
  active: false,
  visible: false,
  key: 'Game',
};

export class GameScene extends Phaser.Scene {
  private square: Phaser.GameObjects.Rectangle & { body: Phaser.Physics.Arcade.Body };
 
  constructor() {
    super(sceneConfig);
  }
 
  public create() {
    this.square = this.add.rectangle(400, 400, 100, 100, 0xFFFFFF) as any;
    this.physics.add.existing(this.square);
  }
 
  public update() {
    // TODO
  }
}

Once we’ve defined a new scene, we need to tell our Game object about it. A Game object has a property called scene that can take in either a single scene or a list of scenes. For now, we only have one scene, so add this line inside of your gameConfig object:

// ... gameConfig {
  scene:  GameScene,
// ... }

All of a sudden, our game is a bit more exciting. If you run this now, you should see a white square appear on your screen. In the code above, we’re defining a scene, then telling it to create a white square at 400, 400 with a width and height of 100 pixels. The create method is called automatically by scenes whenever they are started, and it the best place to create any game objects the scene needs.

Another important method is the update method, which gets called each tick and should be used to update the state of the scene or its game objects. We’ll get to that method in the next section.

You’ll also notice in the code above that we create a field for the square on our GameScene. We can reference this later when we add input controls. In Phaser, you can add physics-enabled game objects by calling this.physics.add rather than this.add. Unfortunately, at the time of writing this tutorial, there is no built-in physics rectangle factory, so we first have to create the rectangle and then add a physics body after by calling this.physics.add.existing on the square.

4. Adding Movement

We have a game, a scene, and a square. All that we need now is a way for the player to provide input to the game. To do this, we’re going to change the update method of our GameScene class. Replace the // TODO with the following code:

const cursorKeys = this.input.keyboard.createCursorKeys();
 
if (cursorKeys.up.isDown) {
  this.square.body.setVelocityY(-500);
} else if (cursorKeys.down.isDown) {
  this.square.body.setVelocityY(500);
} else {
  this.square.body.setVelocityY(0);
}
 
if (cursorKeys.right.isDown) {
  this.square.body.setVelocityX(500);
} else if (cursorKeys.left.isDown) {
  this.square.body.setVelocityX(-500);
} else {
  this.square.body.setVelocityX(0);
}

If you run your game now, the square should move in different directions as you press down on the arrow keys. This code uses Phaser’s built-in Arcade physics system to set the velocity of our square. We’re missing some things, like checking to make sure our square stays within our screen boundaries or adding other objects to collide with, but this simple code should get you started on implementing the actual mechanics of your game.

Conclusion

This tutorial was meant to be a brief overview of how to start developing in Phaser 3 with TypeScript. You can go to the GitHub repository for the starter kit I wrote to download the source code for this tutorial. It contains everything we covered, as well as some other useful tips about switching between scenes and menu buttons. Additionally, you can use it as a guide for how you might want to organize your project as you start adding more files.

I hope this tutorial was helpful and that you’ll feel like a Phaser pro in no time. Be on the lookout for more tutorials coming soon about more advanced topics with Phaser!