Build a HTML5 game like “Magick” iPad game using Phaser and ARCADE physics – adding multiple levels

Read all posts about "" game

Time to see how to add multiple levels to Magick game. I created four levels with Tiled, in four different layers.

At the moment we have four different tiles: a ground tile, the block tile which can be added by players, a deadly tile and the exit tile.

Look at the prototype:

Try to complete all levels. Actually levels 1 and 3 are quite easy.

The player walks and climbs on his own, you can only click anywhere on an empty spot to summon a block, but you can summon only one blockat once.

You can also change player direction by tapping on it.

The core of this prototype is in filterTiles method which allows you to define tiles subsets and define what happens when the player overlaps such tiles.

The source code is pretty straightforward and consists in one HTML file, one CSS file and six TypeScript files.

It’s still uncommented, I will add comment during next steps, when I’ll add more tile types.

index.html

The web page which hosts the game, to be run inside thegame element.

<!DOCTYPE html>
<html>
    <head>
        <meta name="viewport" content="initial-scale=1, maximum-scale=1">
        <link rel="stylesheet" href="style.css">
        <script src="main.js"></script> 
    </head>
    <body>   
        <div id = "thegame"></div>
    </body>
</html>

style.css

The cascading style sheets of the main web page.

* {
    padding : 0;
    margin : 0;
}

body {
    background-color: #000000;    
}

canvas {
    touch-action : none;
    -ms-touch-action : none;
}

gameOptions.ts

Configurable game options. It’s a good practice to place all configurable game options, if possible, in a single and separate file, for a quick tuning of the game. I also grouped the variables to keep them more organized.

// CONFIGURABLE GAME OPTIONS
// changing these values will affect gameplay

export const GameOptions = {

    player : {
        speed : 120,
        jumpSpeed : {
            x : 30,
            y : -100
        },
        gravity : 400,
        triggerRadius : 32
    },

    tileSize : 32
}

main.ts

This is where the game is created, with all Phaser related options.

// MAIN GAME FILE

// modules to import
import Phaser from 'phaser';
import { PreloadAssets } from './preloadAssets';
import { PlayGame } from './playGame';

// object to initialize the Scale Manager
const scaleObject : Phaser.Types.Core.ScaleConfig = {
    mode : Phaser.Scale.FIT,
    autoCenter : Phaser.Scale.CENTER_BOTH,
    parent : 'thegame',
    width : 800,
    height : 320
}

// game configuration object
const configObject : Phaser.Types.Core.GameConfig = {
    type : Phaser.AUTO,
    backgroundColor : 0x000000,
    scale : scaleObject,
    scene : [PreloadAssets, PlayGame],
    physics : {
        default : 'arcade',
        arcade : {
            gravity : {
                y : 0
            }
        }
    }
}

// the game itself
new Phaser.Game(configObject);

preloadAssets.ts

Here we preload all assets to be used in the game.

// CLASS TO PRELOAD ASSETS
 
// this class extends Scene class
export class PreloadAssets extends Phaser.Scene {
 
    // constructor    
    constructor() {
        super({
            key : 'PreloadAssets'
        });
    }
 
    // method to be execute during class preloading
    preload() : void {

        this.load.image('tiles', 'assets/sprites/tiles.png');
        this.load.tilemapTiledJSON('map', 'assets/maps/map.json');
        this.load.image('player', 'assets/sprites/player.png');
       
    }
 
    // method to be called once the instance has been created
    create() : void {

        // call PlayGame class
        this.scene.start('PlayGame');
    }
}

playGame.ts

Main game file, all game logic is stored here.

// THE GAME ITSELF

import { GameOptions } from './gameOptions';
import { Player } from './player';
import { MapUtils } from './mapUtils';

enum TileType {
    None,
    Ground,
    Block,
    Deadly,
    Exit    
}

// this class extends Scene class
export class PlayGame extends Phaser.Scene {

    constructor() {
        super({
            key : 'PlayGame'
        });
    }

    map : Phaser.Tilemaps.Tilemap;
    player : Player;
    levelLayer : Phaser.Tilemaps.TilemapLayer;
    specialTiles : Phaser.Tilemaps.Tile[];
    currentLevel : number;

    init(data : any) : void {
        if (data.level != undefined) {
            if (data.level > 4) {
                data.level = 1;
            }
            this.currentLevel = data.level;
        }
        else {
            this.currentLevel = 1;
        }
    }
  
