How to Build a Simple Game in the Browser with Phaser 3 and TypeScript

Browser-based games have come a long way in recent years. Frameworks like Phaser make it easier than ever for developers to create engaging web games without needing to be graphics programming experts. Combining Phaser with TypeScript allows you to leverage the power of static typing to write cleaner and more maintainable game code.

In this in-depth guide, we‘ll walk through building a complete game in Phaser 3 and TypeScript from start to finish. Whether you‘re new to browser game development or looking to level up your skills, this post will equip you with the knowledge you need to start making games.

Why Phaser and TypeScript?

Phaser is a well-established and popular open source framework for making HTML5 games in JavaScript. Created by Richard Davey of Photon Storm, Phaser streamlines 2D game development by providing a robust suite of tools and APIs for graphics, physics, input, sound, and more.

Compared to lower-level graphics APIs like WebGL and Canvas, Phaser is much easier to get started with as a web developer. It provides sensible defaults and abstracts away many of the details of cross-browser support and performance optimization. At the same time, it‘s extremely flexible and not overly opinionated, giving you full control over your game.

Phaser 3, released in 2018, was a major update that modernized the framework and added powerful new features. It introduced a scene-based architecture, enhanced physics systems, a unified loader, and more. Phaser games can be compiled to native desktop and mobile apps, opening up even more possibilities.

While Phaser is written in plain JavaScript, the Phaser community maintains TypeScript definitions that allow the framework to be used seamlessly from TypeScript. TypeScript is a statically typed superset of JavaScript from Microsoft that compiles to plain JavaScript.

For game programming, using TypeScript provides some notable advantages:

  • The strong typing catches bugs at compile-time before they cause runtime errors
  • Code editors provide richer autocompletion, refactoring, and inline documentation
  • Defining interfaces for game objects and config makes the codebase more understandable
  • ES6+ features like classes, imports, and async/await are supported

TypeScript has seen rapid adoption by the JavaScript community in recent years. Frameworks like Angular use it as a first-class language. Many developers find the productivity benefits far outweigh the initial learning curve.

By combining Phaser with TypeScript, we get a powerful yet accessible technology stack for game development that is fun to use and allows us to write high-quality code. So let‘s dive in and start building!

Setting Up a Phaser/TypeScript Project

Before we start coding, we need to configure our project and install some dependencies. I recommend using Visual Studio Code as your code editor since it has excellent built-in TypeScript support, but any editor will work.

First, create a new directory for the project and initialize it as an npm package:

mkdir phaser3-typescript-game
cd phaser3-typescript-game
npm init -y

Next, install Phaser 3 along with some development dependencies:

npm install phaser
npm install --save-dev typescript webpack webpack-cli ts-loader webpack-dev-server

Here‘s what each of these dependencies does:

  • phaser is obviously the game framework itself
  • typescript is the TypeScript compiler
  • webpack is a module bundler that will compile our TS code and assets into a browser-friendly build
  • ts-loader allows Webpack to transpile TypeScript
  • webpack-dev-server provides a local development server with live reloading

With the dependencies in place, let‘s add some configuration files. Create a tsconfig.json file in the root to configure the TS compiler:

{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "sourceMap": true
  },
  "include": [
    "src/**/*"
  ]
}

And add a webpack.config.js to configure the build process:

const path = require(‘path‘);

module.exports = {
  entry: ‘./src/index.ts‘,
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: ‘ts-loader‘,
        exclude: /node_modules/
      }
    ]
  },
  resolve: {
    extensions: [ ‘.ts‘, ‘.tsx‘, ‘.js‘ ]
  },
  output: {
    filename: ‘game.js‘,
    path: path.resolve(__dirname, ‘dist‘)
  },
  mode: ‘development‘
};

This config tells Webpack to compile the src/index.ts file and all its dependencies into a dist/game.js bundle. It also configures TypeScript compilation and sourcemaps.

Finally, add some NPM scripts to your package.json for ease of development:

"scripts": {
  "start": "webpack serve --config webpack.config.js",
  "build": "webpack --config webpack.config.js"
}

We now have a complete project setup that‘s ready for game development! Run npm start and Webpack will launch a dev server at http://localhost:8080/.

Creating the Game

Time for the fun part – actually coding our game! We‘ll be making a simple space-themed endless runner where the player tries to survive as long as possible while dodging oncoming asteroids.

Game Configuration

Let‘s start by creating a src/index.ts file that will serve as our game‘s entry point. Here we‘ll configure Phaser with some basic settings:

import ‘phaser‘;

