Try to keep jumping over the endless stream of cacti.

Controls

  • Spacebar: Jump in the air
  • Arrow key down: Slam the ball back to the ground
  • Arrow key left: Move the ball to the left of the screen
  • Arrow key right: Move the ball to the right of the screen

/**
 * WARNING: TRY TO FIGURE THE EXERCISES OUT ON YOUR OWN!
 * ONLY USE MY SOURCE CODE AS A LAST RESORT, GOOD LUCK!
 */

// Configuration for the game
// Other options that can be set in this config:
// https://photonstorm.github.io/phaser3-docs/Phaser.Core.Config.html
const config = {
    type: Phaser.AUTO,
    // width of the game
    width: 800,
    // height of the game
    height: 450,
    // the html id of the parent object, 
    // useful to specify where in a webpage the game will be
    parent: 'game-area',
    // background color of the game
    backgroundColor: 0x6dc4f2,
    // function names for the default engine events
    scene: {
        preload: preload,
        create: create,
        update: update
    },
    // This options turns off blurring when scaling up
    pixelArt: true,
    physics: {
        // use the arcade physics engine
        default: 'arcade',
        arcade: {
            // debug option to show velocity lines and
            // collision boxes
            debug: false
        }
    }
};

// Create a new Phaser Game
const game = new Phaser.Game(config);
let scene;

// Objects in the game
let ball, ground;
// UI elements
let scoretext, infotext;
// groups
let clouds, pillars;
// Input objects
let left, right;

let jumping = false;
let playing = false;
// Declare a variable to keep score
let score = 0;
// Speed of the game
let speed = 5;
// Current rotation of the ball
let rotation = 0;
// amount of updates before we level up
const levelThreshold = 1200;
// update counter
let upd = 1;
// add a new pillar next levelup
let pillarspawn = false;
// Async pointer to cloud spawning
let spawner;

/**
 * Preload function
 * This function gets executed once before the create function
 *
 * Use this function to load all assets (images, music, sounds) into
 * memory so you can use them later to build your game scene
 */
function preload() {
    // Load the sprites for our game
    this.load.image(
        'ball',
        'https://hubben-amal.github.io/game-io-workshops/img/bouncy-ball/ball.png'
    );
    this.load.image(
        'ground',
        'https://hubben-amal.github.io/game-io-workshops/img/bouncy-ball/ground.png'
    );
    this.load.image(
        'cactus',
        'https://hubben-amal.github.io/game-io-workshops/img/bouncy-ball/cactus.png'
    );
    this.load.image(
        'cloud',
        'https://hubben-amal.github.io/game-io-workshops/img/bouncy-ball/cloud.png'
    );
    // Load the audio for the effects
    this.load.audio('jump', 'https://hubben-amal.github.io/game-io-workshops/sound/bouncy-ball/jump.wav');
    this.load.audio('score', 'https://hubben-amal.github.io/game-io-workshops/sound/bouncy-ball/score.wav');
    this.load.audio('death', 'https://hubben-amal.github.io/game-io-workshops/sound/bouncy-ball/death.wav');
}

/**
 * Create function
 * This function gets executed once after the preload and creates the game scene
 *
 * Use this function to create and configure all your game elements in the scene
 */
function create() {
    // Add the bouncy ball physics object to the game
    ball = this.physics.add.sprite(100, 380, 'ball');
    // scaling the ball
    ball.setScale(2);
    // Add gravity to the ball
    ball.setGravityY(600);
    ball.setCollideWorldBounds(true);
    ball.setDragX(20);

    // Create and add the ground
    ground = this.add.tileSprite(400, 418, 400, 32, 'ground');
    ground.setScale(2);
    // Move more to front
    ground.setDepth(1);

    // create a physics group for pillars and clouds
    pillars = this.physics.add.group();
    clouds = this.physics.add.group();
    // Collision detection
    this.physics.add.overlap(ball, pillars, gameover, null, this);
    // Setup the bounds for the world
    this.physics.world.setBounds(0, 0, this.sys.game.config.width, this.sys.game.config.height - 50);

    // Setup inputs
    this.input.keyboard.on('keydown_SPACE', handleSpace);
    this.input.keyboard.on('keydown_DOWN', slam);
    // we add the keys as well so the default functions of scrolling the page are disabled
    this.input.keyboard.addKey('SPACE');
    this.input.keyboard.addKey('DOWN');

    left = this.input.keyboard.addKey('LEFT');
    right = this.input.keyboard.addKey('RIGHT');

    // displaying score
    scoretext = this.add.text(400, 50, score, {
        fontFamily: '"Arial"',
        fontSize: '50pt',
        color: '#000000'
    });
    scoretext.setDepth(5);
    // displaying info at the start of the game
    infotext = this.add.text(50, 400, 'Press space to start game', {
        fontFamily: '"Arial"',
        fontSize: '20pt'
    });
    infotext.setDepth(5);

    scene = this;
}

