“Dashy Panda” HTML5 game prototype improved with coins to collect, powered by Phaser and Arcade physics

Read all posts about "" game

“Dashy Panda” tutorial series gets a new update with coins to collect.

Since we care to reuse objects and sprites as much as we can, there is only one coin which is placed some pixels away from the right edge of the screen, then moves towards the player – remember: in endless runners the player does not run, it’s the whole environment which moves towards the player – just to be placed once more some pixels away from the right edge of the screen if the player collects it or if the coin leaves the screen to the left side, because the player failed to collect it.

To add some difficulty to the game, coins are moved up and down with a tween, so you will need some timing skill to collect them if they are close to a spike.

Have a look at the game:

Tap/click and hold to move the panda, avoid spiked and collect coins.

If you touch a spike, it’s game over and you will restart the game.

The lack of background graphics does not help you understanding how fast the panda is moving, I am going to add environment graphics in next step.

Have a look at the completely commented source code:

let game;

let gameOptions = {

    // ground height, in pixels
    groundHeight: 40,

    // panda movement range, in pixels
    pandaMovement: [40, 160],

    // panda speed, in pixels per second
    pandaSpeed: 200,

    // pixels distance range between spikes
    spikeGap: [50, 140],

    // spike speed, in milliseconds to complete the tween
    spikeSpeed: [350, 700],

    // spike loop delay, in milliseconds
    spikeDelay: [300, 700],

    // height of the spike, in pixels
    spikeHeight: [20, 55],

    // distance range from the right edge of the game and the coin, in pixels
    coinRange: [50, 150]
}

window.onload = function() {
    let gameConfig = {
        type: Phaser.AUTO,
        backgroundColor:0x6cc9bf,
        scale: {
            mode: Phaser.Scale.FIT,
            autoCenter: Phaser.Scale.CENTER_BOTH,
            parent: "thegame",
            width: 480,
            height: 320
        },
        physics: {
            default: "arcade",
            arcade: {
                // debug: true
            }
        },
        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("panda", "panda.png");
        this.load.image("spike", "spike.png");
        this.load.image("coin", "coin.png");
    }
    create(){

        // physics group which contains all spikes
        this.spikeGroup = this.physics.add.group();

        // first spike position is in the horizontal center of the canvas
        let spikeX = game.config.width / 2;

        // we place 10 spikes. 10 are more than enough to give the feeling of an endless runner
        for(let i = 0; i < 10; i++){

            // spike creation
            let spike = this.spikeGroup.create(spikeX, game.config.height - gameOptions.groundHeight, "spike");

            // spike registration point is center / bottom
            spike.setOrigin(0.5, 0);

            // immovable - won't move if something collides with it
            spike.setImmovable(true);

            // set the physics body size smaller than the spike itself, so the game will sometimes forgive the player
            spike.setSize(12, 40, true);

            // method to create a tween and attach it to the spike
            this.createTween(spike);

            // determine next spike position
            spikeX += Phaser.Math.Between(gameOptions.spikeGap[0], gameOptions.spikeGap[1]);
        }

        // add the panda to the game
        this.panda = this.physics.add.sprite(gameOptions.pandaMovement[0], game.config.height - gameOptions.groundHeight, "panda");
        this.panda.setOrigin(0.5, 1);
        this.panda.setSize(24, 30, true);

        // the panda can move only until it reaches pandaXLimit. Then the
        // whole game will move towards the panda
        this.panda.canMove = true;

        // isMoving tells if the panda or the whole game are moving. In other
        // words if the player wants to move the panda, no matter what is
        // actually being moved
        this.panda.isMoving = false;

        // add the coin to the game
        this.coin = this.physics.add.sprite(game.config.width + Phaser.Math.Between(gameOptions.coinRange[0], gameOptions.coinRange[1]), this.panda.y - this.panda.getBounds().height / 2, "coin");
        this.coin.setCircle(10);

        // tween to move the coin up and down
        this.tweens.add({
            targets: this.coin,
            y: game.config.height / 2,
            duration: 1000,
            repeat: -1,
            yoyo: true,
            ease: "Cubic.easeOut"
        })

        // add the ground to the game
        let ground = this.add.sprite(0, game.config.height - gameOptions.groundHeight,  "ground");
        ground.setOrigin(0, 0);

        // move the panda when an input is pressed
        this.input.on("pointerdown", this.movePanda, this);

        // stop the panda when an input is released
        this.input.on("pointerup", this.stopPanda, this);
    }

    createTween(object){
        this.tweens.add({

            // object affected by the tween
            targets: object,

            // y position to tween
            y: game.config.height - gameOptions.groundHeight - Phaser.Math.Between(gameOptions.spikeHeight[0], gameOptions.spikeHeight[1]),

            // tween duration
            duration: Phaser.Math.Between(gameOptions.spikeSpeed[0], gameOptions.spikeSpeed[1]),

            // play the tween forever
            repeat: -1,

            // playing the tween back and forth
            yoyo: true,

            // tween easing
            ease: "Quint.easeIn"
        })
    }

    movePanda(){

        // the idea is: if the panda can move, then move the panda, else
        // move all spikes and the coin towards the panda. Then set isMoving to true
        if(this.panda.canMove){
            this.panda.body.velocity.x = gameOptions.pandaSpeed;
        }
        else{
            this.spikeGroup.setVelocityX(-gameOptions.pandaSpeed);
            this.coin.setVelocityX(-gameOptions.pandaSpeed);
        }
        this.panda.isMoving = true;
    }

    stopPanda(){

        // the idea is: stop the panda and all spikes as well as the coin, set isMoving to false
        this.panda.setVelocityX(0);
        this.spikeGroup.setVelocityX(0);
        this.coin.setVelocityX(0);
        this.panda.isMoving = false;
    }

    getRightmostSpike(){

        // getting rightmost spike
        let rightmostSpike = 0;
        this.spikeGroup.getChildren().forEach(function(spike){
            rightmostSpike = Math.max(rightmostSpike, spike.x);
        });
        return rightmostSpike;
    }

    update(){

        // this is how I make the panda stop when it reaches its maximum
        // horizontal position and start moving the environment instead
        if(this.panda.canMove &amp;&amp; this.panda.x > gameOptions.pandaMovement[1]){
            this.panda.canMove = false;
            this.panda.body.velocity.x = 0;
            this.movePanda();
        }

        // recycle the coin if it leaves the screen to the left
        if(this.coin.getBounds().right < 0){
            this.coin.x = game.config.width + Phaser.Math.Between(gameOptions.coinRange[0], gameOptions.coinRange[1]);
        }

        // recycle spikes when they leave the screen
        this.spikeGroup.getChildren().forEach(function(spike){
            if(spike.getBounds().right < 0){
                spike.x = this.getRightmostSpike() + Phaser.Math.Between(gameOptions.spikeGap[0], gameOptions.spikeGap[1]);
                spike.y = game.config.height - gameOptions.groundHeight;
                this.tweens.killTweensOf(spike);
                this.createTween(spike);
            }
        }, this);

        // restart the game if the panda hits the spikes
        this.physics.world.collide(this.panda, this.spikeGroup, function(){
            this.scene.start("PlayGame");
        }, null, this)

        // recycle the coin if the panda hits it
        this.physics.world.overlap(this.panda, this.coin, function(){
            this.coin.x = game.config.width + Phaser.Math.Between(gameOptions.coinRange[0], gameOptions.coinRange[1]);
        }, null, this)
    }
}

Now the panda must survive and collect coins, how about keeping track of the best distance travelled? Stay tuned, 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

214 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
// 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