Do you like my tutorials?

Then consider supporting me on Ko-fi

Talking about Radical game, Game development, HTML5, Javascript and Phaser.

Radical tutorial series has not been updated for a long time and it still runs on an old Phaser 2 version, so it’s time to update it to Phaser 3 and add some new features such as Arcade groups and object pooling.

Let’s have a look at the game before explaining it:

Playing is easy: tap on the left half of the screen to move left, and on the right half of the screen to move right.

Hit a barrier and it’s game over.

You can wrap around the screen thanks to Phaser.Math.Wrap method which wraps a value around a minimum and a maximum (line 81).

All barriers are inside a physics group created with this.physics.add.group (line 55). When you create a phyiscs group and add elements into it, they automatically become physics bodies and you will be able to apply physics features to each body inside a physics group just by applying them to the group itself, just like we are doing at line 60 with setVelocityY method which set vertical velocity to all barriers inside the group (line 60).

All barriers are created at the beginning of the game, we created 10 barriers which are way more than the ones needed to cover the entire play area (line 56) and we just move barriers to the top once they leave the stage from the bottom.

This way we can render an infinite tunnel only using 10 barriers.

Have a look at the source code:

let game;
let gameOptions = {
    shipHorizontalSpeed: 400,       // ship horizontal speed, can be modified to change gameplay
    barrierSpeed: 100,              // barrier vertical speed, can be modified to change gameplay
    barrierGap: 150,                // gap between two barriers, in pixels
    safeZones: 5                    // amount of possible safe zone. It affects safe zone width
}
window.onload = function() {
    let gameConfig = {
        type: Phaser.AUTO,
        backgroundColor:0x222222,
        scale: {
            mode: Phaser.Scale.FIT,
            autoCenter: Phaser.Scale.CENTER_BOTH,
            parent: "thegame",
            width: 320,
            height: 480
        },
        scene: playGame,
        physics: {
            default: 'arcade',
            arcade: {
                gravity: {
                    y: 0
                }
            }
        }
    }
    game = new Phaser.Game(gameConfig);
    window.focus();
}
class playGame extends Phaser.Scene{
    constructor(){
        super("PlayGame");
    }
    preload(){
        this.load.image("ship", "ship.png");
        this.load.image("barrier", "barrier.png");
    }
    create(){
        this.ship = this.physics.add.sprite(game.config.width / 2, game.config.height / 5 * 4, "ship");
        this.input.on("pointerdown", this.moveShip, this);
        this.input.on("pointerup", this.stopShip, this);
        this.addBarriers();

    }
    moveShip(p){
        let speedMultiplier = (p.x < game.config.width / 2) ? -1 : 1;
        this.ship.body.velocity.x = gameOptions.shipHorizontalSpeed * speedMultiplier;
    }
    stopShip(){
        this.ship.body.velocity.x = 0;
    }
    addBarriers(){
        this.horizontalBarrierGroup = this.physics.add.group()
        for(let i = 0; i < 10; i++){
            this.horizontalBarrierPool = [this.horizontalBarrierGroup.create(0, 0, "barrier"), this.horizontalBarrierGroup.create(0, 0, "barrier")];
            this.placeHorizontalBarriers();
        }
        this.horizontalBarrierGroup.setVelocityY(gameOptions.barrierSpeed);
    }
    getTopmostBarrier(){
        let topmostBarrier = game.config.height;
        this.horizontalBarrierGroup.getChildren().forEach(function(barrier){
            topmostBarrier = Math.min(topmostBarrier, barrier.y)
        });
        return topmostBarrier;
    }
    placeHorizontalBarriers(){
        let topmost = this.getTopmostBarrier();
        let holePosition = Phaser.Math.Between(0, gameOptions.safeZones - 1);
        this.horizontalBarrierPool[0].x = holePosition * game.config.width / gameOptions.safeZones;
        this.horizontalBarrierPool[0].y = topmost - gameOptions.barrierGap;
        this.horizontalBarrierPool[0].setOrigin(1, 0);
        this.horizontalBarrierPool[1].x = (holePosition + 1) * game.config.width / gameOptions.safeZones;
        this.horizontalBarrierPool[1].y = topmost - gameOptions.barrierGap;
        this.horizontalBarrierPool[1].setOrigin(0, 0);
        this.horizontalBarrierPool = [];
    }
    update(){
        this.ship.x = Phaser.Math.Wrap(this.ship.x, 0, game.config.width);
        this.physics.world.collide(this.ship, this.horizontalBarrierGroup, function(){
            this.scene.start("PlayGame");
        }, null, this);
        this.horizontalBarrierGroup.getChildren().forEach(function(barrier){
            if(barrier.y > game.config.height){
                this.horizontalBarrierPool.push(barrier);
                if(this.horizontalBarrierPool.length == 2){
                    this.placeHorizontalBarriers();
                }
            }
        }, this);
    }
}

Once more, less than 100 lines to create a fully working, optimized physics prototype thanks to Phaser. Download the source code.

Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.