
In this tutorial, we’ll walk through creating a Flappy Bird-inspired game. The game will feature a player who controls a character (a chainsaw in this case) that needs to fly through a series of obstacles (trees). Players must avoid touching them to stay alive, and the longer they survive, the higher their score!
The purpose of this tutorial is to introduce you to the creation of a simple side-scrolling game with physics, user input, and scoring mechanisms.
Phaser.io is a game engine that allows you to create games directly in your web browser (like Chrome or Firefox). This means you don’t even need to download any complex software. All you do is write simple code, and voila, your game works in the browser.
– Simple and accessible: It’s perfect for beginners. Even if you’ve never coded before, you can follow this tutorial and create your own game.
– Compatibility: Phaser works on almost every browser, so your friends can play your game too!
– Easy experimentation: You can easily modify your code and see the changes right away.
Before we start coding, you will need a few simple tools:
– A web browser (Chrome, Firefox, Edge, etc.).
– A text editor to write your code (we recommend Visual Studio Code or Notepad if you prefer something simple).
NOTE : If you don’t want to use Visual Studio Code, you can use Notepad, which is already installed on your computer.
HTML is the language used to create web pages. It’s like the skeleton of a page. With HTML, you tell the browser what to display. It is composed of tags defined by the element name surrounded by chevrons.
The HTML document itself begins with `<html>` and ends with `</html>`. The visible part of the HTML document is between `<body>` and `</body>`.
CSS makes web pages look nice. It’s like choosing the colors and styles for your page.
JavaScript makes web pages interactive. It’s the brain behind everything that moves and does actions. Phaser.io uses JavaScript to create video games.
Phaser is actually a JavaScript library. This means it gives you pre-built tools to create games. You don’t have to code everything from scratch!
“`html
<!DOCTYPE html>
<html lang=”en”>
<head>
<meta charset=”UTF-8″ />
<meta name=”viewport” content=”width=device-width, initial-scale=1.0″ />
<title>My First Phaser Game</title>
<script src=”https://cdn.jsdelivr.net/npm/[email protected]/dist/phaser.js”></script>
</head>
<body>
<script>
// This is where our Phaser game starts
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
scene: {
preload: preload,
create: create,
update: update,
},
};
const game = new Phaser.Game(config);
function preload() {
// Load assets here
}
function create() {
// Initialize the game here
}
function update() {
// Game logic goes here
}
</script>
</body>
</html>
“`
– `<!DOCTYPE html>` tells the browser that this is a web page.
– The line `<script src=”https://cdn.jsdelivr.net/npm/[email protected]/dist/phaser.js”></script>` loads Phaser.io into your page. This line indicates that we want to load a javascript code that can be found at the address indicated in src.
The config object is like a recipe. It sets the size of your game (800 pixels wide and 600 pixels tall) and defines the game stages (`preload`, `create`, `update`). Several other attributes can be used to define other specific parameters, such as :
– type: Phaser will automatically decide whether to use WebGL or Canvas rendering.
– scale: Ensures the game fits within the browser window.
The preload() function is where you load the images, sounds, or other resources needed for your game. For example, you might load a character or background image here.
The create() function is used to place these images or other elements into your game. This is where you start bringing your game to life.
The update() function runs continuously while the game is active. This is where you write the logic that makes your characters or objects move.
Once you’ve pasted the code into your HTML file, open that file in your web browser. You should see a blank screen (since we haven’t added anything yet), but this is a good start!
If something doesn’t work as expected, don’t worry! Here’s how to find and fix mistakes:
– On most browsers, press `F12` or right-click on the page and select “Inspect”.
– Go to the Console tab. This is where the browser will display error messages when something goes wrong.
Errors are a normal part of coding. The key is to be patient, use the console to spot them, and fix them step by step.
Start by creating an `index.html` file that will load Phaser and the game code. Here’s what the file should look like:
“`html
<!DOCTYPE html>
<html>
<head>
<script src=”https://cdn.jsdelivr.net/npm/[email protected]/dist/phaser-arcade-physics.min.js”></script>
<script src=”./index.js”></script>
<style>
body, html {
background: black;
margin: 0;
padding: 0;
overflow: hidden;
width: 100%;
height: 100%;
}
canvas {
display: block;
}
</style>
</head>
<body>
</body>
</html>
“`
Explanation
– This file sets up a basic HTML page with Phaser loaded from a CDN. We also link the `index.js` file where all the game logic will reside.
Before jumping into the game logic, let’s define a few constants in `index.js`. These will help us easily control the game dimensions and other key parameters.
“`javascript
const GAME_WIDTH = 800;
const GAME_HEIGHT = 600;
“`
– GAME_WIDTH: Width of the game screen.
– GAME_HEIGHT: Height of the game screen.
Now, let’s begin coding the game !
Preloading Assets
We use the `preload()` function to load all the game assets before the game starts. We’ll load the background, player (chainsaw), and obstacle (trees) images.
“`javascript
class MainScene extends Phaser.Scene {
constructor() {
super({ key: ‘MainScene’ });
}
preload() {
this.load.setBaseURL(‘./assets/’);
this.load.image(‘background’, ‘sky.png’); // background image
this.load.image(‘chainsaw’, ‘chainsaw.png’); // player (chainsaw)
this.load.image(‘obstacle’, ‘tree.png’); // obstacles (trees)
}
}
“`
In the `create()` function, we’ll set up the game world. This includes:
– Adding the background.
– Creating the player (a chainsaw).
– Setting up gravity so the player “falls” automatically.
– Creating the obstacles (trees).
– Displaying the score.
“`javascript
create() {
this.gameStop = false; // Flag to track if the game has stopped
this.score = 0; // Player’s score
this.centerX = GAME_WIDTH / 2;
this.centerY = GAME_HEIGHT / 2;
// Add the background
this.add.image(0, 0, ‘background’).setOrigin(0, 0).setDisplaySize(GAME_WIDTH, GAME_HEIGHT);
// Add the player
this.player = this.physics.add.sprite(100, this.centerY, ‘chainsaw’);
this.player.setCollideWorldBounds(true); // Prevent the player from leaving the screen
this.player.setGravityY(1000); // Apply gravity to the player
// Create a group to hold obstacles
this.obstacles = this.physics.add.group();
// Create obstacles at regular intervals
this.obstaclesAddTick = this.time.addEvent({
delay: 1500, // Add an obstacle every 1.5 seconds
callback: this.addObstacle,
callbackScope: this,
loop: true
});
// Detect collisions between the player and obstacles
this.physics.add.collider(this.player, this.obstacles, this.gameEnd, null, this);
// Allow the player to jump with mouse clicks or spacebar
this.input.on(‘pointerdown’, this.jump, this);
this.input.keyboard.on(‘keydown-SPACE’, this.jump, this);
// Display the score
this.scoreText = this.add.text(16, 16, ‘Score: 0’, {
fontFamily: ‘Arial’,
fontSize: ’24px’,
color: ‘white’
}).setDepth(10); // Prevent the score from being hidden by obstacles
}
“`
Explanation
– this.player.setGravityY(1000): Adds gravity to the player, so they will constantly fall.
– this.obstacles: A group of obstacles (trees) that will be generated at regular intervals.
– this.scoreText: Displays the player’s score.
The game needs a way for the player to jump (fly) when clicking or pressing space. We’ll add a `jump()` method to handle this:
“`javascript
jump() {
if (this.gameStop) return; // Don’t allow jumping after the game is over
this.player.setVelocityY(-400); // Makes the player “jump” by setting vertical velocity
}
“`
Explanation:
– setVelocityY(-400): Causes the player to jump upwards with a force of `-400`.
Now, we’ll add obstacles (trees) that move across the screen. The player must avoid hitting these obstacles to stay alive. We’ll use the `addObstacle()` function to generate these at regular intervals.
“`javascript
addObstacle() {
const gap = Phaser.Math.Between(140, 300); // Random gap between the obstacles
const maxOffset = gap / 2; // Maximum offset to move the obstacles vertically
const offset = Phaser.Math.Between(0, maxOffset); // Random offset
// Add the top obstacle (flipped)
const topObstacle = this.obstacles.create(GAME_WIDTH, -offset – gap / 2, ‘obstacle’);
topObstacle.flipY = true; // Flip the obstacle to face downwards
topObstacle.setOrigin(0, 0);
topObstacle.body.allowGravity = false;
topObstacle.setVelocityX(-200); // Move the obstacle from right to left
topObstacle.body.setSize(30, 300, true);
// Add the bottom obstacle
const bottomObstacle = this.obstacles.create(GAME_WIDTH, GAME_HEIGHT – offset + gap / 2, ‘obstacle’);
bottomObstacle.setOrigin(0, 1);
bottomObstacle.body.allowGravity = false;
bottomObstacle.setVelocityX(-200);
bottomObstacle.body.setSize(30, 300, true);
}
“`
Explanation
– gap: The vertical space between the top and bottom obstacles.
– setVelocityX(-200): Moves the obstacle from the right to the left of the screen at a speed of 200 pixels per second.
In the `update()` function, we’ll check for:
– If the player is out of bounds (top or bottom of the screen).
– If the player successfully passes an obstacle without hitting it, the score increases.
“`javascript
update() {
if (this.gameStop) return;
// End the game if the player goes out of bounds
if (this.player.y > GAME_HEIGHT || this.player.y < 0) {
this.gameEnd();
}
// Check each obstacle for scoring and removal
this.obstacles.getChildren().forEach(obstacle => {
// Remove the obstacle if it’s off the screen
if (obstacle.x + obstacle.width < 0) {
obstacle.destroy();
}
// Increase score if the player passes the obstacle
if (!obstacle.scored && obstacle.x < this.player.x) {
obstacle.scored = true;
this.score += 1;
this.scoreText.setText(‘Score: ‘ + this.score);
}
});
}
“`
Explanation
– this.gameEnd(): Ends the game if the player goes out of bounds.
– obstacle.scored: A flag that ensures each obstacle only increases the score once.
If the player collides with an obstacle or goes out of bounds, we’ll end the game and show a “Game Over” message.
“`javascript
gameEnd() {
if (this.gameStop) return;
this.gameStop = true;
this.time.removeEvent(this.obstaclesAddTick); // Stop adding new obstacles
this.obstacles.setVelocityX(0); // Stop all obstacles from moving
this.player.setTint(0xff0000); // Change the player color to indicate game over
// Display Game Over message
this.add.text(this.centerX, this.centerY – 30, ‘Game Over’, {
fontSize: ’64px’,
fontFamily: ‘Arial’,
color: ‘white’
}).setOrigin(0.5);
this.add.text
(this.centerX, this.centerY + 30, ‘Press any key to restart’, {
fontSize: ’32px’,
fontFamily: ‘Arial’,
color: ‘white’
}).setOrigin(0.5);
// Restart the game on key press
this.input.keyboard.once(‘keydown’, this.restartGame, this);
}
restartGame() {
this.scene.restart(); // Restart the game by reloading the scene
}
“`
Explanation:
-gameEnd(): Stops the game, shows the “Game Over” message, and allows the player to restart the game by pressing any key.
“`javascript
const GAME_WIDTH = 800;
const GAME_HEIGHT = 600;
class MainScene extends Phaser.Scene {
constructor() {
super({ key: ‘MainScene’ });
}
preload() {
this.load.setBaseURL(‘./assets/’);
this.load.image(‘background’, ‘sky.png’); // background
this.load.image(‘chainsaw’, ‘chainsaw.png’); // player
this.load.image(‘obstacle’, ‘tree.png’); // tree
}
create() {
this.gameStop = false;
this.score = 0;
this.centerX = GAME_WIDTH / 2;
this.centerY = GAME_HEIGHT / 2;
// Add the background
this.add.image(0, 0, ‘background’).setOrigin(0, 0).setDisplaySize(GAME_WIDTH, GAME_HEIGHT);
// Add the player
this.player = this.physics.add.sprite(100, this.centerY, ‘chainsaw’);
this.player.setCollideWorldBounds(true);
this.player.setGravityY(1000); // Add gravity to the player
// Create the obstacles group
this.obstacles = this.physics.add.group();
// Generate obstacles
this.obstaclesAddTick = this.time.addEvent({
delay: 1500,
callback: this.addObstacle,
callbackScope: this,
loop: true
});
// Detect collision between the player and the obstacles
this.physics.add.collider(this.player, this.obstacles, this.gameEnd, null, this);
// Jump when the player click or press space
this.input.on(‘pointerdown’, this.jump, this);
this.input.keyboard.on(‘keydown-SPACE’, this.jump, this);
// Display the score
this.scoreText = this.add.text(16, 16, ‘Score: 0’)
.setFontFamily(‘Arial’)
.setFontSize(24)
.setColor(‘white’)
.setDepth(10); // Prevent the score to be hidden by the obstacles
}
update() {
if (this.gameStop) return;
// Check if the player is out of the screen
if (this.player.y > GAME_HEIGHT || this.player.y < 0) {
this.gameEnd();
}
this.obstacles.getChildren().forEach(pipe => {
// Detect if the obstacle is out of the screen
if (pipe.x + pipe.width < 0) {
pipe.destroy(); // Delete the pipe
}
// Detect if the player has passed the pipe
if (!pipe.scored && pipe.x < this.player.x) {
pipe.scored = true;
this.score += 1;
this.scoreText.setText(‘Score: ‘ + this.score);
}
});
}
jump() {
if (this.gameStop) return;
this.player.setVelocityY(-400); // Jump
}
addObstacle() {
const gap = Phaser.Math.Between(140, 300);
const maxOffset = gap / 2;
const offset = Phaser.Math.Between(0, maxOffset);
// Add top obstacle
const topObstacle = this.obstacles.create(GAME_WIDTH, -offset – gap /2, ‘obstacle’);
topObstacle.flipY=true; // Flip the obstacle
topObstacle.setOrigin(0, 0);
topObstacle.body.allowGravity = false;
topObstacle.setVelocityX(-200);
topObstacle.body.setSize(30, 300, true);
// Add bottom obstacle
const bottomObstacle = this.obstacles.create(GAME_WIDTH, GAME_HEIGHT – offset + gap /2, ‘obstacle’);
bottomObstacle.setOrigin(0, 1);
bottomObstacle.body.allowGravity = false;
bottomObstacle.setVelocityX(-200);
bottomObstacle.body.setSize(30, 300, true);
}
gameEnd() {
if(this.gameStop) return;
this.gameStop = true;
this.time.removeEvent(this.obstaclesAddTick);
this.obstacles.setVelocityX(0); // Stop the obstacles
this.player.setTint(0xff0000); // Change the player color
// Restart the game when a key is pressed
this.input.keyboard.once(‘keydown’, this.restartGame, this);
// Display the game over message
this.add.text(this.centerX, this.centerY – 30, ‘Game Over’)
.setFontSize(64)
.setFontFamily(‘Arial’)
.setOrigin(0.5, 0.5)
.setFontStyle(‘bold’)
.setColor(‘white’);
this.add.text(this.centerX, this.centerY + 30, ‘Press any key to restart’)
.setFontSize(32)
.setFontFamily(‘Arial’)
.setOrigin(0.5, 0.5)
}
// Restart the game
restartGame() {
this.scene.restart();
}
}
const config = {
type: Phaser.AUTO,
width: GAME_WIDTH,
height: GAME_HEIGHT,
scene: MainScene,
physics: {
default: ‘arcade’,
arcade: {
debug: false
}
},
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH,
}
};
const game = new Phaser.Game(config);
“`
To replace images, in your source folder where the images are, you can simply change the images from the game folder to other images as you wish. You can name your images and modify the names that are put in the part of the code used to identify the source images. As per the section “Building the Main Game Scene” – Preloading assets:
preload() {
this.load.setBaseURL(‘./assets/’);
this.load.image(‘background’, ‘sky.png’); // background image
this.load.image(‘chainsaw’, ‘chainsaw.png’); // player (chainsaw)
this.load.image(‘obstacle’, ‘tree.png’); // obstacles (trees)
}
}
“`
Make sure that the name of the source image (here highlighted in yellow) matches the name of your images as you named them in the folder. And then, anytime the ‘background’, ‘chainsaw’ or ‘obstacle’ will be used in the code, it will refer to the image you chose. If you also chose to modify the names used in the code (‘background’, ‘chainsaw’ or ‘obstacle’), please make sure to also modify those names in the entire code whenever they are used!
To customize this game, you can, for example:
– Add a difficulty increase: Speed up the obstacles or reduce the gap between them as the player’s score increases.
– Add sound effects: Include jump and collision sounds for more feedback.
– Add levels: You could implement different background themes or progressively harder levels.
– Add animations: Animate the player sprite (e.g., make the chainsaw spin as it moves).
– Add a difficulty increase: Speed up the obstacles or reduce the gap between them as the player’s score increases.
– Add sound effects: Include jump and collision sounds for more feedback.
– Add levels: You could implement different background themes or progressively harder levels.
– Add animations: Animate the player sprite (e.g., make the chainsaw spin as it moves).