HTML5 prototype to shoot and destroy walls in a tile based level like in “Castle Ramble” built with Phaser and Arcade physics

Read all posts about "" game

Castle Ramble by Chorrus Games, available for iOS and Android, is an infinite roguelite platformer where you can shoot at tiles and break them.

What a nice idea! Why not building a HTML5 prototype to let the hero break tiles?

We’ll start from the Yeah Bunny prototype, split the screen in two, then you will be able to jump by tapping on the left side of the screen and fire by tapping on the right side of the screen.

Once a bullet hits a tile, BOOM, the tile no longer exists. Have a look:

Click or tap on the left half of the screen to jump, double jump or wall jump. You can also slide on walls. By clicking or tapping on the right side of the screen, you will fire a bullet which can break walls.

This adds a great twist to tile based games, and here you can find the completely commented source code of the prototype:

let game;
let gameOptions = {

    // player gravity
    playerGravity: 900,

    // player friction when on wall
    playerGrip: 100,

    // player horizontal speed, in pixels per second
    playerSpeed: 200,

    // player jump force
    playerJump: 400,

    // player double jump force
    playerDoubleJump: 300,

    // bullet speed, in pixels per second
    bulletSpeed: 400
}

// constant to make "destructible" more readable
const DESTRUCTIBLE = 1;
window.onload = function() {
    let gameConfig = {
        type: Phaser.AUTO,
        backgroundColor: 0x444444,
        scale: {
            mode: Phaser.Scale.FIT,
            autoCenter: Phaser.Scale.CENTER_BOTH,
            parent: "thegame",
            width: 640,
            height: 480
        },
        physics: {
            default: "arcade",
            arcade: {
                gravity: {
                    y: 0
                }
            }
        },
        scene: [preloadGame, playGame]
    }
    game = new Phaser.Game(gameConfig);
    window.focus();
}
class preloadGame extends Phaser.Scene{
    constructor(){
        super("PreloadGame");
    }
    preload(){
        this.load.tilemapTiledJSON("level", "level.json");
        this.load.image("tile", "tile.png");
        this.load.image("hero", "hero.png");
        this.load.image("bullet", "bullet.png");
    }
    create(){
        this.scene.start("PlayGame");
    }
}
class playGame extends Phaser.Scene{
    constructor(){
        super("PlayGame");
    }
    create(){

        // creation of "level" tilemap
        this.map = this.make.tilemap({
            key: "level"
        });

        // adding tiles to tilemap
        let tile = this.map.addTilesetImage("tileset01", "tile");

        // tiles 1 and 2 have collision enabled
        this.map.setCollision([1, 2]);

        // which layer should we render? "layer01"
        this.layer = this.map.createDynamicLayer("layer01", tile);

        // add the hero sprite and enable ARCADE physics
        this.hero = this.physics.add.sprite(300, 376, "hero");

        // set hero horizontal speed
        this.hero.body.velocity.x = gameOptions.playerSpeed;

        // the hero can jump
        this.canJump = true;

        // the hero cannot double jump
        this.canDoubleJump = false;

        // the hero is not on the wall
        this.onWall = false;

        // the hero can fire a bullet
        this.canFire = true;

        // add the bullet and enable arcade physics
        this.bullet = this.physics.add.sprite(-20, -20, "bullet");

        // wait for player input
        this.input.on("pointerdown", this.handleInput, this);

        // set workd bounds to allow camera to follow the player
        this.cameras.main.setBounds(0, 0, 1920, 1440);

        // make the camera follow the player
        this.cameras.main.startFollow(this.hero);
    }

    handleInput(e){

        // left half of the screen = jump
        if(e.x < game.config.width / 2){

            // the hero can jump when:
            // canJump is true AND the hero is on the ground (blocked.down)
            // OR
            // the hero is on the wall
            if((this.canJump && this.hero.body.blocked.down) || this.onWall){

                // apply jump force
                this.hero.body.velocity.y = -gameOptions.playerJump;

                // is the hero on a wall?
                if(this.onWall){

                    // change the horizontal velocity too. This way the hero will jump off the wall
                    this.setPlayerXVelocity(true);
                }

                // hero can't jump anymore
                this.canJump = false;

                // hero is not on the wall anymore
                this.onWall = false;

                // hero can now double jump
                this.canDoubleJump = true;
            }
            else{

                // can the hero make the doubple jump?
                if(this.canDoubleJump){

                    // the hero can't double jump anymore
                    this.canDoubleJump = false;

                    // apply double jump force
                    this.hero.body.velocity.y = -gameOptions.playerDoubleJump;
                }
            }
        }

        // right half of the sceen = fire
        else{

            // can the player fire?
            if(this.canFire){

                // player can't fire anymore
                this.canFire = false;

                // place the bullet at player position
                this.bullet.x = this.hero.x;
                this.bullet.y = this.hero.y;

                // set bullet speed
                this.bullet.body.velocity.x = gameOptions.bulletSpeed * ((this.hero.body.velocity.x > 0) ? 1 : -1);
            }
        }
    }
    update(){

        // set some default gravity values. Look at setDefaultValues method for more information
        this.setDefaultValues();

        // handle collision between bullet and tiles
        this.physics.world.collide(this.bullet, this.layer, function(bullet, layer){

            // stop the bullet
            bullet.body.velocity.x = 0;

            // place the bullet out of the stage
            bullet.x = -20;

            // now the player can fire again
            this.canFire = true;

            // is the tile destructible?
            if(layer.index == DESTRUCTIBLE){

                // remove the tile from the map
                this.map.removeTileAt(layer.x, layer.y);
            }
        }, null, this);

        // handle collision between hero and tiles
        this.physics.world.collide(this.hero, this.layer, function(hero, layer){

            // some temporary variables to determine if the player is blocked only once
            let blockedDown = hero.body.blocked.down;
            let blockedLeft = hero.body.blocked.left
            let blockedRight = hero.body.blocked.right;

            // if the hero hits something, no double jump is allowed
            this.canDoubleJump = false;

            // hero on the ground
            if(blockedDown){

                // hero can jump
                this.canJump = true;
            }

            // hero on the ground and touching a wall on the right
            if(blockedRight){

                // horizontal flipping hero sprite
                hero.flipX = true;
            }

            // hero on the ground and touching a wall on the right
            if(blockedLeft){

                // default orientation of hero sprite
                hero.flipX = false;
            }

            // hero NOT on the ground and touching a wall
            if((blockedRight || blockedLeft) && !blockedDown){

                // hero on a wall
                hero.scene.onWall = true;

                // remove gravity
                hero.body.gravity.y = 0;

                // setting new y velocity
                hero.body.velocity.y = gameOptions.playerGrip;
            }

            // adjust hero speed according to the direction it's moving
            this.setPlayerXVelocity(!this.onWall || blockedDown);
        }, null, this)
    }

    // default values to be set at the beginning of each update cycle,
    // which may be changed according to what happens into "collide" callback function
    // (if called)
    setDefaultValues(){
        this.hero.body.gravity.y = gameOptions.playerGravity;
        this.onWall = false;
        this.setPlayerXVelocity(true);
    }

    // set player velocity according to the direction it's facing, unless "defaultDirection"
    // is false, in this case multiplies the velocity by -1
    setPlayerXVelocity(defaultDirection){
        this.hero.body.velocity.x = gameOptions.playerSpeed * (this.hero.flipX ? -1 : 1) * (defaultDirection ? 1 : -1);
    }
}

Another great game concept built in a few lines thanks to Phaser power. Download the source code and play with it.

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
// 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
// Save the Totem
// 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