Build the HTML5 Game "2048" Using the Latest Version of Phaser 3

Apr. 28th, 2025

Create a project

Download the latest version of Phaser.js from the Phaser.io website (the version used in this article is 3.88.2) and install it. Open the Phaser Launcher,
  1. In the Projects tab panel.
  2. Click the New button in the upper left corner.
  3. In the Select a template list, select 2048 template.
  4. Select path for the project in the Folder on the right.
  5. Enter the project name in the Project Name on the right, for example 2048.
  6. Finally, click the Create button.
The latest version of Phaser 3 already includes an almost usable 2048 template, except for a few places that need improvement.

Understand the project structure

In the Code Editor interface of the 2048 project created above, as shown in the figure below, on the left side, you can see the project asset folder assets and the project code folder src, as well as the project configuration file project.config.

In Phaser 3 projects, project.config, index.html, and main.js each play different but crucial roles. Below is a detailed introduction to their roles.

project.config

The project.config file is usually used to store project configuration information, which can help developers and tools better understand and manage the project. Based on the code snippet you provided, this file uses the INI format and contains multiple parts. Its specific functions are as follows:

  • Game configuration: The [game] section specifies the basic information of the game, such as the title being the title of the game, and the width and height being the width and height of the game window.
  • 
    [game]
    title = "2048"
    width = 1280
    height = 720
    								
  • User information: The [user] section stores information related to the user, such as the id being the user's Unique Device Identifier.
  • 
    [user]
    id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    								
  • Editor and version information: The [editor] section records information such as the editor version, Phaser version, creation time, and code font size of the project, which helps developers understand the development environment and history of the project.
  • 
    [editor]
    editor_version = "1.1.0"
    phaser_version = "3.88.2"
    creation_at = 1742569365604
    code_font_size = 16
    								

index.html

