How to Build a Mario Platformer with Phaser


Building a Super Mario Bros-style platformer teaches every core skill in 2D game development: physics simulation, collision detection, scene management, and responsive input handling. In this tutorial, you’ll create a fully playable side-scrolling platformer with coins, enemies, question blocks, touch controls, and a flagpole finish — all in a single HTML file using Phaser 3.

The game runs on desktop browsers with keyboard controls and on mobile devices with on-screen touch buttons. Every sprite is generated procedurally at runtime, so no external image files are needed.

How This Was Researched

This guide was developed by analyzing the official Phaser 3 documentation and the Phaser Examples repository, which provide tested patterns for arcade physics, sprite generation, and scene management. We followed Phaser’s recommended class-based scene architecture and verified API usage against Phaser 3.80.1, the current stable release available on the jsDelivr CDN. All code patterns are based on documented APIs and community-proven approaches — this is a code walkthrough, not a hands-on product review. Last researched: August 2026.

Prerequisites

You need a modern web browser (Chrome, Firefox, Safari, or Edge), a text editor for saving the HTML file, and basic JavaScript knowledge including classes and arrow functions. No build tools, package managers, or local servers are required — save the file and open it directly in your browser. Understanding of object-oriented programming helps, but the tutorial explains every concept as it appears in the code.

Game Architecture

The game uses Phaser’s class-based scene system to separate concerns cleanly into four distinct scenes. BootScene generates all pixel-art textures procedurally using the graphics API, then hands off to GameScene where the actual gameplay happens. GameOverScene and LevelCompleteScene handle end states with click-to-restart behavior.

Each scene extends Phaser.Scene and manages its own lifecycle through create() and update() methods. This pattern keeps code organized and makes adding new features — like additional levels or enemy types — straightforward without touching existing logic.

class BootScene extends Phaser.Scene {
  constructor() { super({ key: 'BootScene' }); }
  create() {
    this.generateTextures();
    this.scene.start('GameScene');
  }
}

The game config sets 800×600 resolution with Phaser.Scale.FIT and Phaser.Scale.CENTER_BOTH for responsive scaling across devices. Arcade physics provides gravity at 600 pixels per second squared, which feels snappy for platformer movement.

Player Mechanics

Movement uses acceleration-based physics instead of direct velocity assignment. When you press left, the player accelerates at -500 pixels per second squared; releasing the key sets acceleration to zero and velocity to zero for instant stopping. This creates responsive, tight controls that feel good on both keyboard and touch.

Variable-height jumping works by checking the jump button state during the upward phase. When jump is pressed and the player is on the floor, velocity is set to -400. While the button is held and velocity is still negative (rising), extra downward acceleration is reduced to let the player float higher. Releasing the button mid-air applies stronger gravity to cut the jump short.

if (left) {
  this.player.setAccelerationX(-500);
  this.player.setFlipX(true);
} else if (right) {
  this.player.setAccelerationX(500);
  this.player.setFlipX(false);
} else {
  this.player.setAccelerationX(0);
  this.player.setVelocityX(0);
}

Enemy stomping checks two conditions: the player’s feet must be touching the enemy’s head (player.body.touching.down && enemy.body.touching.up). A successful stomp destroys the enemy and bounces the player upward. Side contact with an enemy costs a life and triggers a respawn at the starting position.

Level Design

The level spans 3200 pixels horizontally with ground tiles forming a solid base at the bottom. Floating platforms are placed at varying heights using a static physics group, creating vertical traversal challenges that require precise jumping.

Coins are scattered across platforms and open spaces. Each coin spins using a tween animation that rotates it 360 degrees over one second. Collecting a coin adds 10 points to the score and plays a sine-wave beep through the Web Audio API.

Question blocks sit above platforms and spawn coins when the player jumps into them from below. Nine goomba enemies patrol the ground level using velocity reversal on world bounds collision — they bounce off walls and change direction automatically.

A flagpole at position 2800 marks the end of the level. When the player touches it, the game transitions to the LevelCompleteScene victory screen.

Adding Polish

The heads-up display shows score, lives, and a countdown timer fixed to the screen using setScrollFactor(0). The timer decrements once per second and triggers game over when it reaches zero, adding time pressure to the platforming challenge.

Touch controls appear as three circular buttons at the bottom of the screen: left arrow, right arrow, and jump. These use touchstart and touchend events with CSS touch-action: none to prevent scrolling interference on mobile devices. The buttons visually highlight when pressed using a CSS class toggle.

Sound effects are generated in real time using the Web Audio API. Jump sounds use a 400Hz sine wave, coin collection uses 800Hz, and enemy stomps use a 150Hz square wave. Each sound lasts 0.1 to 0.2 seconds — short enough to feel responsive without becoming repetitive.

What You Learned

You’ve built a complete platformer with class-based scene architecture, procedural texture generation, acceleration-based player movement, variable-height jumping, enemy patrol AI, coin collection, question block interactions, touch controls for mobile, and a Web Audio sound system. These patterns work for any 2D action game — the class-based structure scales well as you add more enemy types, power-ups, or multi-level progression. For more Phaser projects, explore our games hub, try the Phaser Breakout tutorial, or check out the Pac-Man build.

FAQ

How do I add more levels to the game?

Create additional scene classes with different platform layouts and enemy placements, then register them in the game config’s scene array. Pass level data through the scene’s init() method and use this.scene.start('Level2Scene', { level: 2 }) to transition when the player reaches the flagpole.

Can I replace the procedural sprites with real artwork?

Yes. Replace the generateTexture() calls in BootScene.create() with standard image loading: use this.load.image('player', 'assets/player.png') in a preload() method, then reference the texture keys normally. The physics bodies and gameplay code remain identical.

Why does the game feel different on mobile versus desktop?

Touch input has no key-repeat behavior like keyboards do, so holding a direction button keeps the acceleration constant rather than repeating. The Phaser.Scale.FIT configuration rescales the canvas to fit the screen while maintaining the 4:3 aspect ratio, which may make elements appear smaller on phones. Adjust the button sizes in CSS to match your target device.