{"id":87,"date":"2017-04-02T20:24:29","date_gmt":"2017-04-02T20:24:29","guid":{"rendered":"http:\/\/www.netexl.com\/blog\/?p=87"},"modified":"2017-11-06T19:37:38","modified_gmt":"2017-11-06T19:37:38","slug":"making-of-a-responsive-game-in-phaser-part-3","status":"publish","type":"post","link":"https:\/\/www.netexl.com\/blog\/making-of-a-responsive-game-in-phaser-part-3\/","title":{"rendered":"Making of a responsive game in Phaser: Part 3"},"content":{"rendered":"<p>In <a href=\"https:\/\/www.netexl.com\/blog\/making-of-a-responsive-game-in-phaser-part-1\/\" target=\"_blank\">part one<\/a> and <a href=\"https:\/\/www.netexl.com\/blog\/making-of-a-responsive-game-in-phaser-part-2\/\" target=\"_blank\">part two<\/a> of this article series, we designed &#8220;Main Menu&#8221; screen and did some code to\u00a0manage scaling of it for various resolutions and aspect ratios. &#8220;Main Menu&#8221; screen followed the same layout for both portrait and landscape orientations.<\/p>\n<p>In part three we will be working on actual &#8220;Game&#8221; screen and implement\u00a0different layouts for\u00a0portrait and landscape orientations. The game is a number puzzle which contains a 6&#215;6 number tiles grid, 2 additional tiles for displaying current and best scores and another 3 icons for various options.<\/p>\n<p>In the image below you will see the alignment of various elements in portrait and landscape orientations. The layouts are different here. In portrait orientation the grid is centered with other elements placed on top and bottom of it while in landscape orientation the grid is moved to the left which takes 2\/3rd of the available width and rest of the elements are stacked on top of each other in the remaining 1\/3rd space.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-91\" src=\"https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2017\/04\/ResponsiveGame2-300x156.png\" alt=\"\" width=\"408\" height=\"212\" srcset=\"https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2017\/04\/ResponsiveGame2-300x156.png 300w, https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2017\/04\/ResponsiveGame2-280x145.png 280w, https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2017\/04\/ResponsiveGame2.png 650w\" sizes=\"auto, (max-width: 408px) 100vw, 408px\" \/><\/p>\n<p>Let us look at the code below for key points.<\/p>\n<pre class=\"theme:xcode lang:js decode:true \">TheGame.MyGame = function (game) {\r\n};\r\n\r\nTheGame.MyGame.prototype = {\r\n    preload: function () {\r\n    },\r\n    create: function () {\r\n\t\tthis.background = this.add.image(0, 0, \"background\");\r\n\t\tthis.background.height = this.game.height;\r\n\t\tthis.background.width = this.game.width;\r\n\r\n        \/\/ First add all sprites to the game and then position them after scaling\r\n\t\tthis.scoreTile = this.game.add.image(-200, -200, \"current\");\r\n\t\tthis.scoreTile.anchor.setTo(0.5);\r\n\t\tthis.bestScoreTile = this.game.add.image(-200, -200 , \"best\");\r\n\t\tthis.bestScoreTile.anchor.setTo(0.5);\r\n\t\tthis.soundButton = this.game.add.button(-200, -200, \"settings\", this.toggleSound, this);\r\n\t\tthis.soundButton.anchor.setTo(0.5);\r\n\t\tthis.soundButton.frame = 2;\r\n\t\tthis.newButton = this.game.add.button(-200 , -200, \"settings\", this.toggleSound, this);\r\n\t\tthis.newButton.anchor.setTo(0.5);\r\n\t\tthis.newButton.frame = 5;\r\n\t\tthis.helpButton = this.game.add.button(-200 , -200, \"settings\", this.toggleSound, this);\r\n\t\tthis.helpButton.anchor.setTo(0.5);\r\n\t\tthis.helpButton.frame = 4;\r\n\t\t\r\n        \/\/ Add all grid tiles to the game\r\n        this.initTiles();\r\n                \r\n        \/\/ Position the controls using available width and height in the game\r\n\t\tthis.positionControls(this.game.width, this.game.height);\r\n\r\n    },\r\n\tscaleSprite: function (sprite, availableSpaceWidth, availableSpaceHeight, padding, scaleMultiplier, isFullScale) {\r\n\t\tvar scale = this.getSpriteScale(sprite._frame.width, sprite._frame.height, availableSpaceWidth, availableSpaceHeight, padding, isFullScale);\r\n\t\tsprite.scale.x = scale * scaleMultiplier;\r\n\t\tsprite.scale.y = scale * scaleMultiplier;\r\n\t},\r\n\tgetSpriteScale: function (spriteWidth, spriteHeight, availableSpaceWidth, availableSpaceHeight, minPadding, isFullScale) {\r\n\t\tvar ratio = 1;\r\n\t\tvar currentDevicePixelRatio = window.devicePixelRatio;\r\n\t\t\/\/ Sprite needs to fit in either width or height\r\n\t\tvar widthRatio = (spriteWidth * currentDevicePixelRatio + 2 * minPadding) \/ availableSpaceWidth;\r\n\t\tvar heightRatio = (spriteHeight * currentDevicePixelRatio + 2 * minPadding) \/ availableSpaceHeight;\r\n\t\tif(widthRatio &gt; 1 || heightRatio &gt; 1){\r\n\t\t\tratio = 1 \/ Math.max(widthRatio, heightRatio);\r\n\t\t} else {\r\n\t\t\tif(isFullScale)\r\n\t\t\t\tratio = 1 \/ Math.max(widthRatio, heightRatio);\r\n\t\t}\r\n\t\treturn ratio * currentDevicePixelRatio;\t\r\n\t},\r\n\tresize: function (width, height) {\r\n\t\tthis.background.height = height;\r\n\t\tthis.background.width = width;\r\n\t\tthis.positionControls(width, height);\r\n\t},\r\n\tpositionControls: function (width, height) {\r\n\t\t\/\/ We would consider landscape orientation if height to width ratio is less than 1.3.\r\n\t\t\/\/ Pick any value you like if you have a different preference for landscape or portrait orientation\r\n        var isLandscape = height \/ width  &lt; 1.3 ? true: false;\r\n\t\tif(isLandscape){\r\n\t\t\tvar availableGridSpace = Math.min(width * 2 \/ 3, height);\r\n\t\t\tthis.calculatedTileSize = (availableGridSpace * 0.9) \/ 6;\r\n\t\t\tthis.verticalMargin = (height - 6 * this.calculatedTileSize) \/ 2;\r\n\t\t\tthis.horizontalMargin = (width * 2 \/ 3 - 6 * this.calculatedTileSize) \/ 2;\r\n\t\t\t\r\n\t\t\tthis.tileGroup.x = this.horizontalMargin;\r\n\t\t\tthis.tileGroup.y = this.verticalMargin;\r\n\r\n\t\t\tthis.scaleSprite(this.scoreTile, width \/ 3, height \/ 3, 50, 1);\r\n\t\t\tthis.scoreTile.x = width * 2 \/ 3 + width \/ 6;\r\n\t\t\tthis.scoreTile.y = this.verticalMargin + this.scoreTile.height \/ 2;\r\n\r\n\t\t\tthis.scaleSprite(this.bestScoreTile, width \/ 3, height \/ 3, 50, 1);\t\t\t\r\n\t\t\tthis.bestScoreTile.x = width * 2 \/ 3 + width \/ 6;\r\n\t\t\tthis.bestScoreTile.y = this.verticalMargin + this.scoreTile.height + 50;\r\n\r\n\t\t\tvar calculatedSettingsVerticalSpace = height - 2 * this.verticalMargin - 2 * 50 - this.scoreTile.height - this.bestScoreTile.height;\r\n\r\n\t\t\tthis.scaleSprite(this.soundButton, width \/ 3, calculatedSettingsVerticalSpace \/ 3, 20, 1);\r\n\t\t\tthis.soundButton.x = width * 2 \/ 3 + width \/ 6;\r\n\t\t\tthis.soundButton.y = height - this.verticalMargin - this.soundButton.height \/ 2;\r\n\r\n\t\t\tthis.scaleSprite(this.newButton, width \/ 3, calculatedSettingsVerticalSpace \/ 3, 20, 1);\r\n\t\t\tthis.newButton.x = width * 2 \/ 3 + width \/ 6;\r\n\t\t\tthis.newButton.y = height - this.verticalMargin - this.soundButton.height - 50;\r\n\t\t\r\n\t\t\tthis.scaleSprite(this.helpButton, width \/ 3, calculatedSettingsVerticalSpace \/ 3, 20, 1);\r\n\t\t\tthis.helpButton.x = width * 2 \/ 3 + width \/ 6;\r\n\t\t\tthis.helpButton.y = height - this.verticalMargin - this.soundButton.height - this.newButton.height\/ 2 - 100;\r\n\r\n\t\t\tfor (var i = 0; i &lt; TheGame.Params.fieldSize.rows; i++) {\r\n\t\t\t\tfor (var j = 0; j &lt; TheGame.Params.fieldSize.cols; j++) {\r\n\t\t\t\t\tthis.scaleSprite(this.tilesArray[i][j], this.calculatedTileSize, this.calculatedTileSize, 0, 1, true);\r\n\t\t\t\t\tvar tileX = j * this.calculatedTileSize + this.calculatedTileSize \/ 2;\r\n\t\t\t\t\tvar tileY = i * this.calculatedTileSize + this.calculatedTileSize \/ 2;\r\n\t\t\t\t\tthis.tilesArray[i][j].x = tileX;\r\n\t\t\t\t\tthis.tilesArray[i][j].y = tileY;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t} else {\r\n\t\t\t\r\n\t\t\tvar availableGridSpace = this.game.width;\r\n\t\t\tthis.calculatedTileSize = (availableGridSpace * 0.9) \/ 6;\r\n\t\t\tthis.verticalMargin = (this.game.height - 6 * this.calculatedTileSize) \/ 2;\r\n\t\t\tthis.horizontalMargin = (this.game.width - 6 * this.calculatedTileSize) \/ 2;\r\n\t\t\t\r\n\t\t\tthis.tileGroup.x = this.horizontalMargin;\r\n\t\t\tthis.tileGroup.y = this.verticalMargin;\r\n\t\r\n\t\t\tthis.scaleSprite(this.scoreTile, width \/ 2, this.verticalMargin, 20, 1);\r\n\t\t\tthis.scoreTile.x = width \/ 4;\r\n\t\t\tthis.scoreTile.y = this.verticalMargin \/ 2;\r\n\r\n\t\t\tthis.scaleSprite(this.bestScoreTile, width \/ 2, this.verticalMargin, 20, 1);\t\t\t\r\n\t\t\tthis.bestScoreTile.x = width * 3 \/ 4;\r\n\t\t\tthis.bestScoreTile.y = this.verticalMargin \/ 2;\r\n\r\n\t\t\tthis.scaleSprite(this.soundButton, width \/ 3, this.verticalMargin, 50, 0.75);\r\n\t\t\tthis.soundButton.x = this.world.centerX;\r\n\t\t\tthis.soundButton.y = height - this.verticalMargin \/ 2;\r\n\r\n\t\t\tthis.scaleSprite(this.newButton, width \/ 3, this.verticalMargin, 50, 0.75);\r\n\t\t\tthis.newButton.x = this.world.centerX - this.soundButton.width;\r\n\t\t\tthis.newButton.y = height - this.verticalMargin \/ 2;\r\n\t\t\t\r\n\t\t\tthis.scaleSprite(this.helpButton, width \/ 3, this.verticalMargin, 50, 0.75);\r\n\t\t\tthis.helpButton.x = this.world.centerX + this.soundButton.width;\r\n\t\t\tthis.helpButton.y = height - this.verticalMargin \/ 2;\r\n\r\n\t\t\tfor (var i = 0; i &lt; TheGame.Params.fieldSize.rows; i++) {\r\n\t\t\t\tfor (var j = 0; j &lt; TheGame.Params.fieldSize.cols; j++) {\r\n\t\t\t\t\tthis.scaleSprite(this.tilesArray[i][j], this.calculatedTileSize, this.calculatedTileSize, 0, 1, true);\r\n\t\t\t\t\tvar tileX = j * this.calculatedTileSize + this.calculatedTileSize \/ 2;\r\n\t\t\t\t\tvar tileY = i * this.calculatedTileSize + this.calculatedTileSize \/ 2;\r\n\t\t\t\t\tthis.tilesArray[i][j].x = tileX;\r\n\t\t\t\t\tthis.tilesArray[i][j].y = tileY;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\t\t\r\n\t},\r\n    initTiles: function () {\r\n\r\n\t\tthis.tilesArray = [];\r\n        this.tileGroup = this.game.add.group();\r\n\t\tthis.tileGroup.x = this.horizontalMargin;\r\n\t\tthis.tileGroup.y = this.verticalMargin;\r\n\r\n        for (var i = 0; i &lt; TheGame.Params.fieldSize.rows; i++) {\r\n            this.tilesArray[i] = [];\r\n            for (var j = 0; j &lt; TheGame.Params.fieldSize.cols; j++) {\r\n\r\n\t\t\t\tvar tileX = j * this.calculatedTileSize + this.calculatedTileSize \/ 2;\r\n\t\t\t\tvar tileY = i * this.calculatedTileSize + this.calculatedTileSize \/ 2;\r\n\t\t\t\tvar tile = this.game.add.sprite(tileX, tileY, \"tiles\");\r\n\t\t\t\ttile.anchor.set(0.5);\r\n\t\t\t\ttile.value = 0;\r\n\t\t\t\tthis.scaleSprite(tile, this.calculatedTileSize, this.calculatedTileSize, 0, 1);\r\n\t\t\t\tthis.tilesArray[i][j] = tile;\r\n\t\t\t\tthis.tileGroup.add(tile);\r\n\t\t\t\r\n            }\r\n        }\r\n\r\n    },\r\n    toggleSound: function (button) {\r\n    },\r\n    restart: function (button) {\r\n    },\r\n    help: function (button) {\r\n    }\r\n};<\/pre>\n<p>Screenshots of portrait and landscape orientations are given below. You can\u00a0check demo of &#8220;Game&#8221; screen on your device <a href=\"https:\/\/www.netexl.com\/blog\/demo\/2\/index.html\" target=\"_blank\">here<\/a>.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-100\" src=\"https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2017\/04\/ResposiveGamePart3-1-169x300.png\" alt=\"\" width=\"169\" height=\"300\" srcset=\"https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2017\/04\/ResposiveGamePart3-1-169x300.png 169w, https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2017\/04\/ResposiveGamePart3-1-158x280.png 158w, https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2017\/04\/ResposiveGamePart3-1.png 252w\" sizes=\"auto, (max-width: 169px) 100vw, 169px\" \/><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-101\" src=\"https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2017\/04\/ResposiveGamePart3-2-300x188.png\" alt=\"\" width=\"300\" height=\"188\" srcset=\"https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2017\/04\/ResposiveGamePart3-2-300x188.png 300w, https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2017\/04\/ResposiveGamePart3-2-280x175.png 280w, https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2017\/04\/ResposiveGamePart3-2.png 448w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/p>\n<p>We looked at &#8220;<em>getSpriteScale<\/em>&#8221; in part two of this article\u00a0series. There is one change to this method. We are passing an additional parameter &#8220;<em>isFullScreen<\/em>&#8221; which will stretch sprite to fit into the available space if available width and height is more than the sprite size. This is required since the number tiles are required to be exact fit in the available space. Current number\u00a0tiles are assumed to be square but this method can be changed for rectangular tiles accordingly. We don&#8217;t have any such need at the moment so we will leave it as-is.<\/p>\n<p>In &#8220;positionControls&#8221; method we are aligning controls according to their orientation. It actually does not matter whether the game is running on pc or mobile device. All we care for is the available width and height for the game and shall resize everything else accordingly.<\/p>\n<p>There is a landscape ratio we are using in the game which is used to decide when to switch to portrait mode.<\/p>\n<p>In portrait mode<\/p>\n<ul>\n<li>Puzzle grid is centered and uses the height same as it width.<\/li>\n<li>Remaining space is divided into upper section and lower section.<\/li>\n<li>Upper section contains current and high score tiles.<\/li>\n<li>Lower section contains 3 icons for various options.<\/li>\n<\/ul>\n<p>In landscape mode<\/p>\n<ul>\n<li>Puzzle grid is left aligned and is allocated 2\/3rd of the available space.<\/li>\n<li>Remaining 1\/3rd space contains current and high score tiles as well as 3 icons for various options which are stacked on top of each other and center aligned to the 1\/3rd space.<\/li>\n<\/ul>\n<div style=\"height:250px;\"><script async src=\"\/\/pagead2.googlesyndication.com\/pagead\/js\/adsbygoogle.js\"><\/script>\r\n<ins class=\"adsbygoogle\"\r\n     style=\"display:inline-block;width:300px;height:250px\"\r\n     data-ad-client=\"ca-pub-0810934084049672\"\r\n     data-ad-slot=\"8927903874\"><\/ins>\r\n<script>\r\n(adsbygoogle = window.adsbygoogle || []).push({});\r\n<\/script><\/div>\n<h3>Next<\/h3>\n<p>Working on finishing the game and will post the link to finished game for you to play. Until then.. Happy coding..<\/p>\n<p><strong>Update: <\/strong>I finished the game and a few things changed during the process. Info icon was removed\u00a0and some more minor changes to the alignment of individual elements but the basic concept remains the same.<strong>\u00a0<\/strong>Check out finished game<strong>\u00a0<\/strong>link below<\/p>\n<p><a href=\"http:\/\/www.netexl.com\/games\/numberspread\/\" target=\"_blank\">Number Spread<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In part one and part two of this article series, we designed &#8220;Main Menu&#8221; screen and did some code to\u00a0manage scaling of it for various resolutions and aspect ratios. &#8220;Main Menu&#8221; screen followed the same layout for both portrait and landscape orientations. In part three we will be working on actual &#8220;Game&#8221; screen and implement\u00a0different layouts for\u00a0portrait and landscape orientations. The game is a number puzzle which contains a 6&#215;6 number tiles grid, 2 additional tiles for displaying current and best scores and another 3 icons for various options. In the image below you will see the alignment of various elements in portrait and landscape orientations. The layouts are different here. In portrait orientation the grid is centered with other elements placed on top and bottom of it while in landscape orientation the grid is moved to the left which takes 2\/3rd of the available width and rest of the[&#8230;]<\/p>\n","protected":false},"author":5,"featured_media":85,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5,3,4],"tags":[],"class_list":["post-87","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-framework","category-html5","category-phaser"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/posts\/87","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=87"}],"version-history":[{"count":5,"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/posts\/87\/revisions"}],"predecessor-version":[{"id":606,"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/posts\/87\/revisions\/606"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/media\/85"}],"wp:attachment":[{"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/media?parent=87"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/categories?post=87"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/tags?post=87"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}