In Part One of this tutorial we created the WorldScene, and in Part Two we made the BattleScene. If you haven’t read them I strongly recommend for you to do so before continuing. Now, in Part Three, we will combine both scenes into a working game.

Source code

You can download the files for this tutorial here .

Don't miss out! Offer ends in Access all 200+ courses

Access all 200+ courses New courses added monthly

New courses added monthly Cancel anytime

Cancel anytime Certificates of completion ACCESS NOW

Assets

All assets used in this tutorial are CC0 licensed. The tiles are created by Kenney Vleugels and you can download them at www.kenney.nl

Player characters – https://opengameart.org/content/rpg-character-sprites

Enemies – https://opengameart.org/content/dragon-1

Scene Switching

At the start, I will try to simplify and explain the scene switching logic. We will use the final version of the WorldScene from the first part of the tutorial and we will create very basic BattleScene and UIScene.

Get your WorldScene working and then edit the config object to add BattleScene and UIScene. Edit it to look like this:

var config = { type: Phaser.AUTO, parent: 'content', width: 320, height: 240, zoom: 2, pixelArt: true, physics: { default: 'arcade', arcade: { gravity: { y: 0 }, debug: false // set to true to view zones } }, scene: [ BootScene, WorldScene, BattleScene, UIScene ] }; var game = new Phaser.Game(config); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 var config = { type : Phaser . AUTO , parent : 'content' , width : 320 , height : 240 , zoom : 2 , pixelArt : true , physics : { default : 'arcade' , arcade : { gravity : { y : 0 } , debug : false // set to true to view zones } } , scene : [ BootScene , WorldScene , BattleScene , UIScene ] } ; var game = new Phaser . Game ( config ) ;

And here are our simplified BattleScene and UIScenes. We will use them to show how the scene switching will work:

var BattleScene = new Phaser.Class({ Extends: Phaser.Scene, initialize: function BattleScene () { Phaser.Scene.call(this, { key: 'BattleScene' }); }, create: function () { // set the background of the main scene green this.cameras.main.setBackgroundColor('rgba(0, 200, 0, 0.5)'); // Run UI Scene at the same time this.scene.run('UIScene'); } }); var UIScene = new Phaser.Class({ Extends: Phaser.Scene, initialize: function UIScene () { Phaser.Scene.call(this, { key: 'UIScene' }); }, create: function () { this.graphics = this.add.graphics(); this.graphics.lineStyle(1, 0xffffff); this.graphics.fillStyle(0x031f4c, 1); this.graphics.strokeRect(2, 150, 90, 100); this.graphics.fillRect(2, 150, 90, 100); this.graphics.strokeRect(95, 150, 90, 100); this.graphics.fillRect(95, 150, 90, 100); this.graphics.strokeRect(188, 150, 130, 100); this.graphics.fillRect(188, 150, 130, 100); } }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 var BattleScene = new Phaser . Class ( { Extends : Phaser . Scene , initialize : function BattleScene ( ) { Phaser . Scene . call ( this , { key : 'BattleScene' } ) ; } , create : function ( ) { // set the background of the main scene green this . cameras . main . setBackgroundColor ( 'rgba(0, 200, 0, 0.5)' ) ; // Run UI Scene at the same time this . scene . run ( 'UIScene' ) ; } } ) ; var UIScene = new Phaser . Class ( { Extends : Phaser . Scene , initialize : function UIScene ( ) { Phaser . Scene . call ( this , { key : 'UIScene' } ) ; } , create : function ( ) { this . graphics = this . add . graphics ( ) ; this . graphics . lineStyle ( 1 , 0xffffff ) ; this . graphics . fillStyle ( 0x031f4c , 1 ) ; this . graphics . strokeRect ( 2 , 150 , 90 , 100 ) ; this . graphics . fillRect ( 2 , 150 , 90 , 100 ) ; this . graphics . strokeRect ( 95 , 150 , 90 , 100 ) ; this . graphics . fillRect ( 95 , 150 , 90 , 100 ) ; this . graphics . strokeRect ( 188 , 150 , 130 , 100 ) ; this . graphics . fillRect ( 188 , 150 , 130 , 100 ) ; } } ) ;

Now we need to change the WorldScene to start the BattleScene. Change WorldScene onMeetEnemy like this:

onMeetEnemy: function(player, zone) { // we move the zone to some other location zone.x = Phaser.Math.RND.between(0, this.physics.world.bounds.width); zone.y = Phaser.Math.RND.between(0, this.physics.world.bounds.height); // shake the world this.cameras.main.shake(300); // switch to BattleScene this.scene.switch('BattleScene'); }, 1 2 3 4 5 6 7 8 9 10 11 onMeetEnemy : function ( player , zone ) { // we move the zone to some other location zone . x = Phaser . Math . RND . between ( 0 , this . physics . world . bounds . width ) ; zone . y = Phaser . Math . RND . between ( 0 , this . physics . world . bounds . height ) ; // shake the world this . cameras . main . shake ( 300 ) ; // switch to BattleScene this . scene . switch ( 'BattleScene' ) ; } ,

If you run the game now, you will see that, when the player hits an invisible enemy, it starts BattleScene. And BattleScene will control the UIScene. Lets see how to return back to the WorldScene. I will create a BattleScene method exitBattle:

exitBattle: function() { this.scene.sleep('UIScene'); this.scene.switch('WorldScene'); }, 1 2 3 4 exitBattle : function ( ) { this . scene . sleep ( 'UIScene' ) ; this . scene . switch ( 'WorldScene' ) ; } ,

This function will sleep the UIScene (make it not active and not visible) and will switch from BattleScene to WorldScene. For now I will add simple time event that will call this function after 2 seconds.

Add this to the end of BattleScene create method:

var timeEvent = this.time.addEvent({delay: 2000, callback: this.exitBattle, callbackScope: this}); 1 var timeEvent = this . time . addEvent ( { delay : 2000 , callback : this . exitBattle , callbackScope : this } ) ;

Now we have a problem. Everything works only the first time we switch to BattleScene. The second time, its create function is not called so we don’t have UIScene visible and we don’t exit BattleScene after the given time.

To fix this we need to listen to Scene ‘wake’ event. Add this code at the end of BattleScene create function:

this.sys.events.on('wake', this.wake, this); 1 this . sys . events . on ( 'wake' , this . wake , this ) ;

We need to add the wake function. It will run the UIScene and will add a timed event to exit the BattleScene:

wake: function() { this.scene.run('UIScene'); this.time.addEvent({delay: 2000, callback: this.exitBattle, callbackScope: this}); }, 1 2 3 4 wake : function ( ) { this . scene . run ( 'UIScene' ) ; this . time . addEvent ( { delay : 2000 , callback : this . exitBattle , callbackScope : this } ) ; } ,

Now our simple BattleScene should work just fine each time we enter a battle.

Make the game work

Its time to change the BattleScene code to the one from the second part of this tutorial. Remove only its BootScene as we don’t need two boot scenes to load our game. You may need to add the resources of the enemies to the current BootScene loader.

// enemies this.load.image("dragonblue", "assets/dragonblue.png"); this.load.image("dragonorrange", "assets/dragonorrange.png"); 1 2 3 // enemies this . load . image ( "dragonblue" , "assets/dragonblue.png" ) ; this . load . image ( "dragonorrange" , "assets/dragonorrange.png" ) ;

Its time to change the unit class like this:

var Unit = new Phaser.Class({ Extends: Phaser.GameObjects.Sprite, initialize: function Unit(scene, x, y, texture, frame, type, hp, damage) { Phaser.GameObjects.Sprite.call(this, scene, x, y, texture, frame) this.type = type; this.maxHp = this.hp = hp; this.damage = damage; // default damage this.living = true; this.menuItem = null; }, // we will use this to notify the menu item when the unit is dead setMenuItem: function(item) { this.menuItem = item; }, // attack the target unit attack: function(target) { if(target.living) { target.takeDamage(this.damage); this.scene.events.emit("Message", this.type + " attacks " + target.type + " for " + this.damage + " damage"); } }, takeDamage: function(damage) { this.hp -= damage; if(this.hp <= 0) { this.hp = 0; this.menuItem.unitKilled(); this.living = false; this.visible = false; this.menuItem = null; } } }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 var Unit = new Phaser . Class ( { Extends : Phaser . GameObjects . Sprite , initialize : function Unit ( scene , x , y , texture , frame , type , hp , damage ) { Phaser . GameObjects . Sprite . call ( this , scene , x , y , texture , frame ) this . type = type ; this . maxHp = this . hp = hp ; this . damage = damage ; // default damage this . living = true ; this . menuItem = null ; } , // we will use this to notify the menu item when the unit is dead setMenuItem : function ( item ) { this . menuItem = item ; } , // attack the target unit attack : function ( target ) { if ( target . living ) { target . takeDamage ( this . damage ) ; this . scene . events . emit ( "Message" , this . type + " attacks " + target . type + " for " + this . damage + " damage" ) ; } } , takeDamage : function ( damage ) { this . hp -= damage ; if ( this . hp < = 0 ) { this . hp = 0 ; this . menuItem . unitKilled ( ) ; this . living = false ; this . visible = false ; this . menuItem = null ; } } } ) ;

Here you will see the variable menuItem. We will link each unit to its menu item, and when the unit is dead, it will notify the menu item for this, so the player won’t be able to select a killed enemy.

Also we have new member variable – living. We will use it to check if the current unit is alive. Only living units will be able to participate in battle.

Now we need to edit the next unit attack circle to take in consideration this new property. Change BattleScene nextTurn method to this:

nextTurn: function() { do { this.index++; // if there are no more units, we start again from the first one if(this.index >= this.units.length) { this.index = 0; } } while(this.units[this.index].living); // if its player hero if(this.units[this.index] instanceof PlayerCharacter) { this.events.emit("PlayerSelect", this.index); } else { // else if its enemy unit // pick random hero var r = Math.floor(Math.random() * this.heroes.length); // call the enemy"s attack function this.units[this.index].attack(this.heroes[r]); // add timer for the next turn, so will have smooth gameplay this.time.addEvent({ delay: 3000, callback: this.nextTurn, callbackScope: this }); } }, 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 nextTurn : function ( ) { do { this . index ++ ; // if there are no more units, we start again from the first one if ( this . index > = this . units . length ) { this . index = 0 ; } } while ( this . units [ this . index ] . living ) ; // if its player hero if ( this . units [ this . index ] instanceof PlayerCharacter ) { this . events . emit ( "PlayerSelect" , this . index ) ; } else { // else if its enemy unit // pick random hero var r = Math . floor ( Math . random ( ) * this . heroes . length ) ; // call the enemy"s attack function this . units [ this . index ] . attack ( this . heroes [ r ] ) ; // add timer for the next turn, so will have smooth gameplay this . time . addEvent ( { delay : 3000 , callback : this . nextTurn , callbackScope : this } ) ; } } ,

Here we increment the index until we get a living unit. The units that are dead won’t be able to move any more. But now we have a problem. We will have endless cycle if there are no living units. To avoid this, we must check for game over or victory.

Add this code at the top of nextTurn:

if(this.checkEndBattle()) { this.endBattle(); return; } 1 2 3 4 if ( this . checkEndBattle ( ) ) { this . endBattle ( ) ; return ; }

And here is how checkEndBattle should look:

checkEndBattle: function() { var victory = true; // if all enemies are dead we have victory for(var i = 0; i < this.enemies.length; i++) { if(this.enemies[i].living) victory = false; } var gameOver = true; // if all heroes are dead we have game over for(var i = 0; i < this.heroes.length; i++) { if(this.heroes[i].living) gameOver = false; } return victory || gameOver; }, 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 checkEndBattle : function ( ) { var victory = true ; // if all enemies are dead we have victory for ( var i = 0 ; i < this . enemies . length ; i ++ ) { if ( this . enemies [ i ] . living ) victory = false ; } var gameOver = true ; // if all heroes are dead we have game over for ( var i = 0 ; i < this . heroes . length ; i ++ ) { if ( this . heroes [ i ] . living ) gameOver = false ; } return victory | | gameOver ; } ,

And here is endBattle:

endBattle: function() { // clear state, remove sprites this.heroes.length = 0; this.enemies.length = 0; for(var i = 0; i < this.units.length; i++) { // link item this.units[i].destroy(); } this.units.length = 0; // sleep the UI this.scene.sleep('UIScene'); // return to WorldScene and sleep current BattleScene this.scene.switch('WorldScene'); }, 1 2 3 4 5 6 7 8 9 10 11 12 13 14 endBattle : function ( ) { // clear state, remove sprites this . heroes . length = 0 ; this . enemies . length = 0 ; for ( var i = 0 ; i < this . units . length ; i ++ ) { // link item this . units [ i ] . destroy ( ) ; } this . units . length = 0 ; // sleep the UI this . scene . sleep ( 'UIScene' ) ; // return to WorldScene and sleep current BattleScene this . scene . switch ( 'WorldScene' ) ; } ,

The final version of nextTurn:

nextTurn: function() { // if we have victory or game over if(this.checkEndBattle()) { this.endBattle(); return; } do { // currently active unit this.index++; // if there are no more units, we start again from the first one if(this.index >= this.units.length) { this.index = 0; } } while(!this.units[this.index].living); // if its player hero if(this.units[this.index] instanceof PlayerCharacter) { // we need the player to select action and then enemy this.events.emit("PlayerSelect", this.index); } else { // else if its enemy unit // pick random living hero to be attacked var r; do { r = Math.floor(Math.random() * this.heroes.length); } while(!this.heroes[r].living) // call the enemy's attack function this.units[this.index].attack(this.heroes[r]); // add timer for the next turn, so will have smooth gameplay this.time.addEvent({ delay: 3000, callback: this.nextTurn, callbackScope: this }); } }, 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 nextTurn : function ( ) { // if we have victory or game over if ( this . checkEndBattle ( ) ) { this . endBattle ( ) ; return ; } do { // currently active unit this . index ++ ; // if there are no more units, we start again from the first one if ( this . index > = this . units . length ) { this . index = 0 ; } } while ( ! this . units [ this . index ] . living ) ; // if its player hero if ( this . units [ this . index ] instanceof PlayerCharacter ) { // we need the player to select action and then enemy this . events . emit ( "PlayerSelect" , this . index ) ; } else { // else if its enemy unit // pick random living hero to be attacked var r ; do { r = Math . floor ( Math . random ( ) * this . heroes . length ) ; } while ( ! this . heroes [ r ] . living ) // call the enemy's attack function this . units [ this . index ] . attack ( this . heroes [ r ] ) ; // add timer for the next turn, so will have smooth gameplay this . time . addEvent ( { delay : 3000 , callback : this . nextTurn , callbackScope : this } ) ; } } ,

As we have seen from the simple version of BattleScene we need to listen for the wake event. I’ve created new function startBattle and moved more of the starting logic there. Here is how my startBattle looks:

startBattle: function() { // player character - warrior var warrior = new PlayerCharacter(this, 250, 50, "player", 1, "Warrior", 100, 20); this.add.existing(warrior); // player character - mage var mage = new PlayerCharacter(this, 250, 100, "player", 4, "Mage", 80, 8); this.add.existing(mage); var dragonblue = new Enemy(this, 50, 50, "dragonblue", null, "Dragon", 50, 3); this.add.existing(dragonblue); var dragonOrange = new Enemy(this, 50, 100, "dragonorrange", null,"Dragon2", 50, 3); this.add.existing(dragonOrange); // array with heroes this.heroes = [ warrior, mage ]; // array with enemies this.enemies = [ dragonblue, dragonOrange ]; // array with both parties, who will attack this.units = this.heroes.concat(this.enemies); this.index = -1; // currently active unit this.scene.run("UIScene"); }, 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 startBattle : function ( ) { // player character - warrior var warrior = new PlayerCharacter ( this , 250 , 50 , "player" , 1 , "Warrior" , 100 , 20 ) ; this . add . existing ( warrior ) ; // player character - mage var mage = new PlayerCharacter ( this , 250 , 100 , "player" , 4 , "Mage" , 80 , 8 ) ; this . add . existing ( mage ) ; var dragonblue = new Enemy ( this , 50 , 50 , "dragonblue" , null , "Dragon" , 50 , 3 ) ; this . add . existing ( dragonblue ) ; var dragonOrange = new Enemy ( this , 50 , 100 , "dragonorrange" , null , "Dragon2" , 50 , 3 ) ; this . add . existing ( dragonOrange ) ; // array with heroes this . heroes = [ warrior , mage ] ; // array with enemies this . enemies = [ dragonblue , dragonOrange ] ; // array with both parties, who will attack this . units = this . heroes . concat ( this . enemies ) ; this . index = - 1 ; // currently active unit this . scene . run ( "UIScene" ) ; } ,

And here is how the BattleScene create function changes:

create: function () { // change the background to green this.cameras.main.setBackgroundColor("rgba(0, 200, 0, 0.5)"); this.startBattle(); // on wake event we call startBattle too this.sys.events.on('wake', this.startBattle, this); }, 1 2 3 4 5 6 7 create : function ( ) { // change the background to green this . cameras . main . setBackgroundColor ( "rgba(0, 200, 0, 0.5)" ) ; this . startBattle ( ) ; // on wake event we call startBattle too this . sys . events . on ( 'wake' , this . startBattle , this ) ; } ,

Now we will change the MenuItem class like this:

var MenuItem = new Phaser.Class({ Extends: Phaser.GameObjects.Text, initialize: function MenuItem(x, y, text, scene) { Phaser.GameObjects.Text.call(this, scene, x, y, text, { color: "#ffffff", align: "left", fontSize: 15}); }, select: function() { this.setColor("#f8ff38"); }, deselect: function() { this.setColor("#ffffff"); }, // when the associated enemy or player unit is killed unitKilled: function() { this.active = false; this.visible = false; } }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 var MenuItem = new Phaser . Class ( { Extends : Phaser . GameObjects . Text , initialize : function MenuItem ( x , y , text , scene ) { Phaser . GameObjects . Text . call ( this , scene , x , y , text , { color : "#ffffff" , align : "left" , fontSize : 15 } ) ; } , select : function ( ) { this . setColor ( "#f8ff38" ) ; } , deselect : function ( ) { this . setColor ( "#ffffff" ) ; } , // when the associated enemy or player unit is killed unitKilled : function ( ) { this . active = false ; this . visible = false ; } } ) ;

Here you can see the unitKilled method called by the units on dying. This will deactivate and hide the menu item. Now we must change the menu navigation, so when the selection moves up or down, it will skip the deactivated items.

Here is how to change Menu methods moveSelectionUp and moveSelectionDown:

moveSelectionUp: function() { this.menuItems[this.menuItemIndex].deselect(); do { this.menuItemIndex--; if(this.menuItemIndex < 0) this.menuItemIndex = this.menuItems.length - 1; } while(!this.menuItems[this.menuItemIndex].active); this.menuItems[this.menuItemIndex].select(); }, moveSelectionDown: function() { this.menuItems[this.menuItemIndex].deselect(); do { this.menuItemIndex++; if(this.menuItemIndex >= this.menuItems.length) this.menuItemIndex = 0; } while(!this.menuItems[this.menuItemIndex].active); this.menuItems[this.menuItemIndex].select(); }, 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 moveSelectionUp : function ( ) { this . menuItems [ this . menuItemIndex ] . deselect ( ) ; do { this . menuItemIndex -- ; if ( this . menuItemIndex < 0 ) this . menuItemIndex = this . menuItems . length - 1 ; } while ( ! this . menuItems [ this . menuItemIndex ] . active ) ; this . menuItems [ this . menuItemIndex ] . select ( ) ; } , moveSelectionDown : function ( ) { this . menuItems [ this . menuItemIndex ] . deselect ( ) ; do { this . menuItemIndex ++ ; if ( this . menuItemIndex > = this . menuItems . length ) this . menuItemIndex = 0 ; } while ( ! this . menuItems [ this . menuItemIndex ] . active ) ; this . menuItems [ this . menuItemIndex ] . select ( ) ; } ,

We need to change the select method too, so when the menu is activated, it will select an active item:

select: function(index) { if(!index) index = 0; this.menuItems[this.menuItemIndex].deselect(); this.menuItemIndex = index; while(!this.menuItems[this.menuItemIndex].active) { this.menuItemIndex++; if(this.menuItemIndex >= this.menuItems.length) this.menuItemIndex = 0; if(this.menuItemIndex == index) return; } this.menuItems[this.menuItemIndex].select(); this.selected = true; }, 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 select : function ( index ) { if ( ! index ) index = 0 ; this . menuItems [ this . menuItemIndex ] . deselect ( ) ; this . menuItemIndex = index ; while ( ! this . menuItems [ this . menuItemIndex ] . active ) { this . menuItemIndex ++ ; if ( this . menuItemIndex > = this . menuItems . length ) this . menuItemIndex = 0 ; if ( this . menuItemIndex == index ) return ; } this . menuItems [ this . menuItemIndex ] . select ( ) ; this . selected = true ; } ,

This is the moment to mention, that I decided to change the name of the event SelectEnemies to SelectedAction. Also, I changed the listeners to mach the name. You can continue with the old name or use the new one. The best way is to just use replace all and your ide will do everything automatically. As the event is fired when the player have selected an action, I think the code now is a bit more self explanatory.

We need to change the Menu remap method too:

remap: function(units) { this.clear(); for(var i = 0; i < units.length; i++) { var unit = units[i]; unit.setMenuItem(this.addMenuItem(unit.type)); } this.menuItemIndex = 0; }, 1 2 3 4 5 6 7 8 remap : function ( units ) { this . clear ( ) ; for ( var i = 0 ; i < units . length ; i ++ ) { var unit = units [ i ] ; unit . setMenuItem ( this . addMenuItem ( unit . type ) ) ; } this . menuItemIndex = 0 ; } ,

And change Menu addMenuItem to this:

addMenuItem: function(unit) { var menuItem = new MenuItem(0, this.menuItems.length * 20, unit, this.scene); this.menuItems.push(menuItem); this.add(menuItem); return menuItem; }, 1 2 3 4 5 6 addMenuItem : function ( unit ) { var menuItem = new MenuItem ( 0 , this . menuItems . length * 20 , unit , this . scene ) ; this . menuItems . push ( menuItem ) ; this . add ( menuItem ) ; return menuItem ; } ,

Now we are almost done with the changes. What is left, is to fix the UIScene and the WorldScene to listen for wake event. I will start with the UIScene. The code that is responsible for the global menu creation will stay at its create method, but the code responsible for the specific battle will go to new function. Change UIScene create method to this:

create: function () { // draw some background for the menu this.graphics = this.add.graphics(); this.graphics.lineStyle(1, 0xffffff); this.graphics.fillStyle(0x031f4c, 1); this.graphics.strokeRect(2, 150, 90, 100); this.graphics.fillRect(2, 150, 90, 100); this.graphics.strokeRect(95, 150, 90, 100); this.graphics.fillRect(95, 150, 90, 100); this.graphics.strokeRect(188, 150, 130, 100); this.graphics.fillRect(188, 150, 130, 100); // basic container to hold all menus this.menus = this.add.container(); this.heroesMenu = new HeroesMenu(195, 153, this); this.actionsMenu = new ActionsMenu(100, 153, this); this.enemiesMenu = new EnemiesMenu(8, 153, this); // the currently selected menu this.currentMenu = this.actionsMenu; // add menus to the container this.menus.add(this.heroesMenu); this.menus.add(this.actionsMenu); this.menus.add(this.enemiesMenu); this.battleScene = this.scene.get("BattleScene"); // listen for keyboard events this.input.keyboard.on("keydown", this.onKeyInput, this); // when its player cunit turn to move this.battleScene.events.on("PlayerSelect", this.onPlayerSelect, this); // when the action on the menu is selected // for now we have only one action so we dont send and action id this.events.on("SelectedAction", this.onSelectedAction, this); // an enemy is selected this.events.on("Enemy", this.onEnemy, this); // when the scene receives wake event this.sys.events.on('wake', this.createMenu, this); // the message describing the current action this.message = new Message(this, this.battleScene.events); this.add.existing(this.message); this.createMenu(); }, 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 create : function ( ) { // draw some background for the menu this . graphics = this . add . graphics ( ) ; this . graphics . lineStyle ( 1 , 0xffffff ) ; this . graphics . fillStyle ( 0x031f4c , 1 ) ; this . graphics . strokeRect ( 2 , 150 , 90 , 100 ) ; this . graphics . fillRect ( 2 , 150 , 90 , 100 ) ; this . graphics . strokeRect ( 95 , 150 , 90 , 100 ) ; this . graphics . fillRect ( 95 , 150 , 90 , 100 ) ; this . graphics . strokeRect ( 188 , 150 , 130 , 100 ) ; this . graphics . fillRect ( 188 , 150 , 130 , 100 ) ; // basic container to hold all menus this . menus = this . add . container ( ) ; this . heroesMenu = new HeroesMenu ( 195 , 153 , this ) ; this . actionsMenu = new ActionsMenu ( 100 , 153 , this ) ; this . enemiesMenu = new EnemiesMenu ( 8 , 153 , this ) ; // the currently selected menu this . currentMenu = this . actionsMenu ; // add menus to the container this . menus . add ( this . heroesMenu ) ; this . menus . add ( this . actionsMenu ) ; this . menus . add ( this . enemiesMenu ) ; this . battleScene = this . scene . get ( "BattleScene" ) ; // listen for keyboard events this . input . keyboard . on ( "keydown" , this . onKeyInput , this ) ; // when its player cunit turn to move this . battleScene . events . on ( "PlayerSelect" , this . onPlayerSelect , this ) ; // when the action on the menu is selected // for now we have only one action so we dont send and action id this . events . on ( "SelectedAction" , this . onSelectedAction , this ) ; // an enemy is selected this . events . on ( "Enemy" , this . onEnemy , this ) ; // when the scene receives wake event this . sys . events . on ( 'wake' , this . createMenu , this ) ; // the message describing the current action this . message = new Message ( this , this . battleScene . events ) ; this . add . existing ( this . message ) ; this . createMenu ( ) ; } ,

You see now that we call createMenu both when we start the UIScene for the first time and when its started through wake event. Here is our createMenu:

createMenu: function() { // map hero menu items to heroes this.remapHeroes(); // map enemies menu items to enemies this.remapEnemies(); // first move this.battleScene.nextTurn(); }, 1 2 3 4 5 6 7 8 createMenu : function ( ) { // map hero menu items to heroes this . remapHeroes ( ) ; // map enemies menu items to enemies this . remapEnemies ( ) ; // first move this . battleScene . nextTurn ( ) ; } ,

After running the game now you may found a problem. If its player turn, and he press multiple times space when selecting an enemy, nextTurn is called multiple times and you can witness strange behavior. To fix this we must be sure that we send only one event per menu. To do so, I’ve decide to add property selected to the Menu class. When Menu gets focus, this property becomes true. When the player chooses an action, it becomes false and the Menu won’t get its action method called again.

Here is how the Menu class should look at the end:

var Menu = new Phaser.Class({ Extends: Phaser.GameObjects.Container, initialize: function Menu(x, y, scene, heroes) { Phaser.GameObjects.Container.call(this, scene, x, y); this.menuItems = []; this.menuItemIndex = 0; this.x = x; this.y = y; this.selected = false; }, addMenuItem: function(unit) { var menuItem = new MenuItem(0, this.menuItems.length * 20, unit, this.scene); this.menuItems.push(menuItem); this.add(menuItem); return menuItem; }, // menu navigation moveSelectionUp: function() { this.menuItems[this.menuItemIndex].deselect(); do { this.menuItemIndex--; if(this.menuItemIndex < 0) this.menuItemIndex = this.menuItems.length - 1; } while(!this.menuItems[this.menuItemIndex].active); this.menuItems[this.menuItemIndex].select(); }, moveSelectionDown: function() { this.menuItems[this.menuItemIndex].deselect(); do { this.menuItemIndex++; if(this.menuItemIndex >= this.menuItems.length) this.menuItemIndex = 0; } while(!this.menuItems[this.menuItemIndex].active); this.menuItems[this.menuItemIndex].select(); }, // select the menu as a whole and highlight the choosen element select: function(index) { if(!index) index = 0; this.menuItems[this.menuItemIndex].deselect(); this.menuItemIndex = index; while(!this.menuItems[this.menuItemIndex].active) { this.menuItemIndex++; if(this.menuItemIndex >= this.menuItems.length) this.menuItemIndex = 0; if(this.menuItemIndex == index) return; } this.menuItems[this.menuItemIndex].select(); this.selected = true; }, // deselect this menu deselect: function() { this.menuItems[this.menuItemIndex].deselect(); this.menuItemIndex = 0; this.selected = false; }, confirm: function() { // when the player confirms his slection, do the action }, // clear menu and remove all menu items clear: function() { for(var i = 0; i < this.menuItems.length; i++) { this.menuItems[i].destroy(); } this.menuItems.length = 0; this.menuItemIndex = 0; }, // recreate the menu items remap: function(units) { this.clear(); for(var i = 0; i < units.length; i++) { var unit = units[i]; unit.setMenuItem(this.addMenuItem(unit.type)); } this.menuItemIndex = 0; } }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 var Menu = new Phaser . Class ( { Extends : Phaser . GameObjects . Container , initialize : function Menu ( x , y , scene , heroes ) { Phaser . GameObjects . Container . call ( this , scene , x , y ) ; this . menuItems = [ ] ; this . menuItemIndex = 0 ; this . x = x ; this . y = y ; this . selected = false ; } , addMenuItem : function ( unit ) { var menuItem = new MenuItem ( 0 , this . menuItems . length * 20 , unit , this . scene ) ; this . menuItems . push ( menuItem ) ; this . add ( menuItem ) ; return menuItem ; } , // menu navigation moveSelectionUp : function ( ) { this . menuItems [ this . menuItemIndex ] . deselect ( ) ; do { this . menuItemIndex -- ; if ( this . menuItemIndex < 0 ) this . menuItemIndex = this . menuItems . length - 1 ; } while ( ! this . menuItems [ this . menuItemIndex ] . active ) ; this . menuItems [ this . menuItemIndex ] . select ( ) ; } , moveSelectionDown : function ( ) { this . menuItems [ this . menuItemIndex ] . deselect ( ) ; do { this . menuItemIndex ++ ; if ( this . menuItemIndex > = this . menuItems . length ) this . menuItemIndex = 0 ; } while ( ! this . menuItems [ this . menuItemIndex ] . active ) ; this . menuItems [ this . menuItemIndex ] . select ( ) ; } , // select the menu as a whole and highlight the choosen element select : function ( index ) { if ( ! index ) index = 0 ; this . menuItems [ this . menuItemIndex ] . deselect ( ) ; this . menuItemIndex = index ; while ( ! this . menuItems [ this . menuItemIndex ] . active ) { this . menuItemIndex ++ ; if ( this . menuItemIndex > = this . menuItems . length ) this . menuItemIndex = 0 ; if ( this . menuItemIndex == index ) return ; } this . menuItems [ this . menuItemIndex ] . select ( ) ; this . selected = true ; } , // deselect this menu deselect : function ( ) { this . menuItems [ this . menuItemIndex ] . deselect ( ) ; this . menuItemIndex = 0 ; this . selected = false ; } , confirm : function ( ) { // when the player confirms his slection, do the action } , // clear menu and remove all menu items clear : function ( ) { for ( var i = 0 ; i < this . menuItems . length ; i ++ ) { this . menuItems [ i ] . destroy ( ) ; } this . menuItems . length = 0 ; this . menuItemIndex = 0 ; } , // recreate the menu items remap : function ( units ) { this . clear ( ) ; for ( var i = 0 ; i < units . length ; i ++ ) { var unit = units [ i ] ; unit . setMenuItem ( this . addMenuItem ( unit . type ) ) ; } this . menuItemIndex = 0 ; } } ) ;

And here how we will change the UIScene onKeyInput:

onKeyInput: function(event) { if(this.currentMenu && this.currentMenu.selected) { if(event.code === "ArrowUp") { this.currentMenu.moveSelectionUp(); } else if(event.code === "ArrowDown") { this.currentMenu.moveSelectionDown(); } else if(event.code === "ArrowRight" || event.code === "Shift") { } else if(event.code === "Space" || event.code === "ArrowLeft") { this.currentMenu.confirm(); } } }, 1 2 3 4 5 6 7 8 9 10 11 12 13 onKeyInput : function ( event ) { if ( this . currentMenu && this.currentMenu.selected) { if(event.code === "ArrowUp") { this.currentMenu.moveSelectionUp(); } else if ( event . code === "ArrowDown" ) { this . currentMenu . moveSelectionDown ( ) ; } else if ( event . code === "ArrowRight" | | event . code === "Shift" ) { } else if ( event . code === "Space" | | event . code === "ArrowLeft" ) { this . currentMenu . confirm ( ) ; } } } ,

Now when you run the game almost everything should run fine. But sometimes when you go back from BattleScene to WorldScene, the character is not waiting patiently but moving in some direction and you need to press and release the key for this direction to stop it. Its small bug, but we have to fix it. We need to wait for WorldScene wake event and then reset the keys.

Add this row to the end of WorldScene create function:

this.sys.events.on('wake', this.wake, this); 1 this . sys . events . on ( 'wake' , this . wake , this ) ;

And here is how the wake method should look:

wake: function() { this.cursors.left.reset(); this.cursors.right.reset(); this.cursors.up.reset(); this.cursors.down.reset(); }, 1 2 3 4 5 6 wake : function ( ) { this . cursors . left . reset ( ) ; this . cursors . right . reset ( ) ; this . cursors . up . reset ( ) ; this . cursors . down . reset ( ) ; } ,

With this we are finished with the third part of the tutorial. Although we have done a lot of work, our game need much more work to be fully functional. Get it as an exercise to save the state of the player characters and to make them get experience and levels. Also you can add more actions to the Actions menu and pass them together with the events.