/**
 * Spawn method for clouds in the sky
 */
function cloudspawn() {
    // Create a new cloud
    const cl = scene.physics.add.sprite(900, Phaser.Math.RND.between(20, 150), 'cloud');
    cl.setScale(Phaser.Math.RND.between(2, 4));
    // Add to a group
    clouds.add(cl);
    // Random depth and speed
    const depth = Phaser.Math.RND.between(2, 4);
    cl.setVelocityX(-speed * (depth * 2 + 16));
    cl.setDepth(depth);
    // Spawn a new cloud inbetween 1 to 2.5 seconds
    spawner = setTimeout(cloudspawn, Phaser.Math.RND.between(1000, 2500));
}

/**
 * Spawn method for pillars
 */
function createPillar() {
    // Add a new physics object to the game
    const pillar = scene
        .physics
        .add
        .sprite(Phaser.Math.RND.between(800, 1500), 350, 'cactus');
    pillar.setScale(2);
    // Add it to the pillar group
    pillars.add(pillar);
    // Set the velocity
    pillar.setVelocityX(-speed * 60);
}

/**
 * Function that is called each time the spacebar is pressed
 */
function handleSpace() {
    if (playing) {
        if (!jumping) {
            scene.sound.play('jump');
            jump();
        }
    } else {
        playing = true;
        // hiding the infotext when playing
        infotext.visible = false;
        // create a pillar when we start the game
        createPillar();
        // start spawning clouds
        cloudspawn();
    }
}

/**
 * Function that slams the ball back to the ground
 */
function slam() {
    ball.setVelocityY(800);
}

/**
 * Move the ball to the right
 */
function moveRight() {
    ball.setVelocityX(ball.body.velocity.x + 5);
}

/**
 * Move the ball to the left
 */
function moveLeft() {
    ball.setVelocityX(ball.body.velocity.x - 5);
}

/**
 * Main update loop
 * The code in this function gets executed on every game update (default 60 times per second)
 *
 * Use this function for all elements that change overtime
 */
function update() {
    if (playing) {
        upd += 1;
        if (upd % levelThreshold === 0) {
            // Leveling up
            if (pillarspawn) {
                createPillar();
            }
            // One extra pillar will be spawned for each two levelups
            pillarspawn = !pillarspawn;
            // increase the speed
            speed += 0.5;
            pillars.children.each(pillar => pillar.setVelocityX(-speed * 60));
        }
        if (left.isDown) {
            moveLeft();
        }
        if (right.isDown) {
            moveRight();
        }
        // Calculate the rotation of the ball using the speed of the ground and the ball
        rotation += 2 * (speed + ball.body.velocity.x / 60) / 64;
        ball.setRotation(rotation);

        // Check if the ball is back on the ground
        if (ball.body.bottom === this.physics.world.bounds.bottom) {
            jumping = false;
        }

        // increase the position on the x-axis by half the speed to make
        // the ground move in the same direction and speed as the cacti
        ground.tilePositionX += speed / 2;

        pillars.children.each(pillar => {
            // if the pillar is off the screen
            if (pillar.body.right < 0) {
                // Score a point
                this.sound.play('score');
                scoreOnePoint();
                // destroy the pillar that is outside of the screen
                pillar.destroy();
            }
        });
    }
}

function jump() {
    // set velocity of the ball
    ball.setVelocityY(-500);
    // set jumping state to true so we can only jump once
    jumping = true;
}

function scoreOnePoint() {
    // create a new pillar
    createPillar();
    // Adding a point to the score
    score += 1;
    // update score text
    scoretext.text = score;
}

function gameover() {
    this.sound.play('death');
    // Game is over, so we are not playing
    playing = false;
    // Display the final score
    infotext.text = 'Final score ' + score + ', press space to start a new game';
    infotext.visible = true;
    // reset the game
    resetGame();
}

function resetGame() {
    // Update counter back to one
    upd = 1;
    // score back to zero
    score = 0;
    // reset speed
    speed = 5;
    scoretext.text = score;
    // Clear the pillars
    pillars.children.each(pillar => pillar.destroy());
    pillars.clear();
    // Stop the Clouds
    clearTimeout(spawner);
    clouds.children.each(cloud => cloud.destroy());
    clouds.clear();
    // Reset player position
    ball.setPosition(100, 360);
    ball.setVelocity(0, 0);
    ball.setRotation(0);
    rotation = 0;
}