Here you will install the TypeScript dependencies required for this project, transpile your TypeScript code to JavaScript, and add PhaserJS to your Svelte JavaScript framework.
Install NPM to manage your dependencies. From your terminal window:
1
npm install
Before starting your Nakama server, transpile the TypeScript code to JS:
1
npx tsc
Add the PhaserJS script tag to your index.html file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html><htmllang="en"><head><metacharset='utf-8'><metaname='viewport'content='width=device-width,initial-scale=1'><title>Svelte app</title><linkrel='icon'type='image/png'href='/favicon.png'><linkrel='stylesheet'href='/global.css'><linkrel='stylesheet'href='/build/bundle.css'> // PhaserJS script tag
<scriptsrc="//cdn.jsdelivr.net/npm/phaser@3.54.0/dist/phaser.min.js"></script><scriptdefersrc='/build/bundle.js'></script></head><body></body></html>
You can now run your application locally by running:
1
npm run dev
Your application will be available at localhost:5000.
Now you can start creating the game’s Phaser scenes to group related logic. For our game we have three, the main menu, matchmaking, and in game scenes:
// ...
create(){Nakama.authenticate()// Create the Welcome banner
this.add.text(CONFIG.WIDTH/2,75,"Welcome to",{fontFamily:"Arial",fontSize:"24px",}).setOrigin(0.5);this.add.text(CONFIG.WIDTH/2,123,"XOXO",{fontFamily:"Arial",fontSize:"72px",}).setOrigin(0.5);this.add.grid(CONFIG.WIDTH/2,CONFIG.HEIGHT/2,300,300,100,100,0xffffff,0,0xffca27);// Create a button to start the game
constplayBtn=this.add.rectangle(CONFIG.WIDTH/2,625,225,70,0xffca27).setInteractive({useHandCursor:true});constplayBtnText=this.add.text(CONFIG.WIDTH/2,625,"Begin",{fontFamily:"Arial",fontSize:"36px",}).setOrigin(0.5);playBtn.on("pointerdown",()=>{Nakama.findMatch()this.scene.start("in-game");});// ...
}}
// ...
create(){this.add.text(CONFIG.WIDTH/2,125,"Searching for an opponent...",{fontFamily:"Arial",fontSize:"24px",}).setOrigin(0.5);this.anims.create({key:"spinnerAnimation",frames:this.anims.generateFrameNumbers("spinner"),frameRate:30,repeat:Phaser.FOREVER,});this.add.sprite(CONFIG.WIDTH/2,CONFIG.HEIGHT/2,"spinner").play("spinnerAnimation").setScale(0.5);}}
// ...
updateBoard(board){board.forEach((element,index)=>{letnewImage=this.INDEX_TO_POS[index]if(element===1){this.phaser.add.image(newImage.x,newImage.y,"O");}elseif(element===2){this.phaser.add.image(newImage.x,newImage.y,"X");}})}updatePlayerTurn(){this.playerTurn=!this.playerTurnif(this.playerTurn){this.headerText.setText("Your turn!")}else{this.headerText.setText("Opponents turn!")}}setPlayerTurn(data){letuserId=localStorage.getItem("user_id");if(data.marks[userId]===1){this.playerTurn=true;this.playerPos=1;this.headerText.setText("Your turn!")}else{this.headerText.setText("Opponents turn!")}}endGame(data){this.updateBoard(data.board)if(data.winner===this.playerPos){this.headerText.setText("Winner!")}else{this.headerText.setText("You loose :(")}}nakamaListener(){Nakama.socket.onmatchdata=(result)=>{switch(result.op_code){case1:this.gameStarted=true;this.setPlayerTurn(result.data)break;case2:console.log(result.data)this.updateBoard(result.data.board)this.updatePlayerTurn()break;case3:this.endGame(result.data)break;}};}// ...
// Register the player move in the correct square
this.nakamaListener()this.add.rectangle(gridCenterX-gridCellWidth,topY,gridCellWidth,gridCellWidth).setInteractive({useHandCursor:true}).on("pointerdown",async()=>{awaitNakama.makeMove(0)});this.add.rectangle(gridCenterX,topY,gridCellWidth,gridCellWidth).setInteractive({useHandCursor:true}).on("pointerdown",()=>{Nakama.makeMove(1)});// ...
}}
In the Main Menu scene, you authenticate the user in Nakama (discussed below) and display a “Welcome to XOXO” banner and button that, on click, takes the user to the In Game scene.
The Matchmaking scene simply displays a waiting spinner while Nakama finds an opponent for the user.
For the In Game scene, you are creating the interactive board of nine individual squares for players to enter their X’s and O’s. You also define the gameplay functions to set and update players turns, and end the game. The nakamaListener defined here communicates these actions via websocket to the Nakama server.
When testing the game locally with two browser windows, you should set deviceId to uuidv4() rather than get it from localStorage, otherwise you will authenticate as the same user twice which will cause the first session to disconnect and make the game unplayable.