10-Minute Practical Guide: Building a "Catch Coins" Game with Phaser 3

2 views 0 likes 0 comments 17 minutesOriginalTutorial

A step-by-step tutorial on setting up a Vite + Phaser 3 environment and building a lightweight 2D arcade game with physics and collision detection. Perfect for developers looking to quickly master HTML5 game development.

#Phaser # Game Development # Frontend Practice # JavaScript # HTML5 # Indie Dev
10-Minute Practical Guide: Building a "Catch Coins" Game with Phaser 3

10-Minute Practical Guide: Building a "Catch Coins" Game with Phaser 3

Over the years, I've seen many backend and frontend developers want to build a small game for fun or add an interactive easter egg to an internal dashboard. But hearing about Unity or Cocos Creator usually brings a headache: multi-gigabyte installers, massive project structures, and steep learning curves. For developers who just want to run a lightweight 2D web game, that's overkill.

If you need a free, well-documented, and out-of-the-box HTML5 game solution, Phaser is undoubtedly the modern answer. It cuts through the noise and focuses on three core tasks: rendering graphics to a canvas, running the game loop frame-by-frame, and handling physics collisions and user input.

Today, I won't dive into source code or feature matrices. Instead, I'll walk you through setting up the environment from scratch and building a "Catch Coins" mini-game. Follow along, and you'll grasp the core game development loop, giving you the confidence to build independent 2D web games.

🛠 Prerequisites: What You Need to Know

  • Environment: Node.js 16+ (used for the local dev server)
  • Basics: Familiarity with foundational JavaScript syntax. No graphics theory or complex math required.
  • Mindset: Don't fear the low-level details! Phaser abstracts vector math and rendering pipelines cleanly. You only need to focus on business logic.

💡 Why use a local server?
Many beginners double-click a .html file directly and get a black screen with CORS or file:// protocol errors in the console. Phaser loads sprite assets via network requests, and modern browsers block local files from cross-origin resource loading for security reasons. A lightweight Vite dev server avoids 80% of these mysterious issues.

🚀 Step 1: Environment Setup & Core Concepts

Open your terminal and run the following commands:
(See installation code block below)

Once installed, open src/main.js (or your entry file). Phaser's architecture is highly focused. Two objects drive everything: Phaser.Game and Scene.

  • Game instance: The canvas manager. Initializes WebGL/Canvas, handles fullscreen, and manages scaling.
  • Scene: The "stage". Games typically split into Start Screen, Gameplay, and Game Over. Each Scene follows a strict lifecycle:
    1. preload(): Asynchronously loads images, audio, and JSON configs. The screen isn't rendered yet, making it perfect for loading spinners.
    2. create(): Resources are ready. Initializes sprites, physics worlds, and input listeners. This is the "setup" phase.
    3. update(time, delta): Executes every frame. delta is the time elapsed since the last frame (ms). Use it for frame-rate independent movement to prevent sluggish gameplay on slower machines.

Let's write a shell to verify the setup:

javascript 复制代码
import Phaser from 'phaser';

class MainScene extends Phaser.Scene {
  constructor() { super('MainScene'); }
  preload() { console.log('Loading assets...'); }
  create() {
    this.add.text(400, 300, 'Phaser environment ready', { fontSize: '28px' })
      .setOrigin(0.5).setColor('#00ffcc');
  }
  update() {}
}

const config = {
  type: Phaser.AUTO,
  width: 800, height: 600,
  backgroundColor: '#1a1a2e',
  scene: MainScene,
  physics: { default: 'arcade', arcade: { gravity: { y: 300 } } }
};
new Phaser.Game(config);

Run npm run dev. If you see the centered text in your browser, your game loop is running successfully.

🎮 Step 2: Hands-On — The "Catch Coins" Game

Text alone isn't fun. Let's add gameplay. The logic is straightforward: a basket moves left/right at the bottom. Coins drop randomly from the top. Catch them for points; miss them and you game over.

1. Prepare Assets

Place two images in public/assets/: basket.png and coin.png. (Solid color squares work fine for testing.)

2. Write the Core Logic

Replace the MainScene class with the full implementation below. Pay attention to the "why" comments:

javascript 复制代码
import Phaser from 'phaser';

class MainScene extends Phaser.Scene {
  constructor() {
    super('MainScene');
    this.score = 0;
    this.basket = null;
    this.coins = null;
  }

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

