Organizing your game code is essential for building scalable, maintainable, and bug-free games. As your Phaser project grows, a well-structured codebase will help you manage complexity, collaborate with others, and add new features efficiently.

This section covers:

  • Why code organization matters
  • Common code organization patterns in Phaser
  • Using modules and classes
  • Practical examples
  • Exercises with solutions

  1. Why Code Organization Matters

Key Benefits:

  • Readability: Easier to understand and navigate your code.
  • Maintainability: Simplifies fixing bugs and adding features.
  • Reusability: Encourages code reuse across different parts of your game or future projects.
  • Collaboration: Makes it easier for teams to work together.

  1. Common Code Organization Patterns in Phaser

Phaser projects can be organized in several ways. Here are the most common patterns:

Pattern Description When to Use
Single File All code in one file Very small prototypes
Scene-based Structure Each scene in its own file/class Most Phaser games
Modular Structure Separate files for scenes, objects, utilities, and configuration Medium to large projects
Component-based Game objects composed of reusable components (advanced) Large, complex games

Tip: For most games, a modular, scene-based structure is recommended.


  1. Using Modules and Classes

Modern JavaScript (ES6+) supports modules and classes, which are ideal for organizing Phaser code.

3.1. Scene Classes

Each game scene (e.g., MainMenu, Game, GameOver) should be its own class in a separate file.

Example: Creating a Scene Class

// src/scenes/MainMenu.js
import Phaser from 'phaser';

export default class MainMenu extends Phaser.Scene {
  constructor() {
    super('MainMenu');
  }

  preload() {
    // Load assets
    this.load.image('logo', 'assets/logo.png');
  }

  create() {
    // Add logo to the scene
    this.add.image(400, 300, 'logo');
    // Start the game on click
    this.input.once('pointerdown', () => {
      this.scene.start('Game');
    });
  }
}

Explanation:

  • MainMenu extends Phaser.Scene.
  • The preload method loads assets.
  • The create method sets up the scene and handles input.

3.2. Game Objects as Classes

Custom game objects (e.g., Player, Enemy) can also be classes.

// src/objects/Player.js
import Phaser from 'phaser';

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

  update(cursors) {
    if (cursors.left.isDown) {
      this.setVelocityX(-160);
    } else if (cursors.right.isDown) {
      this.setVelocityX(160);
    } else {
      this.setVelocityX(0);
    }
  }
}

Explanation:

  • Player extends Phaser.Physics.Arcade.Sprite.
  • The constructor adds the player to the scene and enables physics.
  • The update method handles movement.

3.3. Importing and Using Modules

In your main game file, import and use your scene and object classes:

// src/index.js
import Phaser from 'phaser';
import MainMenu from './scenes/MainMenu.js';
import Game from './scenes/Game.js';

const config = {
  type: Phaser.AUTO,
  width: 800,
  height: 600,
  scene: [MainMenu, Game],
  physics: { default: 'arcade' }
};

const game = new Phaser.Game(config);

  1. Folder Structure Example

A typical Phaser project might look like this:

/src
  /scenes
    MainMenu.js
    Game.js
    GameOver.js
  /objects
    Player.js
    Enemy.js
  /utils
    helpers.js
  index.js
/assets
  logo.png
  player.png
  ...

Explanation:

  • scenes/: All scene classes.
  • objects/: Custom game object classes.
  • utils/: Utility/helper functions.
  • assets/: Images, sounds, etc.

  1. Practical Exercise

Exercise

Task:
Refactor the following code so that the player logic is separated into its own class file. Assume you have a Game scene.

Original Code (all in one file):

class Game extends Phaser.Scene {
  constructor() {
    super('Game');
  }

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

  create() {
    this.player = this.physics.add.sprite(400, 300, 'player');
    this.cursors = this.input.keyboard.createCursorKeys();
  }

  update() {
    if (this.cursors.left.isDown) {
      this.player.setVelocityX(-160);
    } else if (this.cursors.right.isDown) {
      this.player.setVelocityX(160);
    } else {
      this.player.setVelocityX(0);
    }
  }
}

Instructions:

  1. Create a Player.js class in an objects folder.
  2. Move the player logic into this class.
  3. Update the Game scene to use the new Player class.

Solution

Step 1: Create Player.js

// src/objects/Player.js
import Phaser from 'phaser';

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

  move(cursors) {
    if (cursors.left.isDown) {
      this.setVelocityX(-160);
    } else if (cursors.right.isDown) {
      this.setVelocityX(160);
    } else {
      this.setVelocityX(0);
    }
  }
}

Step 2: Update Game.js

// src/scenes/Game.js
import Phaser from 'phaser';
import Player from '../objects/Player.js';

export default class Game extends Phaser.Scene {
  constructor() {
    super('Game');
  }

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

  create() {
    this.player = new Player(this, 400, 300);
    this.cursors = this.input.keyboard.createCursorKeys();
  }

  update() {
    this.player.move(this.cursors);
  }
}

Common Mistakes & Tips:

  • Forgetting to add the player to the scene: Always call scene.add.existing(this) in your custom object’s constructor.
  • Incorrect import paths: Double-check your import statements and folder structure.
  • Not passing the scene to the object constructor: Always pass the scene as the first argument.

  1. Summary

  • Organizing your code into modules and classes makes your Phaser games easier to manage and scale.
  • Use a folder structure that separates scenes, objects, and utilities.
  • Leverage ES6 modules (import/export) to keep code clean and maintainable.
  • Practice refactoring code into separate files and classes for better organization.

Next: In the following section, you'll learn how to manage multiple scenes, allowing for menus, levels, and transitions in your game.

© Copyright 2024. All rights reserved