Phaser is a game engine for the browser, which allows you to create games using JavaScript or TypeScript. Howler.js is a library for handling sound in the browser. This post will show you how to get started with Phaser and Howler.js using TypeScript.
The code can be found here: https://github.com/campbellgoe/phaser-game
The Howler.js documentation is a useful resource for learning how to work with sound in your game.
Also read the Phaser documentation to learn game development in the browser.
scaffold the project with vite using typescript
npm create vite@latest phaser-game -- --template vanilla-ts
change directory into your phaser-game project
cd phaser-game
open vscode or your preferred editor
code .
install phaser and howler from the npm registry, including the types for howler
npm install phaser howler @types/howler
Define your vite config (vite.config.ts) in the root next to where package.json is:
import { defineConfig } from 'vite';
// This will open the app in the browser when you run `npm run dev`
export default defineConfig({
server: { open: true }
});
Define your index.html and place this in the root directory (vite will pick this up) too:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Phaser TypeScript Game</title>
</head>
<body>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
Now the starting point for the TypeScript code of a 2D platformer using Phaser (game engine module) and Howler.js (sound handling module) for our game, main.ts
in the ./src
folder (./src/main.ts
):
import Phaser from 'phaser';
import { Howl } from 'howler';
class SimpleGame extends Phaser.Scene {
private box!: Phaser.GameObjects.Rectangle;
private floors!: Phaser.GameObjects.Rectangle[];
private cursors?: Phaser.Types.Input.Keyboard.CursorKeys;
private moveSound!: Howl;
private soundPlaying: boolean = false;
private isJumping: boolean = false;
private moveDirection: 'left' | 'right' | null = null; // To track the current movement direction
constructor() {
super('simple-game');
}
preload(): void {
// Load your walking sound here if needed
}
create(): void {
// Create a box (rectangle)
this.box = this.add.rectangle(400, 300, 50, 50, 0xffff00);
this.physics.add.existing(this.box);
// (this.box.body as Phaser.Physics.Arcade.Body).setCollideWorldBounds(true);
// Enable gravity
(this.box.body as Phaser.Physics.Arcade.Body).setGravityY(300);
// Create the floor (static object)
this.floors = Array.from({ length: 4 }).map((_, i) =>
this.add.rectangle(400 * i + 100 * i, 580, 400, 40, 0x00ff00)
);
this.floors.forEach(floor => {
// 'true' makes it static
this.physics.add.existing(floor, true);
// Add collision between the box and the floor
this.physics.add.collider(this.box, floor);
});
// Capture keyboard arrow keys
this.cursors = this.input.keyboard?.createCursorKeys();
// Initialize Howler sound
this.moveSound = new Howl({
loop: true,
volume: 1,
src: ['walk.wav'], // Add your move sound file here
});
// Make the camera follow the box
this.cameras.main.startFollow(this.box, true, 0.05, 0.05); // Slight easing on follow
this.cameras.main.setBounds(0, 0, 1600, 600); // Set camera bounds
}
update(): void {
const boxBody = this.box.body as Phaser.Physics.Arcade.Body;
let isWalking = false;
// Respawn if the box falls off the bottom of the screen
if (boxBody.y > this.cameras.main.height) {
boxBody.reset(400, 300); // Reset position
boxBody.setVelocity(0, 0); // Reset velocity to stop any momentum
}
// Reset horizontal velocity
boxBody.setVelocityX(0);
// Direction logic to prioritize first key pressed and prevent changing directions while holding both keys
if (this.cursors?.left?.isDown && this.cursors?.right?.isDown) {
// If both keys are pressed, keep moving in the current direction
if (this.moveDirection === 'left') {
boxBody.setVelocityX(-160); // Continue moving left
isWalking = true;
} else if (this.moveDirection === 'right') {
boxBody.setVelocityX(160); // Continue moving right
isWalking = true;
}
} else if (this.cursors?.left?.isDown) {
// Move left and set the movement direction
this.moveDirection = 'left';
boxBody.setVelocityX(-160);
isWalking = true;
} else if (this.cursors?.right?.isDown) {
// Move right and set the movement direction
this.moveDirection = 'right';
boxBody.setVelocityX(160);
isWalking = true;
} else {
// Reset direction when no key is pressed
this.moveDirection = null;
}
// Jumping - Allow jumping only if the character is touching the floor
if (this.cursors?.up?.isDown && boxBody.blocked.down && !this.isJumping) {
// Jump with upward velocity
boxBody.setVelocityY(-330);
// Prevent multiple jumps while in the air
this.isJumping = true;
}
// Reset jump state when the box lands back on the floor
if (boxBody.blocked.down) {
this.isJumping = false;
}
// Handle walking sound state
if (isWalking && !this.isJumping) {
if (!this.moveSound.playing() && !this.soundPlaying) {
this.moveSound.fade(this.moveSound.volume() || 0, 1, 333);
this.moveSound.play();
this.soundPlaying = true;
}
} else {
if (this.soundPlaying) {
this.moveSound.fade(this.moveSound.volume(), 0, 333);
this.soundPlaying = false;
this.moveSound.once('fade', () => {
this.moveSound.stop();
});
}
}
}
}
const config: Phaser.Types.Core.GameConfig = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#3498db',
scale: {
mode: Phaser.Scale.RESIZE,
autoCenter: Phaser.Scale.CENTER_BOTH
},
physics: {
default: 'arcade',
arcade: {
gravity: { x: 0, y: 300 }, // Global gravity
debug: false // You can enable this for debugging
}
},
scene: SimpleGame
};
new Phaser.Game(config);
Note you could specify matter for rigid 2d bodies for physics but arcade will suffice for now.
Gravity is defined in this arcade physics mode as x and y number values. For y, positive values give gravity downward, negative values upward.
Settings the scale property of the Phaser config to RESIZE and CENTER_BOTH is a good idea for responsive design, meaning it should scale to the correct resolution for the device.
You can define scenes as classes which extend from Phaser.Scene
class SimpleGame extends Phaser.Scene {
...
The create
method is the initialisation function which is where you set your scene up.
The update
method is called on every frame, where you can specify input handling and game logic to run in the main game animation loop.
You may want to update the css to remove the border margin and padding, basically a reset.css could suffice.
And there's a starting point for a 2d platformer. You can add more features to this game, such as more levels, enemies, and collectibles. You can also add more sound effects and music using Howler.js.
Conclusion
This post has shown you how to get started with Phaser and Howler.js using TypeScript. You can now create your own games in the browser using these powerful tools. Have fun creating your games!