Create the HTML5 engine behind iOS hit Memdot with Phaser – step 2: a game prototype

Read all posts about "" game
Here we go with the second step of Memdot game engine. Memdot by Appsolute Games, is a tiny free iOSgame which has been featured on the app store. In first step, we saw how to create the basic engine, with randomly colored dots entering the stage and the player being able to select them once the stage gets covered by a random color. Now, we complete the prototype allowing the player to progress through the game and also adding a timer. To make your life easier, running out of time does not mean “game over” but it should. Or you can just use the timer to reward players with a bonus score or stars. Anyway, this is the prototype of the game. You may need to reload the game and focus the canvas it if you already see the stage covered by a solid color.
There are 4 circles in game, with random colors. More circles can have the same color, but there will be at least two distinct colors on the stage. At a certain time, the stage is covered by a solid color, and circles become transparent. A timer starts. You have to select the circle (or all the circles) with the same color as background color. If you succeed, stage will be uncovered, remaining circles will move away from the stage while new circles come into play. Then the stage will be covered again, and so on. If you select a wrong circle, it’s game over and the game will restart in a few seconds. With this prototype we are very close to a complete working Memdot clone, have a look at the completely commented source code:
// the game
var game;

// size of each tile, in pixels
var gridSize = 40;

// colors to be used in game
var colorsInGame = [0xff0000, 0xff8800, 0x00ff00, 0x0000ff, 0xff00ff, 0x555555];

// how many circles in game?
var circlesInGame = 4;

// creation of the game
window.onload = function() {	
	game = new Phaser.Game(320, 480);
     game.state.add("PlayGame", playGame);
     game.state.start("PlayGame");
}


var playGame = function(game){}

