“Mike Dangers” HTML5 game engine made with Phaser and ARCADE physics – adding diamonds and real object pooling

Read all posts about "" game
Did you enjoy the first step of Mike Dangers series? There still a lot to do, so I am adding another important feature: diamonds. Mike loves to collect diamonds as they are the in-game currency you can use to unlock new characters. So here we go with some diamonds to collect:
Just tap or click to make the player jump. Try to climb the ladders and collect diamonds. If you have a mobile device, you can play directly at this link. But the most interesting thing happens when you open the console: Everything related to diamonds and object pooling is logged in real time in the console. This is basically how it works: * Each floor has a certain chance to have a diamond, that is not all floors will have a diamond. * That said, at the beginning of the game we do not have a diamond instance for each floor istance, and that’s fine * Diamonds placed in the game are stored in an array * Each time the player collects a diamond or leaves a diamond on a disappearing floor, we remove the diamond from the in-game diamond array and place it into a diamond pool array * When we need a new diamond, we first look into the diamond pool array. If we find it, we simply revive it, removing it from the pool array and placing it once again in the in-game array. * If we can’t find a diamond in the diamond pool array, in this only case we generate a new diamond instance. It may seem too complicated to handle diamonds in this way, as all in all we only have a few of them, but once you’ll add diamonds, spikes, boulders, idols, arrows and so on you will understand how critical is to create as few sprites as we can. Also, we never destroy and recreate diamonds, we just kill and revive them, as you can see from the script:
var game;
var gameOptions = {
    gameWidth: 800,
    gameHeight: 1300,
    floorStart: 1 / 8 * 5,
    floorGap: 250,
    playerGravity: 10000,
    playerSpeed: 450,
    climbSpeed: 450,
    playerJump: 1800,
    diamondRatio: 2
}
window.onload = function() {
    game = new Phaser.Game(gameOptions.gameWidth, gameOptions.gameHeight);
    game.state.add("PreloadGame", preloadGame);
    game.state.add("PlayGame", playGame);
    game.state.start("PreloadGame");
}
var preloadGame = function(game){}
preloadGame.prototype = {
    preload: function(){
        game.stage.backgroundColor = 0xaaeaff;
        game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
        game.scale.pageAlignHorizontally = true;
        game.scale.pageAlignVertically = true;
        game.stage.disableVisibilityChange = true;
        game.load.image("ground", "ground.png");
        game.load.image("hero", "hero.png");
        game.load.image("ladder", "ladder.png");
        game.load.image("diamond", "diamond.png");
    },
    create: function(){
        game.state.start("PlayGame");
    }
}
var playGame = function(game){}
playGame.prototype = {
    create: function(){
        game.physics.startSystem(Phaser.Physics.ARCADE);
        this.canJump = true;
        this.isClimbing = false;
        this.defineGroups();
        this.drawLevel();
        this.defineTweens();
        game.input.onTap.add(this.handleTap, this);
    },
    drawLevel: function(){
        this.currentFloor = 0;
        this.currentLadder = 0;
        this.highestFloorY = game.height * gameOptions.floorStart;
        this.floorArray = [];
        this.ladderArray = [];
        this.diamondArray = [null];
        this.diamondPool= [];
        while(this.highestFloorY > - 3 * gameOptions.floorGap){
                this.addFloor();
                if(this.currentFloor > 0){
                    this.addLadder();
                    this.addDiamond();
                }
                this.highestFloorY -= gameOptions.floorGap;
                this.currentFloor ++;
        }
        console.log("These are the diamonds at the start of the game:");
        console.log(this.diamondArray);
        console.log("------------------------------------------------");
        this.currentFloor = 0;
        this.addHero();
    },
    addDiamond: function(){
        if(game.rnd.integerInRange(0, gameOptions.diamondRatio) != 0){
            console.log("Start with diamond at floor " + this.currentFloor);
            var diamond = game.add.sprite(game.rnd.integerInRange(150, game.width - 150), this.highestFloorY - gameOptions.floorGap / 2, "diamond");
            diamond.anchor.set(0.5, 0);
            game.physics.enable(diamond, Phaser.Physics.ARCADE);
            diamond.body.immovable = true;
            this.diamondGroup.add(diamond);
            this.diamondArray[this.currentFloor] = diamond;
        }
        else{
            this.diamondArray[this.currentFloor] = null;
        }
    },
    reviveDiamond: function(){
        if(game.rnd.integerInRange(0, gameOptions.diamondRatio) != 0){
            console.log("let's create a new diamond at floor " + this.currentFloor);
            if(this.diamondPool.length > 0){
                console.log("I have (at least) a diamond inside the diamond pool")
                var diamond = this.diamondPool.pop();
                diamond.y = this.highestFloorY - gameOptions.floorGap / 2;
                diamond.revive();
                this.diamondArray[this.currentFloor] = diamond;
            }
            else{
                console.log("I don't have diamonds in the pool, let's crate a new one");
                var diamond = game.add.sprite(game.rnd.integerInRange(150, game.width - 150), this.highestFloorY - gameOptions.floorGap / 2, "diamond");
                diamond.anchor.set(0.5, 0);
                game.physics.enable(diamond, Phaser.Physics.ARCADE);
                diamond.body.immovable = true;
                this.diamondGroup.add(diamond);
                this.diamondArray[this.currentFloor] = diamond;
            }
            console.log("These are the diamonds in game:");
            console.log(this.diamondArray);
            console.log("These are the diamonds in the pool:");
            console.log(this.diamondPool);
        }
        else{
            console.log("I won't create a new diamond at floor " + this.currentFloor);
        }
        console.log("------------------------------------------------");
    },
    addFloor: function(){
        var floor = game.add.sprite(0, this.highestFloorY, "ground");
        this.floorGroup.add(floor);
        game.physics.enable(floor, Phaser.Physics.ARCADE);
        floor.body.immovable = true;
        floor.body.checkCollision.down = false;
        this.floorArray.push(floor);
    },
    addLadder: function(){
        var ladder = game.add.sprite(100 + (game.width - 200) * (this.currentFloor % 2), this.highestFloorY, "ladder");
        this.ladderGroup.add(ladder);
        ladder.anchor.set(0.5, 0);
        game.physics.enable(ladder, Phaser.Physics.ARCADE);
        ladder.body.immovable = true;
        this.ladderArray.push(ladder);
    },
    addHero: function(){
        this.hero = game.add.sprite(game.width / 2, game.height * gameOptions.floorStart - 40, "hero");
        this.gameGroup.add(this.hero)
        this.hero.anchor.set(0.5, 0);
        game.physics.enable(this.hero, Phaser.Physics.ARCADE);
        this.hero.body.collideWorldBounds = true;
        this.hero.body.gravity.y = gameOptions.playerGravity;
        this.hero.body.velocity.x = gameOptions.playerSpeed;
        this.hero.body.onWorldBounds = new Phaser.Signal();
        this.hero.body.onWorldBounds.add(function(sprite, up, down, left, right){
            if(left){
                this.hero.body.velocity.x = gameOptions.playerSpeed;
                this.hero.scale.x = 1;
            }
            if(right){
                this.hero.body.velocity.x = -gameOptions.playerSpeed;
                this.hero.scale.x = -1;
            }
        }, this)
    },
    defineTweens: function(){
        this.scrollTween = game.add.tween(this.gameGroup).to({
            y: gameOptions.floorGap
        }, 800, Phaser.Easing.Cubic.Out);
        this.scrollTween.onComplete.add(function(){
                this.gameGroup.y = 0;
                this.floorGroup.forEach(function(item) {
                    item.y += gameOptions.floorGap;
                }, this);
                this.ladderGroup.forEach(function(item) {
                    item.y += gameOptions.floorGap;
                }, this);
                this.diamondGroup.forEach(function(item) {
                    item.y += gameOptions.floorGap;
                }, this);
                this.hero.y += gameOptions.floorGap;
        }, this)
        this.fadeTween = game.add.tween(this.floorArray[0]).to({
            alpha: 0
        }, 200, Phaser.Easing.Cubic.Out);
        this.fadeTween.onComplete.add(function(floor){
                floor.y = this.highestFloorY;
                floor.alpha = 1;
        }, this);
        this.fallTween = game.add.tween(this.ladderArray[0]).to({
            y: game.height
        }, 200, Phaser.Easing.Cubic.Out);
        this.fallTween.onComplete.add(function(ladder){
            ladder.y = this.highestFloorY
        }, this);
    },
    defineGroups: function(){
        this.gameGroup = game.add.group();
        this.floorGroup = game.add.group();
        this.ladderGroup = game.add.group();
        this.diamondGroup = game.add.group();
        this.gameGroup.add(this.floorGroup);
        this.gameGroup.add(this.ladderGroup);
        this.gameGroup.add(this.diamondGroup);
    },
    handleTap: function(pointer, doubleTap){
        if(this.canJump && !this.isClimbing){
            this.hero.body.velocity.y = -gameOptions.playerJump;
            this.canJump = false;
        }
    },
    update: function(){
        this.checkFloorCollision();
        this.checkLadderCollision();
        this.checkDiamondCollision();
        this.heroOnLadder();
    },
    checkFloorCollision: function(){
        game.physics.arcade.collide(this.hero, this.floorArray, function(){
            this.canJump = true;
        }, null, this);
    },
    checkLadderCollision: function(){
        game.physics.arcade.overlap(this.hero, this.ladderArray, function(player, ladder){
            if(!this.isClimbing &amp;&amp; Math.abs(player.x - ladder.x) < 10){
                this.hero.body.velocity.x = 0;
                this.hero.body.velocity.y = - gameOptions.climbSpeed;
                this.hero.body.gravity.y = 0;
                this.isClimbing = true;
                this.fadeTween.target =  this.floorArray[this.currentFloor];
                this.fadeTween.start();
                if(this.diamondArray[this.currentFloor] != null){
                    this.killDiamond();
                }
                this.reviveDiamond();
                this.currentFloor = (this.currentFloor + 1) % this.floorArray.length;
                this.scrollTween.start();
            }
        }, null, this);
    },
    checkDiamondCollision: function(){
            game.physics.arcade.overlap(this.hero, this.diamondArray, function(player, diamond){
                this.killDiamond();
            }, null, this);
    },
    killDiamond: function(){
        this.diamondArray[this.currentFloor].kill();
        this.diamondPool.push(this.diamondArray[this.currentFloor]);
        this.diamondArray[this.currentFloor] = null;
        console.log("Removing a diamond from the game and putting it into the pool");
        console.log("These are the diamonds in game:");
        console.log(this.diamondArray);
        console.log("These are the diamonds in the pool:");
        console.log(this.diamondPool);
    },
    heroOnLadder: function(){
        if(this.isClimbing &amp;&amp; this.hero.y <= this.floorArray[this.currentFloor].y - 40){
            this.hero.body.gravity.y = gameOptions.playerGravity;
            this.hero.body.velocity.x = gameOptions.playerSpeed * this.hero.scale.x;
            this.hero.body.velocity.y = 0;
            this.isClimbing = false;
            this.fallTween.target =  this.ladderArray[this.currentLadder];
            this.fallTween.start();
            this.currentLadder = (this.currentLadder + 1) % this.ladderArray.length;
        }
    }
}
In Phaser, when you kill a sprite, you sets its alive, exists and visible properties to false. When you revive it you bring it back to life with its alive, exists and visible properties all set to true. Next time we will add deadly spikes, 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