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!

Conversation
  • Karl says:

    I just wanted to say that this helped me a lot. I’ve been wanting to set up Phaser with TypeScript for a long time, but not found a reliable way of doing it that I felt comfortable with. This is great! Thank you!

    • Joe Bustamante Joe Bustamante says:

      So glad it was helpful for you Karl!

  • Wayne says:

    Great tutorial, thank you. I especially appreciated the webpack stuff.

    • Joe Bustamante Joe Bustamante says:

      Glad it was helpful! Yea, the webpack stuff is always tricky.

  • TomRaaff says:

    Thank you! This was just the startup I needed!

    • Joe Bustamante Joe Bustamante says:

      Thanks Tom! Glad it was helpful.

  • RolanVC says:

    Hello, thank you for this! Like the others, i’ve been wanting to use TS with phaser. And this has really helped. Just one question, though: where in your github code indicates the the BootScene is the first scene? I can’t figure just that one out:

    Thank you again!

    • Joe Bustamante Joe Bustamante says:

      Thanks Rolan! I’m so glad this was helpful. Scenes are actually loaded in the same order they appear in the scenes array (found in src/scenes/index.ts). Because BootScene is the first scene in that array, it’s the one that gets loaded at startup. Hope that helps!

  • Wolfos says:

    It’s not working for me. I get some huge illegible error from Webpack that says something along the lines of “options[0] misses the property ‘patterns'”

    • Wolfos says:

      I did a checkout of your sample project and got it working. No idea what I did wrong since I pretty much copy/pasted everything.

      • Joe Bustamante Joe Bustamante says:

        Huh, weird. It may be the case that some of the code in this post is slightly out-of-date. I’ve been updating the repo occasionally but maybe I forgot to do the code. I’ll run through it all and see if it needs changing. Glad that pulling down the sample project worked though!

        • Flavio says:

          Hi Joe! I ran into the same issue as Wolfos. However, simply pulling in the same versions for the devDependencies that you have in your github’s package.json seems to fix the issue. If I had to guess, it looks like your webpack.config.js properties may not be fully compatible with the newest versions of webpack, which are downloaded by default unless you specify specific versions when adding the dependencies in that first yarn add step.

          Overall very helpful guide; thanks for posting this!

          • Joe Bustamante Joe Bustamante says:

            Forgot to reply to this earlier, but yes, you were correct! The newest webpack-copy-plugin had changed its API. I recently pushed some changes that should fix this, but anyone feel free to let me know or open an issue in the repo if you still have trouble.

  • Yvan says:

    Hi Joe,

    Problem fixed : in the webpack.config.js, the new syntax for CopyPlugin should be :

    new CopyPlugin({
    patterns: [
    { from: ‘index.html’,},
    //{ from: ‘assets/**/*’,},
    ],
    }),

    Best regards

    • Joe Bustamante Joe Bustamante says:

      Hi Yvan, thanks for commenting. What problem were you seeing that removing the “{ from: ‘assets/**/*’ line fixes? I’ve got it working locally for me so just want to make sure there isn’t a cross-compatibility issue or anything.

      • Marcus says:

        LOG from copy-webpack-plugin
        unable to locate ‘assets\**\*’

  • Bunderant says:

    Heads up if anyone is having trouble getting this up and running:

    If you’re getting an error in ‘main.ts’ saying the phaser module can’t be found, go into ‘tsconfig.json’ and add this to the compilerOptions:

    “moduleResolution”: “Node”,

    Then, if you subsequently get the “unable to locate ‘assets\**\*’” error when trying to run, just add an “assets” directory at the root level, and put a placeholder file in there until you actually have some assets to use. I just put it a text file called “hey.txt” and was able to get it up and running.

  • Christian says:

    I could not get the code running with webpack 5 and all up to date dependencies, even with “webpack serve” as command instead of “webpack-dev-server”. I had always this problem: Error: Conflict: Multiple chunks emit assets to the same filename app.bundle.js (chunks app and vendors).

    I just copied your dependecies and it works, like suggested above.

  • Quneyin says:

    Why these explanations aren’t in the official documentation ?!

    Your article is the most helpful one I ever found. I’m using browserify instead of webpack but anyway, my point was to find a starting point for the code itself and it works like a charm.

    Thanks a lot!

  • Gilmarllen says:

    Those are facing the error: “Error: Conflict: Multiple chunks emit assets to the same filename app.bundle.js (chunks app and vendors).”

    Try to change the unninstall and install the dependencies in the following versions:

    “dependencies”: {
    “copy-webpack-plugin”: “^6.2.1”,
    “phaser”: “^3.52.0”,
    “webpack”: “^4.42.1”,
    “webpack-cli”: “^3.3.11”,
    “webpack-dev-server”: “^3.10.3”
    }

  • Joe Bustamante says:

    Hey everyone, author here – if you’ve got an issue with the code, please create an issue on the GitHub page since I no longer really check the comments on this post. I’ll do my best to reply there as things come up. I recently updated the starter kit to use Phaser 3.50 and TS 4, so lots of exciting changes there. Things work for me locally but I know some people on different OS’s have had issues in the past, so if you make an issue we can try and get those fixed. Thanks!

  • Comments are closed.