    // method to be executed when the scene has been created
    create() : void {
        this.map = this.make.tilemap({
            key : 'map'
        });        
        let tileSet : Phaser.Tilemaps.Tileset = this.map.addTilesetImage('tileset01', 'tiles') as Phaser.Tilemaps.Tileset;
        this.levelLayer = this.map.createLayer('Level' + this.currentLevel.toString(), tileSet) as Phaser.Tilemaps.TilemapLayer;
        this.map.setCollisionBetween(TileType.Ground, TileType.Block);
        this.specialTiles = this.map.filterTiles(this.filterSpecialTiles, this) as Phaser.Tilemaps.Tile[];
        this.player = new Player(this, 128, 128);
        this.input.on('pointerdown', this.handleInput, this);        
    }

    filterSpecialTiles(tile : Phaser.Tilemaps.Tile) : boolean {
        return tile.index == TileType.Deadly || tile.index == TileType.Exit;
    }

    handleInput(pointer : Phaser.Input.Pointer) : void {
        if (Phaser.Math.Distance.Between(pointer.x, pointer.y, this.player.x, this.player.y) < GameOptions.player.triggerRadius) {
            this.player.changeDirection();    
        }  
        else {
            MapUtils.addTile(this.map, pointer.x, pointer.y);
        }  
    }

    handleSpecialTiles(player : any, tile : any) : void {
        switch (tile.index) {
            case TileType.Deadly :
                this.scene.start('PlayGame', {
                    level : this.currentLevel
                }); 
                break;
            case TileType.Exit :
                this.scene.start('PlayGame', {
                    level : this.currentLevel + 1
                }); 
                break;
        }
    }

    movePlayer() : void {
        this.player.move(this.map);
    }

    update() : void {
        this.player.stopMoving();
        this.physics.world.collide(this.player, this.levelLayer, this.movePlayer, undefined, this);
        this.physics.world.overlapTiles(this.player, this.specialTiles, this.handleSpecialTiles, undefined, this);
    }

}

player.ts

The player, a custom class which extends Phaser.Physics.Arcade.Sprite object.

// PLAYER CLASS EXTENDS PHASER.PHYSICS.ARCADE.SPRITE

import { GameOptions } from './gameOptions';

export class Player extends Phaser.Physics.Arcade.Sprite {

    isJumping : boolean;
    direction : number;
    body : Phaser.Physics.Arcade.Body;

    constructor(scene : Phaser.Scene, posX : number, posY : number) {
        super(scene, posX, posY, 'player');
        scene.add.existing(this);
        scene.physics.add.existing(this);
        this.isJumping = false;
        this.direction = 1;
        this.body.gravity.y = GameOptions.player.gravity;
    }

    move(map : Phaser.Tilemaps.Tilemap) : void {
        if (this.body.blocked.down) {
            this.body.setVelocityX(GameOptions.player.speed * this.direction);
            this.isJumping = false;
        }
        if (this.body.blocked.right && this.direction == 1) {
            if ((!map.getTileAtWorldXY(this.x + GameOptions.tileSize, this.y - GameOptions.tileSize) && !map.getTileAtWorldXY(this.x, this.y - GameOptions.tileSize)) || this.isJumping) {
                this.jump();
            }
            else {
                this.direction *= -1;
            }
        }
        if(this.body.blocked.left && this.direction == -1) {
            if((!map.getTileAtWorldXY(this.x - GameOptions.tileSize, this.y - GameOptions.tileSize) && !map.getTileAtWorldXY(this.x, this.y - GameOptions.tileSize)) || this.isJumping) {
                this.jump();
            }
            else {
                this.direction *= -1;
            }
        }
    }

    changeDirection() : void {
        this.direction *= -1;
    }

    stopMoving() : void {
        this.setVelocityX(0);
    }

    jump() : void {
        this.body.setVelocity(GameOptions.player.jumpSpeed.x * this.direction, GameOptions.player.jumpSpeed.y);
            this.isJumping = true;
        }
}

mapUtils.ts

A simple class to store map utilities.

export class MapUtils {

    static tilePoint : Phaser.Math.Vector2 | null = null;
    
    static addTile(map: Phaser.Tilemaps.Tilemap, posX: number, posY: number) {
        if (!map.getTileAtWorldXY(posX, posY)) {
            if (this.tilePoint != null) {
                map.removeTileAtWorldXY(this.tilePoint.x, this.tilePoint.y);
            }
            map.putTileAtWorldXY(2, posX, posY);
            this.tilePoint = new Phaser.Math.Vector2(posX, posY);
        }      
    }
}

Once you are able to design levels, the limit is the sky, but we’ll see how to add more tile types in next steps, meanwhile download the source code.

