Do you like my tutorials?

Then consider supporting me on Ko-fi

Talking about Bouncing Ball game, Game development, HTML5, Javascript and Phaser.

Here we go with step 2 of “Bouncing Ball” game series.

In first step we already built a complete game, but it’s time to add a twist: bonus obstacles. Which aren’t actually a bonus since you must collect them or you’ll die.

Now you have to avoid black bars and collect white bars. If you hit a black bar, you die. If you miss a white bar, you die.

Have a look at the game:

Tap or click the game to increase ball speed at the right time, avoid black bars and collect white bars.

Source code did not change that much from first prototype, obstacles now are a sprite sheet rather than an image, and each time the balls hits an obstacle or an obstacle leaves the screen to the left side, we check frame name to see if we hit/missed the right kind of bar.

Then, you can keep playing or it will be game over: have a look at the code:

var game;
var gameOptions = {
    bounceHeight: 300,
    ballGravity: 1200,
    ballPower: 1200,
    obstacleSpeed: 250,
    obstacleDistanceRange: [100, 200],
    localStorageName: 'bestballscore',
    bonusRatio: 20
}
window.onload = function() {
    let gameConfig = {
        type: Phaser.AUTO,
        backgroundColor:0x87ceeb,
        scale: {
            mode: Phaser.Scale.FIT,
            autoCenter: Phaser.Scale.CENTER_BOTH,
            parent: 'thegame',
            width: 750,
            height: 500
        },
        physics: {
            default: 'arcade'
        },
        scene: playGame
    }
    game = new Phaser.Game(gameConfig);
    window.focus();
}
class playGame extends Phaser.Scene{
    constructor(){
        super('PlayGame');
    }
    preload(){
        this.load.image('ground', 'ground.png');
        this.load.image('ball', 'ball.png');
        this.load.spritesheet('obstacle', 'obstacle.png', {
            frameWidth: 20,
            frameHeight: 40
        })
    }
    create(){
        this.obstacleGroup = this.physics.add.group();
        this.firstBounce = 0;
        this.ground = this.physics.add.sprite(game.config.width / 2, game.config.height / 4 * 3, 'ground');
        this.ground.setImmovable(true);
        this.ball = this.physics.add.sprite(game.config.width / 10 * 2, game.config.height / 4 * 3 - gameOptions.bounceHeight, 'ball');
        this.ball.body.gravity.y = gameOptions.ballGravity;
        this.ball.setBounce(1);
        this.ball.setCircle(25);
        let obstacleX = game.config.width;
        for(let i = 0; i < 10; i++){
            let obstacle = this.obstacleGroup.create(obstacleX, this.ground.getBounds().top, 'obstacle');
            obstacle.setOrigin(0.5, 1);
            obstacle.setImmovable(true);
            obstacle.setFrame((Phaser.Math.Between(0, 99) < gameOptions.bonusRatio) ? 0 : 1);
            obstacleX += Phaser.Math.Between(gameOptions.obstacleDistanceRange[0], gameOptions.obstacleDistanceRange[1])
        }
        this.obstacleGroup.setVelocityX(-gameOptions.obstacleSpeed);
        this.input.on('pointerdown', this.boost, this);
        this.score = 0;
        this.topScore = localStorage.getItem(gameOptions.localStorageName) == null ? 0 : localStorage.getItem(gameOptions.localStorageName);
        this.scoreText = this.add.text(10, 10, '');
        this.updateScore(this.score);
    }
    updateScore(inc){
        this.score += inc;
        this.scoreText.text = 'Score: ' + this.score + '\nBest: ' + this.topScore;
    }
    boost(){
        if(this.firstBounce != 0){
            this.ball.body.velocity.y = gameOptions.ballPower;
        }
    }
    getRightmostObstacle(){
        let rightmostObstacle = 0;
        this.obstacleGroup.getChildren().forEach(function(obstacle){
            rightmostObstacle = Math.max(rightmostObstacle, obstacle.x);
        });
        return rightmostObstacle;
    }
    updateObstacle(obstacle){
        this.updateScore(1);
        obstacle.x = this.getRightmostObstacle() + Phaser.Math.Between(gameOptions.obstacleDistanceRange[0], gameOptions.obstacleDistanceRange[1]);
        obstacle.setFrame((Phaser.Math.Between(0, 99) < gameOptions.bonusRatio) ? 0 : 1);
    }
    update(){
        this.physics.world.collide(this.ground, this.ball, function(){
            if(this.firstBounce == 0){
                this.firstBounce = this.ball.body.velocity.y;
            }
            else{
                this.ball.body.velocity.y = this.firstBounce;
            }
        }, null, this);
        this.physics.world.overlap(this.ball, this.obstacleGroup, function(ball, obstacle){
            if(obstacle.frame.name == 1){
                localStorage.setItem(gameOptions.localStorageName, Math.max(this.score, this.topScore));
                this.scene.start('PlayGame');
            }
            else{
                this.updateObstacle(obstacle);
            }
        }, null, this);
        this.obstacleGroup.getChildren().forEach(function(obstacle){
            if(obstacle.getBounds().right < 0){
                if(obstacle.frame.name == 0){
                    localStorage.setItem(gameOptions.localStorageName, Math.max(this.score, this.topScore));
                    this.scene.start('PlayGame');
                }
                else{
                    this.updateObstacle(obstacle);
                }
            }
        }, this)
    }
}

Source code is still uncommented because I plan to add a couple of new features, but it’s easy to see how I built a playable and challenging game in a bit more of 100 lines of code. 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.