  create() {
    // 1. Create the basket, enable physics, and keep it static (immune to gravity)
    this.basket = this.physics.add.staticSprite(400, 560, 'basket').setScale(0.5);
    
    // 2. Create a coins group (object pool concept)
    this.coins = this.physics.add.group({
      key: 'coin',
      repeat: 8, // Pre-generate 9 coins
      setXY: { x: 50, y: 0, stepX: 90 }
    });
    this.coins.children.iterate(child => {
      child.setScale(0.4);
      child.setVelocityY(Phaser.Math.Between(80, 150));
      child.setBounce(1, Phaser.Math.Float());
    });

    // 3. Register collision callback
    this.physics.add.overlap(this.basket, this.coins, this.catchCoin, null, this);

    // 4. Keyboard controls
    this.cursors = this.input.keyboard.createCursorKeys();
    
    // 5. UI text
    this.scoreText = this.add.text(16, 16, 'Score: 0', { fontSize: '24px', fill: '#fff' });
  }

  update() {
    // Frame-rate independent horizontal movement
    if (this.cursors.left.isDown) {
      this.basket.body.setVelocityX(-300);
    } else if (this.cursors.right.isDown) {
      this.basket.body.setVelocityX(300);
    } else {
      this.basket.body.setVelocityX(0);
    }

    // Reset missed coins to the top
    this.coins.children.iterate(child => {
      if (child.active && child.y > 620) {
        this.resetCoin(child);
      }
    });
  }

  catchCoin(basket, coin) {
    this.score += 10;
    this.scoreText.setText(`Score: ${this.score}`);
    this.resetCoin(coin);
  }

  resetCoin(coin) {
    coin.y = Phaser.Math.Between(0, -200);
    coin.x = Phaser.Math.Between(20, 780);
    coin.setVelocityY(Phaser.Math.Between(80, 150));
  }
}

const config = {
  type: Phaser.AUTO,
  width: 800, height: 600,
  backgroundColor: '#1a1a2e',
  scene: MainScene,
  physics: { default: 'arcade', arcade: { gravity: { y: 0 } } } // No gravity needed as we manually set velocity
};

new Phaser.Game(config);

💣 Common Pitfalls & Pro Tips

When mentoring developers through their first Phaser projects, these issues come up frequently. Here's how to avoid them early:

  1. Sprite Origin & Collision Offset: Phaser defaults to setOrigin(0.5), meaning the collision box is centered on the image. Manually changing the origin often misaligns the debug physics box. For collision-heavy games, stick to the default.
  2. Frequent new in update(): JS garbage collection isn't instant. Calling this.add.image() every frame for spawning coins will cause memory leaks and frame drops. Use an Object Pool: pre-create disabled sprites in create(), activate them when needed, and recycle them when they leave the screen.
  3. Choosing a Physics Engine: Phaser bundles Arcade (lightweight, rectangle-based), Matter.js (complex polygons/rigid bodies), and Impact. For casual 2D games, always start with Arcade. It's highly optimized. Only switch to Matter if you need complex slopes or ragdoll physics.
  4. Mobile Responsiveness: 800x600 overflows on phones. Add scale: { mode: Phaser.Scale.FIT, autoCenter: Phaser.Scale.CENTER_BOTH } to your config. The canvas will automatically scale and center to fit any viewport.

📝 Summary & Next Steps

This guide skipped deep rendering pipeline theory because that's for engine contributors. As a product or web developer, mastering Scene Lifecycles + Physics Collisions + Input Events covers 90% of 2D game needs.

You've successfully run your first Phaser project. Next, try:

  • Adding pause/speed-up logic with this.input.keyboard.on('keydown-SPACE', ...)
  • Applying elastic easing to coin drops using this.tweens.add()
  • Integrating tilemap loading from the official Phaser Examples repo

Game development's biggest thrill is seeing instant visual feedback from a single line of code. Drop the perfectionism, get your hands dirty, and start building. Stuck on something? Drop a comment and I'll help you debug!


Installation Commands:

bash 复制代码
npm create vite@latest phaser-game -- --template vanilla
cd phaser-game
npm install phaser
npm run dev
Last Updated:

Comments (0)

Post Comment

Loading...
0/500
Loading comments...