In this section, you will learn how to build a complete game in Phaser by following a structured, step-by-step approach. We will apply the concepts learned throughout the course, from setting up scenes to handling input, managing game state, and adding polish. By the end, you’ll have a playable game and a clear understanding of how to structure your own projects.


  1. Planning the Game Structure

Before coding, it’s crucial to outline the game’s structure:

  • Game Genre: Platformer (player jumps and collects stars)
  • Core Features:
    • Player movement and jumping
    • Collectible items (stars)
    • Score tracking
    • Simple enemy
    • Game over and restart

Game Flow Table:

Step Description
1. Boot/Preload Load assets
2. Main Menu Start game, show instructions
3. Gameplay Player controls, collect stars, avoid enemy
4. Game Over/Win Show score, option to restart

  1. Setting Up the Project

Directory Structure:

/game
  /assets
    player.png
    star.png
    enemy.png
    background.png
  index.html
  main.js

index.html Example:

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Phaser Platformer Game</title>
  <script src="https://cdn.jsdelivr.net/npm/phaser@3/dist/phaser.js"></script>
</head>
<body>
  <script src="main.js"></script>
</body>
</html>

  1. Creating the Game Scenes

We’ll use three scenes: PreloadScene, MenuScene, and GameScene.

3.1 PreloadScene

Loads all assets.

class PreloadScene extends Phaser.Scene {
  constructor() {
    super('PreloadScene');
  }
  preload() {
    this.load.image('background', 'assets/background.png');
    this.load.image('player', 'assets/player.png');
    this.load.image('star', 'assets/star.png');
    this.load.image('enemy', 'assets/enemy.png');
  }
  create() {
    this.scene.start('MenuScene');
  }
}

3.2 MenuScene

Displays the main menu and starts the game.

class MenuScene extends Phaser.Scene {
  constructor() {
    super('MenuScene');
  }
  create() {
    this.add.text(100, 100, 'Platformer Game', { fontSize: '32px', fill: '#fff' });
    this.add.text(100, 150, 'Press SPACE to Start', { fontSize: '24px', fill: '#fff' });

    this.input.keyboard.once('keydown-SPACE', () => {
      this.scene.start('GameScene');
    });
  }
}

3.3 GameScene

Handles the main gameplay.

class GameScene extends Phaser.Scene {
  constructor() {
    super('GameScene');
    this.score = 0;
  }
  create() {
    // Background
    this.add.image(400, 300, 'background');

    // Player
    this.player = this.physics.add.sprite(100, 450, 'player');
    this.player.setCollideWorldBounds(true);

    // Stars group
    this.stars = this.physics.add.group({
      key: 'star',
      repeat: 5,
      setXY: { x: 120, y: 0, stepX: 120 }
    });

    // Enemy
    this.enemy = this.physics.add.sprite(400, 500, 'enemy');
    this.enemy.setVelocityX(100);
    this.enemy.setCollideWorldBounds(true);
    this.enemy.setBounce(1, 0);

    // Score text
    this.scoreText = this.add.text(16, 16, 'Score: 0', { fontSize: '24px', fill: '#fff' });

    // Input
    this.cursors = this.input.keyboard.createCursorKeys();

    // Collisions
    this.physics.add.overlap(this.player, this.stars, this.collectStar, null, this);
    this.physics.add.overlap(this.player, this.enemy, this.hitEnemy, null, this);
  }
  update() {
    // Player movement
    if (this.cursors.left.isDown) {
      this.player.setVelocityX(-160);
    } else if (this.cursors.right.isDown) {
      this.player.setVelocityX(160);
    } else {
      this.player.setVelocityX(0);
    }
    if (this.cursors.up.isDown && this.player.body.touching.down) {
      this.player.setVelocityY(-330);
    }
  }
  collectStar(player, star) {
    star.disableBody(true, true);
    this.score += 10;
    this.scoreText.setText('Score: ' + this.score);

    // Win condition
    if (this.stars.countActive(true) === 0) {
      this.scene.restart();
      alert('You win! Final Score: ' + this.score);
    }
  }
  hitEnemy(player, enemy) {
    this.physics.pause();
    player.setTint(0xff0000);
    this.scoreText.setText('Game Over! Final Score: ' + this.score);
    this.input.keyboard.once('keydown-SPACE', () => {
      this.scene.restart();
    });
  }
}

  1. Initializing the Game

main.js Example:

const config = {
  type: Phaser.AUTO,
  width: 800,
  height: 600,
  physics: {
    default: 'arcade',
    arcade: {
      gravity: { y: 300 },
      debug: false
    }
  },
  scene: [PreloadScene, MenuScene, GameScene]
};

const game = new Phaser.Game(config);

  1. Testing and Iterating

  • Test: Open index.html in your browser.
  • Iterate: Adjust player speed, add more stars, tweak enemy behavior, or improve visuals as desired.

  1. Exercise: Add a Double Jump Feature

Task:
Modify the player so they can jump twice before landing.

Hint:
Track the number of jumps and reset it when the player lands.

Solution:

// In GameScene class
create() {
  // ... existing code ...
  this.jumpCount = 0;
  // ... existing code ...
}

update() {
  // ... existing code ...
  if (this.cursors.up.isDown && (this.player.body.touching.down || this.jumpCount < 2)) {
    if (this.player.body.touching.down) {
      this.jumpCount = 0;
    }
    if (!this.jumpPressed) {
      this.player.setVelocityY(-330);
      this.jumpCount++;
      this.jumpPressed = true;
    }
  } else if (!this.cursors.up.isDown) {
    this.jumpPressed = false;
  }
  if (this.player.body.touching.down) {
    this.jumpCount = 0;
  }
}

Common Mistake:
Forgetting to reset jumpCount when the player lands, which prevents further jumps.


  1. Summary

You have now built a simple but complete game using Phaser, applying all the core concepts: scenes, sprites, input, physics, collisions, and game state management. You also learned how to extend gameplay with new features. This step-by-step approach can be adapted to create more complex games. In the next section, you’ll learn how to polish and finalize your game for release.

© Copyright 2024. All rights reserved