Talking about Wordle game, Game development, HTML5, Javascript, Phaser and TypeScript.
I hope you are enjoying the series about Wordle game.
In first step I showed you how to handle keyboard input and result management.
In second step we saw how to add a virtual keyboard.
In third step I added the game board and improved virtual keyboard layout.
In fourth step I added Bootstrap DOM elements to create an in-game menu to select game mode and display statistics.
Now, we are going to try another way to display Bootstrap – or any other UI framework – elements in a Phaser game.
What if we build a page with a Bootstrap toolbar and the game, with the game having listeners on Bootstrap menu items?
Look at the example:
First, let me explain what you are seeing:
The top black toolbar with the red dropdown menu are outside Phaser game, which starts right below the toolbar and fills the 100% of the remaining vertical space.
Use the keyboard to write the word. You can play with your physical keyboard, using Backspace to delete the last letter, Enter to submit the word or Space to restart the game with a new randomly picked word.
Also, the big menu allows you to play with the word of the day, with a random word or to visit my site. Stats option opens a modal which is outside Phaser game.
If you are reading this post with a mobile device, you can see it in action directly from this link.
Doing this way rather than embedding the DOM directly into Phaser game allows you to keep things separated, and there isn’t a best way in my opinion, but it’s good to see both ways.
Let’s have a look at the assets used in the prototype:
bigfont.fnt and bigfont.png: respectively the XML information and the image of the big font.
bigkey.png: a sprite sheet containing Enter and Delete keys.
box.png: sprite sheet the box where to write the each letter of the word.
font.fnt and font.png: respectively the XML information and the image of the small font.
key.png: sprite sheeet of the virtual keyboard key.
words.json: the list of allowed words.
And now, the completely commented source code, which is made up by one html file, one css file and 11 TypeScript files.
index.html
The web page which hosts the game, to be run inside thegame element, with also all Bootstrap UI including the toolbar, the dropdown menu and the modal box.
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 | <! DOCTYPE html> < html > < head > < meta name = "viewport" content = "initial-scale=1, maximum-scale=1" > < script src = "https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity = "sha256-4+XzXVhsDmqanXGHaHvgh1gMQKX40OUvDEBTu8JcmNs=" crossorigin = "anonymous" ></ script > < link href = "https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel = "stylesheet" integrity = "sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin = "anonymous" > < script src = "https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity = "sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin = "anonymous" ></ script > < link rel = 'stylesheet' href = 'style.css' > < script src = 'main.js' ></ script > </ head > < body > < div class = "wrapper" > < div > < nav class = "navbar navbar-expand navbar-dark bg-dark" > < div class = "container-fluid" id = "contentcontainer" > < ul class = "navbar-nav me-auto mb-2 mb-lg-0" > < li class = "nav-item dropdown" > < button type = "button" class = "btn btn-danger dropdown-toggle rounded-0" data-bs-toggle = "dropdown" > Menu </ button > < ul class = "dropdown-menu" > < li >< a href = "javascript:void(0);" class = "dropdown-item" id = "day" >Word of the Day</ a ></ li > < li >< a href = "javascript:void(0);" class = "dropdown-item" id = "rnd" >Random Word</ a ></ li > < li >< a href = "javascript:void(0);" class = "dropdown-item" id = "stats" data-bs-toggle = "modal" data-bs-target = "#statsmodal" >Stats</ a ></ li > < li >< hr class = "dropdown-divider" ></ li > < li >< a class = "dropdown-item" href = "https://emanueleferonato.com/" >Developed by Emanuele Feronato</ a ></ li > </ ul > </ li > </ ul > < span class = "navbar-text" > Bootstrap toolbar </ span > </ div > </ nav > </ div > < div id = "thegame" ></ div > </ div > < div class = "modal fade" id = "statsmodal" data-bs-backdrop = "static" data-bs-keyboard = "false" tabindex = "-1" > < div class = "modal-dialog" > < div class = "modal-content" > < div class = "modal-header" > < h5 class = "modal-title" id = "staticBackdropLabel" >Game stats</ h5 > < button type = "button" class = "btn-close" data-bs-dismiss = "modal" aria-label = "Close" ></ button > </ div > < div class = "modal-body" id = "modalbody" > </ div > < div class = "modal-footer" > < button type = "button" class = "btn btn-secondary" data-bs-dismiss = "modal" >Close</ button > </ div > </ div > </ div > </ div > </ body > </ html > |
style.css
The style sheet of the main web page, look how we use a flex display with column direction to make Phaser game cover the remaining area.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | * { padding : 0 ; margin : 0 ; } canvas { touch-action : none ; -ms-touch-action : none ; } .wrapper, html, body { height : 100% ; margin : 0 ; } .wrapper { display : flex ; flex-direction : column; } .navbar-dark .navbar-text { color : rgba ( 255 , 255 , 255 , 1 ); } |
gameOptions.ts
Configurable game options.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // CONFIGURABLE GAME OPTIONS // possible game modes // random word: a word randomly picked from words array // word of the day: a word picked according to current date export enum gameMode { RANDOM_WORD, WORD_OF_THE_DAY } export const GameOptions = { // number of rows, that is the amount of tries to guess the word rows : 6, // vertical position of the first row, starting from the top of the screen firstRowY : 150 } |
main.ts
This is where the game is created, with all Phaser related options.
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 | // MAIN GAME FILE // modules to import import Phaser from 'phaser' ; import { PreloadAssets } from './preloadAssets' ; import { PlayGame } from './playGame' ; import { Container } from './container' ; import { MenuHandler } from './menuHandler' ; // object to initialize the Scale Manager const scaleObject : Phaser.Types.Core.ScaleConfig = { mode : Phaser.Scale.FIT, autoCenter : Phaser.Scale.CENTER_BOTH, parent : 'thegame' , width : 700, height : 1244 } // game configuration object const configObject : Phaser.Types.Core.GameConfig = { type : Phaser.AUTO, backgroundColor : 0xffffff, scale : scaleObject, scene : [PreloadAssets, Container, MenuHandler, PlayGame] } // the game itself new Phaser.Game(configObject); |
preloadAssets.ts
Here we preload all assets to be used in the game, such as the JSON object with all words, the images and the bitmap fonts.
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 | // CLASS TO PRELOAD ASSETS // this class extends Scene class export class PreloadAssets extends Phaser.Scene { // constructor constructor() { super ({ key : 'PreloadAssets' }); } // method to be execute during class preloading preload(): void { // this is how we preload a JSON object this .load.json( 'words' , 'assets/words.json' ); // preload images this .load.spritesheet( 'key' , 'assets/key.png' , { frameWidth: 70, frameHeight: 90 }); this .load.spritesheet( 'bigkey' , 'assets/bigkey.png' , { frameWidth: 105, frameHeight: 90 }); this .load.spritesheet( 'box' , 'assets/box.png' , { frameWidth: 100, frameHeight: 100 }); // this is how we preload a bitmap font this .load.bitmapFont( 'font' , 'assets/font.png' , 'assets/font.fnt' ); this .load.bitmapFont( 'bigfont' , 'assets/bigfont.png' , 'assets/bigfont.fnt' ); } // method to be called once the instance has been created create(): void { // call PlayGame class this .scene.start( 'Container' ); } } |
container.ts
This scene is the main scene and acts as a container for two subscenes: one with the game itself and one with the code to handle external Bootstrap events.
It’s a good idea to keep these two scripts separated, just in case you decide to remove the Bootstrap part and change with some other framework.
Another example about combining more scenes can be found reading the post Understanding Phaser capability of running multiple Scenes simoultaneously with a real world example: continous particle scrolling background while main Scene restarts.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // CLASS TO CONTAIN THE GAME // this class extends Scene class export class Container extends Phaser.Scene { // constructor constructor() { super ({ key : 'Container' }); } // method to be called once the scene has been created create(): void { // this is how we launch a scene this .scene.launch( 'PlayGame' ); this .scene.launch( 'MenuHandler' ); } } |
menuHandler.ts
All the code to handle Bootstrap events is placed here, as well as a snippet to horizontally resize the navbar to match the width of the Phaser game.
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 | // CLASS TO HANDLE EXTERNAL MENU import { gameMode } from './gameOptions' ; // this class extends Scene class export class MenuHandler extends Phaser.Scene { // constructor constructor() { super ({ key : 'MenuHandler' }); } // method to be called once the scene has been created create(): void { // resize upper Boostrap menu this .resizeMenu(); // this is how we manage clicks on "Word of the Day" button let wordOfTheDayButton : HTMLElement = document.getElementById( 'day' ) as HTMLElement; wordOfTheDayButton.addEventListener( 'click' , this .startWordOfTheDay.bind( this )); // this is how we manage clicks on "Random Word" button let randomWordButton : HTMLElement = document.getElementById( 'rnd' ) as HTMLElement; randomWordButton.addEventListener( 'click' , this .startRandom.bind( this )); // this is how we manage clicks on "Stats" button let statsButton : HTMLElement = document.getElementById( 'stats' ) as HTMLElement; statsButton.addEventListener( 'click' , this .displayStats.bind( this )); // resize listener, to call resizeMenu method each time the game is resized this .scale.on( 'resize' , this .resizeMenu, this ); } // method to be called each time the menu is resized resizeMenu() : void { // get actual game width let actualGameWidth : number = this .game.scale.displaySize.width; // get navbar element by its id let navbar : HTMLElement = document.getElementById( 'contentcontainer' ) as HTMLElement; // resize navbar navbar.style.width = Math.max(300, actualGameWidth).toString() + 'px' ; } // method to start the game in "word of the day" mode startWordOfTheDay() : void { // this is how we can pass parameters to a scene this .scene.start( 'PlayGame' , { game : gameMode.WORD_OF_THE_DAY }); } // method to start the game in "random" mode startRandom() : void { // this is how we can pass parameters to a scene this .scene.start( 'PlayGame' , { game : gameMode.RANDOM_WORD }); } // method to display statistics in the Bootstrap modal displayStats() : void { // get inner modal element by its id let modalElement : HTMLElement = document.getElementById( 'modalbody' ) as HTMLElement; // inject html modalElement.innerHTML = 'Here we\'ll place the stats<br />This content has been injected by Phaser' ; } } |
playGame.ts
Main game file, where we handle input and result management.
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 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 | // THE GAME ITSELF import KeyboardKey from './keyboardKey' ; import { GameOptions, gameMode } from './gameOptions' ; import { GameGrid } from './gameGrid' ; // possible word states: // perfect, when the letter is in the right position // correct, when the letter exists but it's not in the right position // wrong, when the letter does not exist enum letterState { WRONG = 1, CORRECT, PERFECT } // keyboard layout, as a string array, each item is a row of keys // > represents Enter // < represents Backspace const keyboardLayout : string[] = ['QWERTYUIOP ',' ASDFGHJKL ',' >ZXCVBNM< ']; // this class extends Scene class export class PlayGame extends Phaser.Scene { // array with all possible words words : string[]; // string where to store the current word currentWord : string; // string where to store the word to guess wordToGuess : string; // variable where to store game width gameWidth : number; // main game grid gameGrid : GameGrid; // virtual keyboard, as an array of KeyboardKey instances virtualKeyboard : KeyboardKey[][]; // constructor constructor() { super({ key: ' PlayGame ' }); } // method to be executed when the scene initialises init(mode : any) : void { // store JSON loaded words into words array this.words = this.cache.json.get(' words '); // different actions to do according to game mode // I am using a "switch" rather than an "if...then" because I plan to add more game modes switch (mode.game) { // no game mode defined, or "word of the day" mode case undefined : case gameMode.WORD_OF_THE_DAY : // set the random seed according to current date let seed : string[] = Array(new Date().toISOString().split("T")[0]); // set Phaser seed Phaser.Math.RND.sow(seed); // pick a random word, with the known seed this.wordToGuess = this.words[Phaser.Math.RND.between(0, this.words.length - 1)].toUpperCase(); break; // "random word" mode case gameMode.RANDOM_WORD : this.wordToGuess = Phaser.Utils.Array.GetRandom(this.words).toUpperCase(); } } // method to be executed when the scene has been created create() : void { // set gameWidth to actual game width this.gameWidth = this.game.config.width as number; // at the beginning, current word is empty this.currentWord = ' '; // let' s display somewhere the word to guess console.log( this .wordToGuess); // initialize virtual keyboard this .virtualKeyboard = []; // loop through keyboardLayout array keyboardLayout.forEach((row : string, index : number) => { // initialize virtual keyboard row this .virtualKeyboard[index] = []; // determine position of key sprites // some values are still hardcoded, and need to be optimized let rowWidth : number = 70 * row.length; let firstKeyPosition : number = ( this .game.config.width as number - rowWidth) / 2; // loop through string for ( let i : number = 0; i < row.length; i ++) { // get the i-th character let letter : string = row.charAt(i); // add the keyboard key this .virtualKeyboard[index][i] = new KeyboardKey( this , firstKeyPosition + i * 70 - (letter == '>' ? 35 : 0), 900 + index * 90, row.charAt(i)); } }); // add the game grid this .gameGrid = new GameGrid( this , GameOptions.rows, ( this .gameWidth - 540) / 2, GameOptions.firstRowY); // waiting for keyboard input this .input.keyboard.on( 'keydown' , this .onKeyDown, this ); } // method to process a key pressed onKeyDown(e : KeyboardEvent) : void { // store key pressed in key variable var key : string = e.key; // if the key is space, restart the game if (key == ' ' ) { this .scene.start( 'PlayGame' , { gameMode : 2 }); return ; } // backspace if (key == 'Backspace' ) { this .updateWord( '<' ); return ; } // regular expression saying "I want one letter" const regex = /^[a-zA-Z]{1}$/; // letter a-z or A-Z if (regex.test(key)) { this .updateWord(key); return ; } // enter if (key == 'Enter' ) { this .updateWord( '>' ); } } //method to be called each time we need to update a word updateWord(s : string) : void { switch (s) { // backsace case '<' : // if the word has at least one character, remove the last character if ( this .currentWord.length > 0) { // remove last current word character this .currentWord = this .currentWord.slice(0, -1); // call gameGrid's removeLetter method this .gameGrid.removeLetter(); } break ; // enter case '> ' : // if the word is 5 characters long, proceed to verify the result if (this.currentWord.length == 5) { // if the word is a valid word, proceed to verify the result if (this.words.includes(this.currentWord.toLowerCase())) { // at the beginning we se the result as a series of wrong characters let result : number[] = Array(5).fill(letterState.WRONG); // creation of a temp string with the word to guess let tempWord : string = this.wordToGuess; // loop through all word characters for (let i : number = 0; i < 5; i ++) { // do i-th char of the current word and i-th car of the word to guess match? if (this.currentWord.charAt(i) == tempWord.charAt(i)) { // this is a PERFECT result result[i] = letterState.PERFECT; // remove the i-th character from temp word tempWord = this.removeChar(tempWord, i); } // i-th char of the current word and i-th car of the word to guess do not match else { // loop through all character of the word to guess for (let j : number = 0; j < 5; j ++) { // do i-th char of the current word and j-th car of the word to guess match, // and don' t j-th char of the current word and j-th car of the word to guess match? if ( this .currentWord.charAt(i) == this .wordToGuess.charAt(j) && this .currentWord.charAt(j) != this .wordToGuess.charAt(j)) { // this is a correct result result[i] = letterState.CORRECT; // remove the i-th character from temp word tempWord = this .removeChar(tempWord, j); break ; } } } } // loop through all result items and compose result string accordingly result.forEach((element : number, index : number) => { // get letter position in our virtual keyboard let position : Phaser.Math.Vector2 = this .getLetterPosition( this .currentWord.charAt(index)); // if the key of the virtual keyboard has not already been painted, then paint it. if (parseInt( this .virtualKeyboard[position.x][position.y].frame.name) < element) { this .virtualKeyboard[position.x][position.y].setFrame(element); } }); // reset current word this .currentWord = '' ; // call gameGrid's showResult method this .gameGrid.showResult(result); } } break ; // a-z or A-Z default : // if the word is less than 5 characters long, remove last character if ( this .currentWord.length < 5) { // add the letter this .gameGrid.addLetter(s); // update current word this .currentWord += s.toUpperCase(); } } } // method to get the position the virtual keyboard key, given a letter getLetterPosition(letter : string) : Phaser.Math.Vector2 { // set row to zero let row : number = 0; // set column to zero let column : number = 0; // loop though all keyboardLayout array keyboardLayout.forEach((currentRow : string, index : number) => { // does current row include the letter? if (currentRow.includes(letter)) { // set row to index row = index; // set column to letter position inside current row column = currentRow.indexOf(letter); } }) // return the coordinate as a 2D vector return new Phaser.Math.Vector2(row, column); } // simple method to change the index-th character of a string with '_' // just to have an unmatchable character removeChar(initialString : string, index : number) : string { return initialString.substring(0, index) + '_' + initialString.substring(index + 1); } } |
keyboardKey.ts
Custom class for the virtual keyboard key.
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 | // KEYBOARD KEY CLASS import { PlayGame } from "./playGame" ; import KeyboardLetter from "./keyboardLetter" ; // this class extends Sprite class export default class KeyboardKey extends Phaser.GameObjects.Sprite { // letter bound to the key boundLetter : string; // parent scene parentScene : PlayGame; constructor(scene : PlayGame, x : number, y : number, letter : string) { // different image key according if it's a letter character or '<' or '>' super (scene, x, y, '<> '.includes(letter) ? ' bigkey ' : ' key '); // assign parent scene this.parentScene = scene; // assign bound letter this.boundLetter = letter; // set sprite registration point to top, left this.setOrigin(0); // add the sprite to the scene scene.add.existing(this); // set the sprite interactive this.setInteractive(); // listener for pointer down on the sprite, to call handlePointer callback this.on(' pointerdown ', this.handlePointer); // add a keyboard letter accoring to ' letter value switch (letter) { // backspace case '<' : this .setFrame(0); break ; // enter case '>' : this .setFrame(1); break ; // normal key default : new KeyboardLetter(scene, x + 10, y + 10, letter, 36); } } // method to be called when the user clicks or taps the letter handlePointer() : void { // call 'updateWord' method on parent scene this .parentScene.updateWord( this .boundLetter); } } |
keyboardLetter.ts
Custom class for the letter to be printed on the virtual keyboard key.
1 2 3 4 5 6 7 8 9 10 11 12 13 | // KEYBOARD LETTER import { PlayGame } from "./playGame" ; // this class extends BitmapText class export default class KeyboardLetter extends Phaser.GameObjects.BitmapText { constructor(scene : PlayGame, x : number, y : number, text : string, size : number) { super (scene, x, y, 'font' , text, size); // add the keyboard letter to the scene scene.add.existing( this ); } } |
gameGrid.ts
This class manages the grid where players write and try to guess the word.
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 | // GAME GRID import BigLetterBox from './bigLetterBox' ; export class GameGrid { // current row where to write the letter currentRow : number; // current column where to write the letter currentColumn : number; // array to store all letter boxes letterBox : BigLetterBox[][]; constructor(scene : Phaser.Scene, rows : number, firstRowX : number, firstRowY : number) { // set current row to zero this .currentRow = 0; // set current column to zero this .currentColumn = 0; // initialize letterBox array this .letterBox = []; // loop from 0 to 4 for ( let i: number = 0; i < 5; i ++) { // initialize letterBox[i] array this .letterBox[i] = []; // loop through all rows for ( let j : number = 0; j < rows; j ++) { // assign to letterBox[i][j] a new BigLetterBox instance this .letterBox[i][j] = new BigLetterBox(scene, firstRowX + i * 110, firstRowY + j * 110); } } } // method to add a latter addLetter(letter : string) : void { // set the letter at current row and column this .letterBox[ this .currentColumn][ this .currentRow].setLetter(letter); // increase current column this .currentColumn ++; } // method to remove a letter removeLetter() : void { // decrease current column this .currentColumn --; // unset the letter ant current row and column this .letterBox[ this .currentColumn][ this .currentRow].setLetter( '' ); } // show guess result showResult(result : number[]) : void { // loop through all result items result.forEach((element : number, index : number) => { // set letterBox frame according to element value this .letterBox[index][ this .currentRow].setFrame(element); // paint the letter white this .letterBox[index][ this .currentRow].letterToShow.setTint(0xffffff); }); // increase current row this .currentRow ++; // set current column to zero this .currentColumn = 0; } } |
bigLetterBox.ts
Class to mange the big letter box inside game grid.
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 | // BIG LETTER BOX import BigLetter from "./bigLetter" ; // this class extends Sprite class export default class BigLetterBox extends Phaser.GameObjects.Sprite { // letter to show in the big letter box letterToShow : BigLetter; constructor(scene : Phaser.Scene, x : number, y : number) { super (scene, x, y, 'box' ); // set registration point to top left corner this .setOrigin(0); // add the letter box to the scene scene.add.existing( this ); // calculate letter box bounds let bounds : Phaser.Geom.Rectangle = this .getBounds(); // assign letterToShow an instance of BigLetter this .letterToShow = new BigLetter(scene, bounds.centerX, bounds.centerY + 8); } // method to set the letter setLetter(letter : string) : void { // tint letterToShow black this .letterToShow.setTint(0); // set letterToShow text according to "letter" argument this .letterToShow.setText(letter.toUpperCase()); } } |
bigLetter.ts
Custom class to manage the big letter which appears in the big letter box.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // BIG LETTER // this class extends BitmapText class export default class BigLetter extends Phaser.GameObjects.BitmapText { constructor(scene : Phaser.Scene, x : number, y : number) { super (scene, x, y, 'bigfont' , '' ); // set registration point to center this .setOrigin(0.5); // add the letter to the scene scene.add.existing( this ); } } |
As you can see, there are two ways to use DOM objects in Phaser games, they can be included in the game itself or kept outside the game and controlled by listeners.
Which one would you use? As usual, you can download the source code of the entire project.
Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.