{"id":1070,"date":"2019-02-07T18:07:20","date_gmt":"2019-02-07T18:07:20","guid":{"rendered":"http:\/\/www.netexl.com\/blog\/?p=1070"},"modified":"2019-02-08T17:36:08","modified_gmt":"2019-02-08T17:36:08","slug":"exploring-phaser-3-with-a-game","status":"publish","type":"post","link":"https:\/\/www.netexl.com\/blog\/exploring-phaser-3-with-a-game\/","title":{"rendered":"Exploring Phaser 3 with a Game"},"content":{"rendered":"<p>After sticking with Phaser 2 (CE) for a while finally I decided to jump onto Phaser 3 (ver 3.15.1). Though there is still some work going on such as Scale Manager which is not released yet but the api seems to have grown and stablized with plenty of documentation available now.\u00a0We are going to write a game while we explore this deeper. Let us write classic game of Peg Solitaire which is also known as just \u201cSolitaire\u201d in England and Brainvita in \u201cIndia\u201d.<\/p>\n<p>First we are going to create an html file (index.html) with following content<\/p>\n<pre class=\"lang:default decode:true\">&lt;html&gt;\r\n    &lt;head&gt;\r\n        &lt;title&gt;Peg Solitaire&lt;\/title&gt;\r\n        &lt;meta http-equiv=\"Content-type\" content=\"text\/html; charset=utf-8\"&gt;\r\n        &lt;meta name=\"viewport\" content=\"width=device-width, minimum-scale=1, initial-scale=1, user-scalable=no\"&gt;\r\n \r\n        &lt;script src=\"js\/phaser.min.js\"&gt;&lt;\/script&gt;\r\n    &lt;\/head&gt;\r\n    &lt;body&gt;\r\n        &lt;script src=\"js\/game.js\"&gt;&lt;\/script&gt;\r\n    &lt;\/body&gt;\r\n&lt;\/html&gt;<\/pre>\n<p>html file includes phaser js and our game code (game.js) which is placed in js folder. Let us start with the basic one screen setup as following<\/p>\n<pre class=\"lang:default decode:true \">let loadGame = function () {\r\n\r\n    let config = {\r\n        type: Phaser.AUTO,\r\n        width: 500,\r\n        height: 500,\r\n        backgroundColor: 0x000000,\r\n        scene: TheGame\r\n    }\r\n\r\n    let game = new Phaser.Game(config);\r\n};\r\n\r\nif (document.readyState === \"complete\" || (document.readyState !== \"loading\" &amp;&amp; !document.documentElement.doScroll)) {\r\n    loadGame();\r\n} else {\r\n    document.addEventListener(\"DOMContentLoaded\", loadGame);\r\n}\r\n\r\nclass TheGame extends Phaser.Scene {\r\n\r\n    constructor() {\r\n        super(\"TheGame\");\r\n    }\r\n\r\n    preload() {\r\n    }\r\n\r\n    create() {\r\n    }\r\n}<\/pre>\n<p>If you are coming from Phaser 2 then you would notice some differences. We are starting a game and passing a config which defines the type (similar to Phaser 2; AUTO, WEBGAL, CANVAS), width and height of the game, backgroundColor and one scene. We have\u00a0a skeleton scene which is not doing anything at the moment. We will add code to this as we move forward. In Phaser 2 we has the concept of states where we could define multiple states and then load one state at a time. We could move between states such as a menu screen, game screen, game over screen. Scenes are something similar but highly advanced now. In Phaser 2 we could only load one state a time but now in Phaser 3 we can have multiple scenes running in parallel. We can move from one scene to another or run multiple screens at the same time. Scenes can overlay each other or can be placed adjacent to each other and all we have to do is to change the camera position to move from once scene to another. Scenes are much powerful now since they are now self-contained games in their own right with all game objects such as input, camera, tweens etc available to them. Scenes are managed by The Scene Manager but we would never need to access anything via The Scene Manager. Everything a game requires would be available inside scene itself.<\/p>\n<p>Read all about scenes in the Phaser 3 devlogs\u00a0<a href=\"https:\/\/phaser.io\/phaser3\/devlog\/119\" target=\"_blank\">here<\/a>\u00a0and\u00a0<a href=\"https:\/\/phaser.io\/phaser3\/devlog\/121\" target=\"_blank\">here<\/a>. Its a must read.<\/p>\n<p>The work so far gives us a screen as following<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-1062\" src=\"https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2019\/02\/Phaser3_Placeholder-300x172.png\" alt=\"\" width=\"300\" height=\"172\" srcset=\"https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2019\/02\/Phaser3_Placeholder-300x172.png 300w, https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2019\/02\/Phaser3_Placeholder-768x439.png 768w, https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2019\/02\/Phaser3_Placeholder-280x160.png 280w, https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2019\/02\/Phaser3_Placeholder.png 916w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/p>\n<p>Now let us center align our game in the html. Add following style to canvas element<\/p>\n<pre class=\"lang:default decode:true \">canvas{\r\n    display:block;\r\n    margin: 0;\r\n    position: absolute;\r\n    top: 50%;\r\n    left: 50%;\r\n    transform: translate(-50%, -50%);\r\n}<\/pre>\n<p>We will add this class in css file and load the css in html. Now we have an app.css file in css folder as following<\/p>\n<pre class=\"lang:default decode:true \">body {\r\n    padding: 0px;\r\n    margin: 0px;\r\n    background: #000;\r\n    overflow-x: hidden;\r\n\tfont-family:cursive;\r\n}\r\ncanvas{\r\n    display:block;\r\n    margin: 0;\r\n    position: absolute;\r\n    top: 50%;\r\n    left: 50%;\r\n    transform: translate(-50%, -50%);\r\n}<\/pre>\n<p>html file is changed to<\/p>\n<pre class=\"lang:default mark:6 decode:true \">&lt;html&gt;\r\n    &lt;head&gt;\r\n        &lt;title&gt;Peg Solitaire&lt;\/title&gt;\r\n        &lt;meta http-equiv=\"Content-type\" content=\"text\/html; charset=utf-8\"&gt;\r\n        &lt;meta name=\"viewport\" content=\"width=device-width, minimum-scale=1, initial-scale=1, user-scalable=no\"&gt;\r\n        &lt;link rel=\"stylesheet\" href=\"css\/app.css\"&gt;\r\n \r\n        &lt;script src=\"js\/phaser.min.js\"&gt;&lt;\/script&gt;\r\n    &lt;\/head&gt;\r\n    &lt;body&gt;\r\n        &lt;script src=\"js\/game.js\"&gt;&lt;\/script&gt;\r\n    &lt;\/body&gt;\r\n&lt;\/html&gt;<\/pre>\n<p>Also changed the background color of the game so now our empty game looks like following<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-1065\" src=\"https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2019\/02\/Phaser3_Placeholder1-300x209.png\" alt=\"\" width=\"300\" height=\"209\" srcset=\"https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2019\/02\/Phaser3_Placeholder1-300x209.png 300w, https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2019\/02\/Phaser3_Placeholder1-768x534.png 768w, https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2019\/02\/Phaser3_Placeholder1-280x195.png 280w, https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2019\/02\/Phaser3_Placeholder1.png 939w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/p>\n<p>We are going to create an english board. First we need to import a spritesheet which contains the images we are going to use in this game. I have a simple 3 image spritesheet as following<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-1071\" src=\"https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2019\/02\/pegs.png\" alt=\"\" width=\"180\" height=\"60\" \/><\/p>\n<p>We will import the spritesheet in preload method<\/p>\n<pre class=\"lang:default decode:true \">    preload() {\r\n        this.load.spritesheet(\"pegs\", \"images\/pegs.png\", {\r\n            frameWidth: 60,\r\n            frameHeight: 60\r\n        });\r\n    }<\/pre>\n<p>Now we are going to draw the board<\/p>\n<pre class=\"lang:default decode:true\">class TheGame extends Phaser.Scene {\r\n\r\n    constructor() {\r\n        super(\"TheGame\");\r\n    }\r\n\r\n    preload() {\r\n        this.load.spritesheet(\"pegs\", \"images\/pegs.png\", {\r\n            frameWidth: 60,\r\n            frameHeight: 60\r\n        });\r\n    }\r\n\r\n    create() {\r\n        this.boardDef = [\r\n            [-1, -1, 1, 1, 1, -1, -1],\r\n            [-1, -1, 1, 1, 1, -1, -1],\r\n            [1, 1, 1, 1, 1, 1, 1],\r\n            [1, 1, 1, 0, 1, 1, 1],\r\n            [1, 1, 1, 1, 1, 1, 1],\r\n            [-1, -1, 1, 1, 1, -1, -1],\r\n            [-1, -1, 1, 1, 1, -1, -1]\r\n        ];\r\n\r\n        \/\/ add our sprites\r\n        this.board = [];\r\n        this.selectedPeg = null;\r\n\r\n        for (let i = 0, len = this.boardDef.length; i &lt; len; i++) {\r\n            let r = this.boardDef[i];\r\n            let row = [];\r\n            this.board.push(row);\r\n            for (let j = 0, cnt = r.length; j &lt; cnt; j++) {\r\n                let c = r[j];\r\n                if (c &gt;= 0) {\r\n                    let cell = this.add.image(30 + i * 60, 30 + j * 60, \"pegs\");\r\n                    cell.setFrame(c &gt; 0 ? 1 : 0);\r\n                    cell.setOrigin(0);\r\n                    row.push(cell);\r\n                } else {\r\n                    row.push(null);\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    clickPeg(peg) {\r\n    }\r\n}<\/pre>\n<p>Now we have drawn the\u00a0board as following<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-1073\" src=\"https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2019\/02\/Phaser3_Peg_Game_1-300x240.png\" alt=\"\" width=\"300\" height=\"240\" srcset=\"https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2019\/02\/Phaser3_Peg_Game_1-300x240.png 300w, https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2019\/02\/Phaser3_Peg_Game_1-768x616.png 768w, https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2019\/02\/Phaser3_Peg_Game_1-280x224.png 280w, https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2019\/02\/Phaser3_Peg_Game_1-100x80.png 100w, https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2019\/02\/Phaser3_Peg_Game_1.png 821w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/p>\n<p>In this code we have added the images to their positions.\u00a0The game play\u00a0can be done by simply changing the frame\u00a0according to the rules. No heavy processing or complex logic is required in the game (unless we want to add automatic solver for the game).<\/p>\n<p>One important change in Phaser 3 from Phaser 2 is that all images or sprites are by default positioned from center (in Phaser 2 this started from top left corner) so if we want to change it to top left corner then we can use setOrigin method and set it to 0.<\/p>\n<p>Now let us simply add some event and game code<\/p>\n<pre class=\"lang:default mark:25-29,46-48 decode:true \">class TheGame extends Phaser.Scene {\r\n\r\n    constructor() {\r\n        super(\"TheGame\");\r\n    }\r\n\r\n    preload() {\r\n        this.load.spritesheet(\"pegs\", \"images\/pegs.png\", {\r\n            frameWidth: 60,\r\n            frameHeight: 60\r\n        });\r\n    }\r\n\r\n    create() {\r\n        this.boardDef = [\r\n            [-1, -1, 1, 1, 1, -1, -1],\r\n            [-1, -1, 1, 1, 1, -1, -1],\r\n            [1, 1, 1, 1, 1, 1, 1],\r\n            [1, 1, 1, 0, 1, 1, 1],\r\n            [1, 1, 1, 1, 1, 1, 1],\r\n            [-1, -1, 1, 1, 1, -1, -1],\r\n            [-1, -1, 1, 1, 1, -1, -1]\r\n        ];\r\n\r\n        \/\/  If a Game Object is clicked on, this event is fired.\r\n        \/\/  We can use it to emit the 'clicked' event on the game object itself.\r\n        this.input.on('gameobjectup', function (pointer, gameObject) {\r\n            gameObject.emit('clicked', gameObject);\r\n        }, this);\r\n\r\n        \/\/ add our sprites\r\n        this.board = [];\r\n        this.selectedPeg = null;\r\n\r\n        for (let i = 0, len = this.boardDef.length; i &lt; len; i++) {\r\n            let r = this.boardDef[i];\r\n            let row = [];\r\n            this.board.push(row);\r\n            for (let j = 0, cnt = r.length; j &lt; cnt; j++) {\r\n                let c = r[j];\r\n                if (c &gt;= 0) {\r\n                    let cell = this.add.image(30 + i * 60, 30 + j * 60, \"pegs\");\r\n                    cell.setFrame(c &gt; 0 ? 1 : 0);\r\n                    cell.setOrigin(0);\r\n\r\n                    \/\/ enable input events\r\n                    cell.setInteractive();\r\n                    cell.on('clicked', this.clickPeg, this);\r\n                    cell.gridX = i;\r\n                    cell.gridY = j;\r\n                    row.push(cell);\r\n                } else {\r\n                    row.push(null);\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    clickPeg(peg) {\r\n        if (peg.frame.name === 0) {\r\n            \/\/ if we have not selected a peg to jump then no need to move any further\r\n            if (!this.selectedPeg)\r\n                return;\r\n\r\n            let clickedX = peg.gridX;\r\n            let clickedY = peg.gridY;\r\n            let selectedX = this.selectedPeg.gridX;\r\n            let selectedY = this.selectedPeg.gridY;\r\n\r\n            if ((clickedX + 2 === selectedX || clickedX - 2 === selectedX) &amp;&amp; clickedY === selectedY) {\r\n                \/\/ move horizontal\r\n                let pegToRemove = this.board[(selectedX + clickedX) \/ 2][clickedY];\r\n                if (pegToRemove.frame.name === 0)\r\n                    return;\r\n\r\n                pegToRemove.setFrame(0);\r\n                peg.setFrame(1);\r\n                this.selectedPeg.setFrame(0);\r\n                this.selectedPeg = null;\r\n\r\n            } else if ((clickedY + 2 === selectedY || clickedY - 2 === selectedY) &amp;&amp; clickedX === selectedX) {\r\n                \/\/ move vertical\r\n                let pegToRemove = this.board[clickedX][(selectedY + clickedY) \/ 2];\r\n                if (pegToRemove.frame.name === 0)\r\n                    return;\r\n\r\n                pegToRemove.setFrame(0);\r\n                peg.setFrame(1);\r\n                this.selectedPeg.setFrame(0);\r\n                this.selectedPeg = null;\r\n            }\r\n\r\n        } else {\r\n            if (this.selectedPeg) {\r\n                if (peg === this.selectedPeg) {\r\n                    peg.setFrame(1);\r\n                    this.selectedPeg = null;\r\n                } else {\r\n                    this.selectedPeg.setFrame(1);\r\n                    this.selectedPeg = peg;\r\n                    peg.setFrame(2);\r\n                }\r\n            } else {\r\n                this.selectedPeg = peg;\r\n                peg.setFrame(2);\r\n            }\r\n        }\r\n    }\r\n}<\/pre>\n<p>Now we have a working Peg Solitaire game in about 100 lines of code. I have highlighted the code used for adding the event which was taken right out of the example on Phaser 3 demo site. You can try the code at work below<\/p>\n\n<!-- iframe plugin v.5.2 wordpress.org\/plugins\/iframe\/ -->\n<iframe loading=\"lazy\" src=\"https:\/\/www.netexl.com\/blog\/demo\/14\/index.html\" width=\"500px\" height=\"500px\" scrolling=\"yes\" class=\"iframe-class\" frameborder=\"0\"><\/iframe>\n\n<p>What is next:<\/p>\n<p>The game does not display any message on game over so in next part we are going to add another scene which in turn let us make use of multiple scenes in the game. We would also like to add some animations to the game\u00a0when\u00a0pegs are jumped. I also think that games should be written\u00a0with responsive in mind so in next part we would\u00a0explore the\u00a0responsive behaviour in Phaser 3. In fact Scale Manager is round the corner with next release so we can explore that as well once it is out.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>After sticking with Phaser 2 (CE) for a while finally I decided to jump onto Phaser 3 (ver 3.15.1). Though there is still some work going on such as Scale Manager which is not released yet but the api seems to have grown and stablized with plenty of documentation available now.\u00a0We are going to write a game while we explore this deeper. Let us write classic game of Peg Solitaire which is also known as just \u201cSolitaire\u201d in England and Brainvita in \u201cIndia\u201d. First we are going to create an html file (index.html) with following content &lt;html&gt; &lt;head&gt; &lt;title&gt;Peg Solitaire&lt;\/title&gt; &lt;meta http-equiv=&#8221;Content-type&#8221; content=&#8221;text\/html; charset=utf-8&#8243;&gt; &lt;meta name=&#8221;viewport&#8221; content=&#8221;width=device-width, minimum-scale=1, initial-scale=1, user-scalable=no&#8221;&gt; &lt;script src=&#8221;js\/phaser.min.js&#8221;&gt;&lt;\/script&gt; &lt;\/head&gt; &lt;body&gt; &lt;script src=&#8221;js\/game.js&#8221;&gt;&lt;\/script&gt; &lt;\/body&gt; &lt;\/html&gt; html file includes phaser js and our game code (game.js) which is placed in js folder. Let us start with the basic one screen setup as following let loadGame = function ()[&#8230;]<\/p>\n","protected":false},"author":5,"featured_media":293,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3,24,4],"tags":[],"class_list":["post-1070","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-html5","category-javascript","category-phaser"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/posts\/1070","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/comments?post=1070"}],"version-history":[{"count":4,"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/posts\/1070\/revisions"}],"predecessor-version":[{"id":1088,"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/posts\/1070\/revisions\/1088"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/media\/293"}],"wp:attachment":[{"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/media?parent=1070"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/categories?post=1070"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/tags?post=1070"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}