index.html is the entry HTML file of the project, which provides the basic HTML structure and environment for the game to run. Its specific functions are as follows:

  • Page settings: Set the character encoding, viewport, and title of the page.
  • 
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>2048</title>
    								
  • Style reset: Reset the style through CSS to ensure consistent display of the page in different browsers.
  • 
    <style>
        /* Reset CSS: Please avoid making changes here unless you know what you're doing :) */
        *,
        *::before,
        *::after {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        /* Other style rules */
    </style>
    								
  • Game container: Create a div element as the container for the game, and the Phaser game will be rendered into this container.
  • 
    <div id="game-container"></div>
    								
  • Script import: Import the Phaser library and the main JavaScript file of the project.
  • 
    <script src="./phaser.js"></script>
    <script type="module" src="./src/main.js"></script>
    								

main.js

main.js is the main JavaScript file of the project, which is responsible for initializing the Phaser game and configuring the basic parameters of the game. Its specific functions are as follows:

  • Import scenes: Import various scenes used in the game, such as Boot, Preloader, Game, and GameOver scenes.
  • 
    import { Boot } from './scenes/Boot.js';
    import { Game } from './scenes/Game.js';
    import { GameOver } from './scenes/GameOver.js';
    import { Preloader } from './scenes/Preloader.js';
    								
  • Game configuration: Define the configuration object of the game, including information such as game type, width, height, parent container, background color, zoom mode, and scene.
  • 
    const config = {
        type: Phaser.AUTO,
        width: 1024,
        height: 768,
        parent: 'game-container',
        backgroundColor: '#2d3436',
        scale: {
            mode: Phaser.Scale.FIT,
            autoCenter: Phaser.Scale.CENTER_BOTH
        },
        scene: [
            Boot,
            Preloader,
            Game,
            GameOver
        ]
    };
    								
  • Game initialization: Create a new Phaser game instance using the previously defined configuration object.
  • 
    new Phaser.Game(config);
    								

In summary, project.config stores the configuration information of the project, index.html provides the HTML environment for the game to run, main.js initializes the Phaser game and configures basic parameters. They work together to ensure the normal operation of the game.

Core algorithm

  1. Game state management
    • Initialize the chessboard: Create a two-dimensional array fieldArray to represent the game chessboard, where each element stores an object containing tileValue (the value of the tile), tileSprite (the sprite object of the tile), and canUpgrade (whether it can be upgraded).
    • Add new tiles: After each move, randomly select an empty cell to add a new tile. The value of the new tile has a 90% chance of being 2 and a 10% chance of being 4.
    • Check the end of the game: traverse the entire chessboard. If there are no empty cells and the values of adjacent tiles are not equal, the game is over.
  2. Move and merge logic
    • Moving direction processing: Determine the moving direction (up, down, left, right) based on the user's keyboard input or touch sliding event.
    • Move Logic: Traverse each tile on the chessboard and move the tile in that direction to the farthest empty cell or adjacent tile of the same value.
    • Merge logic: If adjacent tiles have the same value and can be upgraded, merge them into one tile with twice the original value and update the score.
  3. Animation processing
    • New tile appearance animation: The newly added tile is scaled from 0 to 0.8 using tweens animation.
    • Moving animation: During the movement of tiles, use tweens animation to achieve linear movement.
    • Merge animation: The merged tiles are first enlarged to 1.0, then reduced to 0.8, using tweens animation.

Implementation process

  1. Scene loading and initialization
    • Boot scene: Preload the basic resources required for the game, such as background images, and then launch the Preloader scene.
    • 
      export class Boot extends Phaser.Scene {
          constructor() {
              super('Boot');
          }
      
          preload() {
              this.load.setPath('assets');
              this.load.image('background', 'background.png');
          }
      
          create() {
              this.scene.start('Preloader');
          }
      }
      									
    • Preloader scene: Display the progress bar, load all the resources required for the game, such as tile images, and launch the Game scene after loading.
    • 
      export class Preloader extends Phaser.Scene {
          constructor() {
              super('Preloader');
          }
      
          init() {
              // Display Background and Progress Bar
          }
      
          preload() {
              this.load.setPath('assets');
              // Load all tile images
          }
      
          create() {
              this.scene.start('Game');
          }
      }
      									
  2. Game scene creation and initialization
    • Game Scene: Create game and board backgrounds. Initialize the chessboard, create a tile background and tile sprite, and store them in fieldArray. Set up keyboard and touch input events to listen to user movements. Start the game and add two initial tiles.
    • 
      export class Game extends Phaser.Scene {
          constructor() {
              super({ key: 'Game' });
              // Initialize game parameters
          }
      
          create() {
              // Create backgrounds and chessboards
              // Initialize the chessboard and tiles
              // Set input events
              // Add initial tile
          }
      }
      									
  3. Game logic processing
    • Handle input events: Call the handleMove method to handle the movement logic based on the user's keyboard input or touch sliding events.
    • 
      handleKey(e) {
          if (!this.canMove) return;
          // Handle movement according to key code
          this.handleMove(deltaRow, deltaCol);
      }
      
      endSwipe(e) {
          if (!this.canMove) return;
          // Handle movement according to sliding direction
          this.handleMove(deltaRow, deltaCol);
      }
      									
    • Handle movement logic: traverse each tile on the chessboard, move the tile to the farthest empty cell or adjacent tile with the same value according to the direction of movement, and handle merge logic.
    • 
      handleMove(deltaRow, deltaCol) {
          this.canMove = false;
          let isMoved = false;
          // Traverse the chessboard to handle moves and merges
          if (!isMoved) {
              this.canMove = true;
          }
      }
      									
    • Move and merge animations: Use tweens animations to achieve tile movement and merge effects.
    • 
      moveTile(tile, row, col, distance, changeNumber) {
          this.movingTiles++;
          // Create moving animations
          // Merge or add new tiles after the animation is complete
      }
      
      mergeTile(tile, row, col) {
          this.movingTiles++;
          // Create merge animation
          // Add new tiles after the animation is completed
      }
      									
    • Add new tile: After each move, randomly select an empty cell to add a new tile.
    • 
      addTile() {
          // Find all empty cells
          // Randomly select an empty cell
          // 90% probability is 2, 10% probability is 4.
          // Update tile values and sprite textures
          // Create new tile appearance animation
          // Check the end of the game after the animation is completed
      }
      									
    • Check the end of the game: traverse the entire chessboard. If there are no empty cells and the values of adjacent tiles are not equal, the game is over.
    • 
      checkGameOver() {
          for (let row = 0; row < this.boardSize; row++) {
              for (let col = 0; col < this.boardSize; col++) {
                  // Check if there are empty cells or mergeable tiles
              }
          }
          // Game over, display game over interface
      }
      									
  4. Game over processing
    • Display the game over interface: At the end of the game, a semi-transparent mask layer is displayed, displaying the game end text, final score, and restart prompt.
    • 
      gameOver() {
          // Create mask layer
          // Display game over text, score, and restart prompt
          // Listen for touch events, click to restart the game
      }
      									
    • Restart the game: Reset the score and board, and restart the game scene.
    • 
      restartGame() {
          this.score = 0;
          this.scoreText.setText(`Score: 0`);
          this.scene.restart();
      }
      									

Optimization

Delete excess content

Scene GameOver is redundant and can be deleted. In addition to deleting the GameOver.js file, the following content with strikethrough also needs to be deleted in main.js.

import { Boot } from './scenes/Boot.js';
import { Game } from './scenes/Game.js';
import { GameOver } from './scenes/GameOver.js';
import { Preloader } from './scenes/Preloader.js';

//  Find out more information about the Game Config at:
//  https://newdocs.phaser.io/docs/3.70.0/Phaser.Types.Core.GameConfig
const config = {
    type: Phaser.AUTO,
    width: 1024,
    height: 768,
    parent: 'game-container',
    backgroundColor: '#2d3436',
    scale: {
        mode: Phaser.Scale.FIT,
        autoCenter: Phaser.Scale.CENTER_BOTH
    },
    scene: [
        Boot,
        Preloader,
        Game,
        GameOver
    ]
};

new Phaser.Game(config);
						

Game over interface depth issue

In the gameOver function in Game.js, due to its depth issue, the game over interface is blocked by the game board. This issue can be solved by modifying the depth of the game over interface through setDepth.

    gameOver() {
        const overlay = this.add.rectangle(512, 384, 1024, 768, 0x000000, 0.7).setDepth(5000);

        const gameOverText = this.add.text(512, 384, 'GAME OVER', {
            fontFamily: 'Arial Black',
            fontSize: 64,
            color: '#ffffff',
            align: 'center'
        }).setOrigin(0.5).setDepth(5001);

        const scoreText = this.add.text(512, 450, `Score: ${this.score}`, {
            fontFamily: 'Arial',
            fontSize: 32,
            color: '#ffffff',
            align: 'center'
        }).setOrigin(0.5).setDepth(5001);

        const restartText = this.add.text(512, 520, 'Tap to restart', {
            fontFamily: 'Arial',
            fontSize: 24,
            color: '#ffffff',
            align: 'center'
        }).setOrigin(0.5).setDepth(5001);

        overlay.setInteractive();
        overlay.on('pointerdown', () => {
            this.restartGame();
        });
    }
						
The result: