Phaser Tutorial: understanding Phaser states

During these days I am using Phaser for a series of projects, and one feature I found really interesting is state management.

How can we use Phaser states in game development? Basically, if you divide a game into “blocks”, such as splash screen, main menu, the game itself and so on, each of these “blocks” can be developed as a state.

Nothing you can’t do with just plain coding, but if you consider states management flushes the memory, releases resources, removes listeners and manages garbage collection, you will definitively want to use them in your games.

Before the tutorial starts, I want you to play this little game:

The concept is very simple: there’s a random number from 0 to 9 on the screen, and you have to guess if next number drawn will be higher or lower than the number currently displayed.

Each time you guess right you get one point, and the first time you fail, it’s game over and you will see the score. My best score is 12, for your information.

Now, let’s dissect the game:

As you can see there’s a preloading bar, then a menu screen, then the game itself and the game over screen which calls once again the game itself.

Each of these screens will be represented as a state, so our game will have four states + one special state you will meet in a matter of minutes.

Let’s see the content of index.html file:

<!doctype html>
<html>
	<head>
    		<script src="phaser.min.js"></script>
    		<script src="src/boot.js"></script>
		<script src="src/preload.js"></script>
		<script src="src/gametitle.js"></script>
		<script src="src/thegame.js"></script>
		<script src="src/gameover.js"></script>	
    		<style>
    			body{margin:0}
    		</style>
		<script>
			(function() {
				var game = new Phaser.Game(320, 480, Phaser.CANVAS, "game");
				game.state.add("Boot",boot);
				game.state.add("Preload",preload);
				game.state.add("GameTitle",gameTitle);
				game.state.add("TheGame",theGame);
				game.state.add("GameOver",gameOver);
				game.state.start("Boot");
			})();    
		</script>
    </head>
    <body>
    </body>
</html>

This is quite easy, first we load phaser library:

<script src="phaser.min.js"></script>

then all the files where states code is stored

<script src="src/boot.js"></script>
<script src="src/preload.js"></script>
<script src="src/gametitle.js"></script>
<script src="src/thegame.js"></script>
<script src="src/gameover.js"></script>

I saved each state in a separate file. This is not strictly needed, but it will keep your code more organized and reusable. Notice we have five files, just like the four states + one special state.

States declaration is made by game.state.add: the first argument is the name of the state, while the second is the name of the function to call inside such state.

game.state.add("Boot",boot);
game.state.add("Preload",preload);
game.state.add("GameTitle",gameTitle);
game.state.add("TheGame",theGame);
game.state.add("GameOver",gameOver);

Finally, we launch the initial state, Boot, with game.state.start

game.state.start("Boot");

Since Boot state is the first state we are calling, let’s have a look at boot.js:

var boot = function(game){
	console.log("%cStarting my awesome game", "color:white; background:red");
};
  
boot.prototype = {
	preload: function(){
          this.game.load.image("loading","assets/loading.png"); 
	},
  	create: function(){
		this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
		this.scale.pageAlignHorizontally = true;
		this.scale.setScreenSize();
		this.game.state.start("Preload");
	}
}

Boot is our special state: it just adjusts stage size and scale. If you notice, its behavior is not different than a Phaser game: it features preload and create functions. At this time, I am only preloading the loading bar image. Once everything is ready, we pass to Preload state, so here we go with preload.js:

var preload = function(game){}

preload.prototype = {
	preload: function(){ 
          var loadingBar = this.add.sprite(160,240,"loading");
          loadingBar.anchor.setTo(0.5,0.5);
          this.load.setPreloadSprite(loadingBar);
		this.game.load.spritesheet("numbers","assets/numbers.png",100,100);
		this.game.load.image("gametitle","assets/gametitle.png");
		this.game.load.image("play","assets/play.png");
		this.game.load.image("higher","assets/higher.png");
		this.game.load.image("lower","assets/lower.png");
		this.game.load.image("gameover","assets/gameover.png");
	},
  	create: function(){
		this.game.state.start("GameTitle");
	}
}

This function is the actual game preloader, and as you can see all graphic assets are preloaded now. The only interesting feature is that at preloading time I am also placing on the stage the loading bar previously loaded in Boot state:

var loadingBar = this.add.sprite(160,240,"loading");
loadingBar.anchor.setTo(0.5,0.5);
this.load.setPreloadSprite(loadingBar)

With load.setPreloadSprite method, Phaser will act like the sprite we passed as argument was an actual loading bar, setting its width longer as the preloading goes on.

Once all assets have been preloaded, it’s time to pass to GameTitle state, located in gametitle.js:

var gameTitle = function(game){}

gameTitle.prototype = {
  	create: function(){
		var gameTitle = this.game.add.sprite(160,160,"gametitle");
		gameTitle.anchor.setTo(0.5,0.5);
		var playButton = this.game.add.button(160,320,"play",this.playTheGame,this);
		playButton.anchor.setTo(0.5,0.5);
	},
	playTheGame: function(){
		this.game.state.start("TheGame");
	}
}

Now the game title has been placed, along with a “play” button that once pressed jumps to TheGame, located in thegame.js:

var theGame = function(game){
	spriteNumber = null;
	number = 0;
	workingButtons = true;
	higher = true;
	score = 0;
}

theGame.prototype = {
  	create: function(){
		number = Math.floor(Math.random()*10);
		spriteNumber = this.game.add.sprite(160,240,"numbers");
		spriteNumber.anchor.setTo(0.5,0.5);
		spriteNumber.frame = number;	
		var higherButton = this.game.add.button(160,100,"higher",this.clickedHigher,this);
		higherButton.anchor.setTo(0.5,0.5);
		var lowerButton = this.game.add.button(160,380,"lower",this.clickedLower,this);
		lowerButton.anchor.setTo(0.5,0.5);	
	},
	clickedHigher: function(){
		higher=true;
		this.tweenNumber(true);
	},
	clickedLower: function(){
		higher=false;
		this.tweenNumber(false);
	},
	tweenNumber: function(higher){
		if(workingButtons){
			workingButtons=false;
			var exitTween = this.game.add.tween(spriteNumber);
	          exitTween.to({x:420},500);
	          exitTween.onComplete.add(this.exitNumber,this);
	          exitTween.start();
	     }
	},
	exitNumber: function(){
		spriteNumber.x = -180;
	     spriteNumber.frame = Math.floor(Math.random()*10);
	     var enterTween = this.game.add.tween(spriteNumber);
	     enterTween.to({x:160},500);
	     enterTween.onComplete.add(this.enterNumber,this);
	     enterTween.start();
	
	},
	enterNumber: function(){
		workingButtons=true;
		if((higher && spriteNumber.frame<number)||(!higher && spriteNumber.frame>number)){
			this.game.state.start("GameOver",true,false,score);	
		}
		else{  
			score++;
			number = spriteNumber.frame;
		}	
	}
}

And now it should be clear how states work, explaining how the game itself works is beyond the scope of this post, but I want you to focus on the way I am calling GameOver state:

this.game.state.start("GameOver",true,false,score);

This is the first time I am calling the state with all its arguments, so let’s have a look at them:

GameOver is the name of the state to start

the second argument is called clearWorld, default at true, and clears the World display list fully (but not the Stage, so if you’ve added your own objects to the Stage they will need managing directly).

the third argument is called clearCache, default at false, and clears all loaded assets. You won’t use it that often as we want to keep loaded assets.

all other parameters from the fourth are variables that will be passed to the init function (if it has one). So I am going to pass the score to GameOver state, have a look at gameover.js:

var gameOver = function(game){}

gameOver.prototype = {
	init: function(score){
		alert("You scored: "+score)
	},
  	create: function(){
  		var gameOverTitle = this.game.add.sprite(160,160,"gameover");
		gameOverTitle.anchor.setTo(0.5,0.5);
		var playButton = this.game.add.button(160,320,"play",this.playTheGame,this);
		playButton.anchor.setTo(0.5,0.5);
	},
	playTheGame: function(){
		this.game.state.start("TheGame");
	}
}

Do you see init function?

init: function(score){
	alert("You scored: "+score)
}

That’s how I print out (actually I alert) the score.

And that’s all with the states, hope you will use them in your next project as they are really useful. And obviously you can download the source code of the entire project to study it.

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