playGame.prototype = {
     preload: function(){
          
          // preloading the assets
          game.load.spritesheet("circles", "circles.png", gridSize, gridSize);
          game.load.spritesheet("timer", "timer.png", 16, 16);
          game.load.image("background", "background.png");          
     },
     create: function(){
     
          // game won't pause if focus il lost
          game.stage.disableVisibilityChange = true;
     
          // checking if it's game over
          this.gameOver = false;
          
          // set background color to white                                                                                     
          game.stage.backgroundColor = "#ffffff";
          
          // adding a group containing all circles
          this.circleGroup = game.add.group();
          
          // placeCirlces method will handle circle placement and movement
          this.handleCircles();
          
          // filling the entire canvas with a tile sprite
          this.cover = game.add.tileSprite(0, 0, game.width, game.height, "background");
          
          // setting the cover to tansparent
          this.cover.alpha = 0;            
          
          // adding a group containing all timers
          this.timerGroup = game.add.group();
                    
          // adding 10 circle timers to timerGroup group
          for(var i = 0; i < 10; i++){
               var timeCircle = game.add.sprite(i * 20, game.height - 20, "timer");
               this.timerGroup.add(timeCircle);
          }
          
          // horizontal centering timerGroup
          this.timerGroup.x = (game.width - this.timerGroup.width) / 2;
     },
     
     // function to place the circles on the stage
     handleCircles: function(){
          
          // removing old circles, if any
          this.removeOldCircles();
          
          // adding new circles to the game
          this.addNewCircles();
          
          // after two seconds, let's cover the screen
          game.time.events.add(Phaser.Timer.SECOND * 2, this.fadeOut, this);
     },
     
     // function to remove circles from the stage
     removeOldCircles: function(){
          
          // handlong circles already in game, if any
          this.circleGroup.forEach(function(item){
          
               // set remaining circles back to their colors and frames                   
               item.tint = item.tintColor;
               item.frame = 0;
                        
               // choosimg a random direction: 1 = left, 2 = up, 3 = right, 4 = down
               var randomDirection = game.rnd.integerInRange(1, 4);
               
               // a temporary object which will be used to handle circle tween
               var tweenObject = {};
               
               // according to random direction...
               switch(randomDirection){
                    case 1:
                         
                         // left: circle will leave the stage to the left
                         tweenObject.x = - gridSize;
                         break;
                    case 2:
                         
                         // up: circle will leave the stage to the top
                         tweenObject.y = - gridSize;
                         break;
                    case 3:
                         
                         // right: circle will leave the stage to the right
                         tweenObject.x = game.width + gridSize;
                         break;
                    case 4:
                         
                         // down: circle will leave the stage to the bottom
                         tweenObject.y = game.height + gridSize;
                         break;
               }
                             
               // moving the circle to its new position
               var removeTween = game.add.tween(item).to(tweenObject, 500, Phaser.Easing.Cubic.In, true);
               
               removeTween.onComplete.add(function(item){
                    item.destroy();     
               }, this)    
               
          }, this);
     },
     
     // function to add new circles
     addNewCircles: function(){
     
          // possibleColors will contain the same items as colorsInGame array,
          // just repeated (circlesInGame - 1) times.
          // we want to have more circles with the same color, but not ALL circle with the same color
          this.possibleColors = [];
          this.possibleColors.lenght = 0;
          for(var i = 0; i < colorsInGame.length; i++){
               for(var j = 0; j < circlesInGame - 1; j++){
                    this.possibleColors.push(colorsInGame[i])     
               } 
          }
     
          // boardWidth and boardHeight will determine the width and height of the game board,
          // according to game size and grid size.
          // we subtract 2 from both boardWidth and boardHeight because we don't want
          // tiles to be at the very edge of the canvas
          var boardWidth = game.width / gridSize - 2;
          var boardHeight = game.height / gridSize - 2;
          
          // creation of an array with all possible grid positions
          this.positionsArray = [];
          this.positionsArray.length = 0;
          for(var i = 0; i < (boardWidth) * (boardHeight); i++){
               this.positionsArray.push(i);     
          }
          
          // pickedColors is the array which will contain all colors actually used in this game
          this.pickedColors = [];
          this.pickedColors.length = 0;
          
          // repeating this loop circlesInGame times
          for(var i = 0; i < circlesInGame; i++){
               
               // choosing a random position for the circle.
               // this position won't be available anymore as we remove it from positionsArray 
               var randomPosition = Phaser.ArrayUtils.removeRandomItem(this.positionsArray);              
               
               // determining circle x and y position in pixels
               var posX = (1 + randomPosition % (boardWidth)) * gridSize;
               var posY = (1 + Math.floor(randomPosition / boardWidth)) * gridSize;
               
               // creating the circle as a button which calls circleSelected function
               var circle = game.add.button(posX, posY, "circles", this.circleSelected, this);
               
               // adding the circle to circleGroup group 
               this.circleGroup.add(circle);
               
               // tinting the circle with a possible color and removing the color
               // from the array of possible colors.
               // we also save its tint color in a property called tintColor
               circle.tintColor = Phaser.ArrayUtils.removeRandomItem(this.possibleColors)
               circle.tint = circle.tintColor;
               
               // adding the tint color to pickedColors array, if not already in the array
               if(this.pickedColors.indexOf(circle.tint) == -1){
                    this.pickedColors.push(circle.tint);     
               }
               
               // choosimg a random direction: 1 = left, 2 = up, 3 = right, 4 = down
               var randomDirection = game.rnd.integerInRange(1, 4);
               
               // a temporary object which will be used to handle circle tween
               var tweenObject = {};
               
               // according to random direction...
               switch(randomDirection){
                    case 1:
                         
                         // left: circle is placed just outside left border and the tween
                         // will bring it to its initial x position
                         circle.x = - gridSize;
                         tweenObject.x = posX;
                         break;
                    case 2:
                         
                         // up: circle is placed just outside upper border and the tween
                         // will bring it to its initial y position
                         circle.y = - gridSize;
                         tweenObject.y = posY;
                         break;
                    case 3:
                         
                         // right: circle is placed just outside right border and the tween
                         // will bring it to its initial x position
                         circle.x = game.width + gridSize;
                         tweenObject.x = posX;
                         break;
                    case 4:
                         
                         // down: circle is placed just outside bottom border and the tween
                         // will bring it to its initial y position
                         circle.y = game.height + gridSize;
                         tweenObject.y = posY;
                         break;
               }
               
               // adding the tween to circle. This will create the "enter in the stage" effect
               game.add.tween(circle).to(tweenObject, 500, Phaser.Easing.Cubic.Out, true);     
          }
     },
     
     // this function will cover the screen with a random color
     fadeOut: function(){
          
          // this variable will count the ticks
          this.timePassed = 1;
          
          // setting all time circles to frame zero
          this.timerGroup.forEach(function(item){
               item.frame = 0;
          }, this)
          
          // giving the cover a tint color picked among circle colors 
          this.cover.tint = Phaser.ArrayUtils.getRandomItem(this.pickedColors);
          
          // tweening the cover to fully opaque
          var coverTween = game.add.tween(this.cover).to({
               alpha: 1
          }, 200, Phaser.Easing.Linear.None, true);   
          
          // once the cover is fully opaque...
          coverTween.onComplete.add(function(){
          
               // bring to top circleGroup as it was hidden by the cover
               game.world.bringToTop(this.circleGroup);
               
               // for each circle in circleGroup group...
               this.circleGroup.forEach(function(item){
               
                    // tinting it white
                    item.tint = 0xffffff;
                    
                    // setting it to frame 1 to show just a white ring
                    item.frame = 1;
               }, this);
               
               // startig the countdown
               this.countDown = game.time.events.repeat(Phaser.Timer.SECOND / 5, 11, this.tick, this); 
               
          }, this)       
     },
     
     // this function will be called each time a circle is touched
     // b is the circle
     circleSelected: function(b){
     
          // if the screen is already fully covered...
          if(!this.gameOver && this.cover.alpha == 1){
               
               // if the circle has the same tint color of the cover...
               // (we use tintColor property we previously saved, because circle tint color now is white)
               if(b.tintColor == this.cover.tint){
                    
                    // then destroy it
                    b.destroy();
                    
                    // checking if the level is completed, that is there aren't circles with
                    // cover color still on the stage
                    var levelCompleted = true;
                    this.circleGroup.forEach(function(item){
                         if(item.tintColor == this.cover.tint){
                              levelCompleted = false;
                         }
                    }, this);
                    
                    // if level is completed, advance to next level
                    if(levelCompleted){
                         
                         // stop the timer
                         game.time.events.remove(this.countDown);
                         
                         // turning the cover invisible
                         this.cover.alpha = 0;
                         
                         // placing new circles
                         this.handleCircles(); 
                    }                   
               }
               else{
                    
                    // if not, show the actual color of the circle
                    b.tint = b.tintColor
                    b.frame = 0;
                    
                    // then stop the timer
                    game.time.events.remove(this.countDown);
                    
                    // and it's game over
                    this.gameOver = true
                    
                    // wait 5 seconds then restart the game
                    game.time.events.add(Phaser.Timer.SECOND * 5, function(){
                         game.state.start("PlayGame");
                    }, this);
               }
          }
     },
     
     // function to be executed by countDown timer event
     tick: function(e){
     
          // if timePassed is less or equal to 10, that is if there still is time left...
          if(this.timePassed <= 10){
          
               // turn off a timer circle
               this.timerGroup.getChildAt(10 - this.timePassed).frame = 1;
               
               // increase time passed
               this.timePassed ++;
          }
          
          // else, it's game over
          else{               
               //this.gameOver = true;
          }          
     }
}
Apart from the original game being featured in the app store, I see potential in this prototype and will make an halloween game out of it, stay tuned for more news and 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

215 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
// Stairs
// 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