Get the most popular Phaser 3 book

Through 202 pages, 32 source code examples and an Android Studio project you will learn how to build cross platform HTML5 games and create a complete game along the way.

Get the book

215 GAME PROTOTYPES EXPLAINED WITH SOURCE CODE
// 1+2=3
// 100 rounds
// 10000000
// 2 Cars
// 2048
// A Blocky Christmas
// A Jumping Block
// A Life of Logic
// Angry Birds
// Angry Birds Space
// Artillery
// Astro-PANIC!
// Avoider
// Back to Square One
// Ball Game
// Ball vs Ball
// Ball: Revamped
// Balloon Invasion
// BallPusher
// Ballz
// Bar Balance
// Bejeweled
// Biggification
// Block it
// Blockage
// Bloons
// Boids
// Bombuzal
// Boom Dots
// Bouncing Ball
// Bouncing Ball 2
// Bouncy Light
// BoxHead
// Breakout
// Bricks
// Bubble Chaos
// Bubbles 2
// Card Game
// Castle Ramble
// Chronotron
// Circle Chain
// Circle Path
// Circle Race
// Circular endless runner
// Cirplosion
// CLOCKS - The Game
// Color Hit
// Color Jump
// ColorFill
// Columns
// Concentration
// Crossy Road
// Crush the Castle
// Cube Jump
// CubesOut
// Dash N Blast
// Dashy Panda
// Deflection
// Diamond Digger Saga
// Don't touch the spikes
// Dots
// Down The Mountain
// Drag and Match
// Draw Game
// Drop Wizard
// DROP'd
// Dudeski
// Dungeon Raid
// Educational Game
// Elasticity
// Endless Runner
// Erase Box
// Eskiv
// Farm Heroes Saga
// Filler
// Flappy Bird
// Fling
// Flipping Legend
// Floaty Light
// Fuse Ballz
// GearTaker
// Gem Sweeper
// Globe
// Goat Rider
// Gold Miner
// Grindstone
// GuessNext
// Helicopter
// Hero Emblems
// Hero Slide
// Hexagonal Tiles
// HookPod
// Hop Hop Hop Underwater
// Horizontal Endless Runner
// Hundreds
// Hungry Hero
// Hurry it's Christmas
// InkTd
// Iromeku
// Jet Set Willy
// Jigsaw Game
// Knife Hit
// Knightfall
// Legends of Runeterra
// Lep's World
// Line Rider
// Lumines
// Magick
// MagOrMin
// Mass Attack
// Math Game
// Maze
// Meeblings
// Memdot
// Metro Siberia Underground
// Mike Dangers
// Mikey Hooks
// Mini Archer
// Nano War
// Nodes
// o:anquan
// One Button Game
// One Tap RPG
// Ononmin
// Pacco
// Perfect Square!
// Perfectionism
// Phyballs
// Pixel Purge
// PixelField
// Planet Revenge
// Plants Vs Zombies
// Platform
// Platform game
// Plus+Plus
// Pocket Snap
// Poker
// Pool
// Pop the Lock
// Pop to Save
// Poux
// Pudi
// Pumpkin Story
// Puppet Bird
// Pyramids of Ra
// qomp
// Quick Switch
// Racing
// Radical
// Rebuild Chile
// Renju
// Rise Above
// Risky Road
// Roguelike
// Roly Poly
// Run Around
// Rush Hour
// SameGame
// SamePhysics
// Security
// Serious Scramblers
// Shrink it
// Sling
// Slingy
// Snowflakes
// Sokoban
// Space Checkers
// Space is Key
// Spellfall
// Spinny Gun
// Splitter
// Spring Ninja
// Sproing
// Stabilize!
// Stack
// Stairs
// Stick Hero
// String Avoider
// Stringy
// Sudoku
// Super Mario Bros
// Surfingers
// Survival Horror
// Talesworth Adventure
// Tetris
// The Impossible Line
// The Moops - Combos of Joy
// The Next Arrow
// Threes
// Tic Tac Toe
// Timberman
// Tiny Wings
// Tipsy Tower
// Toony
// Totem Destroyer
// Tower Defense
// Trick Shot
// Tunnelball
// Turn
// Turnellio
// TwinSpin
// vvvvvv
// Warp Shift
// Way of an Idea
// Whack a Creep
// Wheel of Fortune
// Where's my Water
// Wish Upon a Star
// Word Game
// Wordle
// Worms
// Yanga
// Yeah Bunny
// Zhed
// zNumbers