const config: Phaser.Types.Core.GameConfig = {
  type: Phaser.AUTO,
  width: 800,
  height: 600,
  physics: {
    default: ‘arcade‘,
    arcade: {
      gravity: { y: 200 }
    }
  },
  scene: {
    preload: preload,
    create: create,
    update: update
  }
};

new Phaser.Game(config);

This sets up a basic 800×600 Phaser game using Arcade physics. The scene property defines the game loop methods. These can go in the same file for simple games:

function preload() {
  // Load assets
}

function create() {
  // Initialize game objects  
}

function update() {
  // Main game loop
}

Adding the Player

Next, let‘s create a Player class to represent our spaceship character. Make a new file src/player.ts:

export default class Player extends Phaser.Physics.Arcade.Sprite {
  constructor(scene: Phaser.Scene, x: number, y: number) {
    super(scene, x, y, ‘player‘);
    scene.add.existing(this);
    scene.physics.add.existing(this);

    this.setCollideWorldBounds(true);
  }

  moveLeft() {
    this.setVelocityX(-200);
  }

  moveRight() {
    this.setVelocityX(200);
  }
}

Our player builds on the Arcade physics Sprite class. It has basic left/right movement methods. We position it in the middle of the screen and make sure it can‘t move outside the game area.

In preload, load the player‘s image asset:

function preload() {  
  this.load.image(‘player‘, ‘assets/player.png‘);
}

And instantiate it in create:

let player: Player;

function create() {
  player = new Player(this, 400, 500);
}

Handling Input

To let the player control the ship, we need to wire up Phaser‘s input events. Modify create to set up the cursor key bindings:

function create() {
  // ...

  const cursors = this.input.keyboard.createCursorKeys();

  this.input.keyboard.on(‘keydown-LEFT‘, () => {
    player.moveLeft();
  });

  this.input.keyboard.on(‘keydown-RIGHT‘, () => {
    player.moveRight();  
  });
}

Spawning Obstacles

Now we need something for the player to dodge! Let‘s create an Obstacle class similar to the Player:

export default class Obstacle extends Phaser.Physics.Arcade.Sprite {  
  constructor(scene: Phaser.Scene, x: number, y: number) {
    super(scene, x, y, ‘obstacle‘);
    scene.add.existing(this);
    scene.physics.add.existing(this);

    this.setVelocityY(100);
  }
}

Obstacles will spawn at the top of the screen and fall downwards. We‘ll generate them on a timer in the update loop:

const obstacleSpawnRate = 3000; // spawn every 3 seconds
let lastObstacleTime = 0;

function update(time: number) {  
  const elapsedTime = time - lastObstacleTime;

  if (elapsedTime > obstacleSpawnRate) {
    lastObstacleTime = time;

    const x = Phaser.Math.Between(50, 750);
    const y = 0;    
    const obstacle = new Obstacle(this, x, y);
  }
}

Finally, add collision detection between the obstacles and player:

function create() {
  // ...

  this.physics.add.overlap(player, obstacles, handleObstacleCollision, null, this);
}

function handleObstacleCollision() {
  this.physics.pause();
  player.setTint(0xff0000);  
  gameOver = true;
}

If the player touches an obstacle, the game ends.

Displaying Score

As a final touch, let‘s display the player‘s survival time as a score. Create a text object in create:

let scoreText: Phaser.GameObjects.Text;

function create() {  
  // ...

  scoreText = this.add.text(16, 16, ‘score: 0‘, { 
    fontSize: ‘32px‘, 
    fill: ‘#fff‘ 
  });
}

And update it in the update loop:

function update(time: number) {
  if (!gameOver) {
    const seconds = Math.floor(time / 1000);
    scoreText.setText(`score: ${seconds}`);
  }

  // ...
}

Next Steps

Congratulations, you‘ve just built a complete browser game with Phaser and TypeScript! There are countless ways you could expand on it:

  • Add different obstacle types and spawn patterns to increase difficulty over time
  • Implement power-ups and special abilities to help the player
  • Display a Game Over screen and let the player restart
  • Include sound effects and background music
  • Make the game responsive for different screen sizes

The skills you‘ve learned can be applied to create any kind of 2D action or arcade-style game. To learn more, I recommend these resources:

While Phaser does simplify the process, developing browser games is still a cross-disciplinary challenge spanning design, programming, game logic, performance, and more. Persistence is key – start small, prototype quickly, and don‘t be afraid to experiment. With time and practice, you can use these tools to build increasingly sophisticated games and interactive experiences. Happy coding!

Similar Posts