{"id":61,"date":"2017-03-29T09:03:23","date_gmt":"2017-03-29T09:03:23","guid":{"rendered":"http:\/\/www.netexl.com\/blog\/?p=61"},"modified":"2017-11-06T19:36:09","modified_gmt":"2017-11-06T19:36:09","slug":"making-of-a-responsive-game-in-phaser-part-2","status":"publish","type":"post","link":"https:\/\/www.netexl.com\/blog\/making-of-a-responsive-game-in-phaser-part-2\/","title":{"rendered":"Making of a responsive game in Phaser: Part 2"},"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>\u00a0of this series we did\u00a0a &#8220;M<em>ain Menu<\/em>&#8221;\u00a0screen design for our game which can be read\u00a0<a href=\"https:\/\/www.netexl.com\/blog\/making-of-a-responsive-game-in-phaser-part-1\/\" target=\"_blank\">here<\/a>. Now its time to dive into the code.<\/p>\n<p>In order to reach to the &#8220;<em>Main Menu<\/em>&#8221; screen, we are going to use 3 states. The first state will be &#8220;<em>Boot<\/em>&#8221; state which is going to set the scale mode to <strong><em>RESIZE<\/em><\/strong>. Its also going to load an image which will be displayed while rest of the assets are loaded in the next state\u00a0which will be\u00a0called &#8220;<em>Loading<\/em>&#8221; state. Once all assets are loaded in the &#8220;<em>Loading<\/em>&#8221; state, we move to &#8220;<em>Main Menu<\/em>&#8221; state where all action\u00a0happens.<\/p>\n<pre class=\"theme:xcode lang:default decode:true \">var TheGame = {\r\n};\r\n\r\nTheGame.Params = {\r\n\tbaseWidth: 1920,\r\n\tbaseHeight: 1080,\r\n\ticonSize: 364\r\n};\r\n\r\nTheGame.Boot = function (game) { };\r\n\r\nTheGame.Boot.prototype =  {\r\n    init: function () {\r\n        this.scale.scaleMode = Phaser.ScaleManager.RESIZE;\r\n    },\r\n    preload: function () {\r\n        this.load.image(\"loading\", \"loading.png\");\r\n    },\r\n    create: function () {\r\n        this.state.start(\"Loading\");\r\n    }\t\r\n};\r\n\r\nTheGame.Loading = function (game) {\r\n};\r\n\r\nTheGame.Loading.prototype = {\r\n    init: function () {\r\n    },\r\n    preload: function () {\r\n        this.stage.backgroundColor = 0x222222;\r\n        var loadingBar = this.add.sprite(this.world.centerX, this.world.centerY, \"loading\");\r\n        loadingBar.anchor.setTo(0.5);\r\n        this.load.setPreloadSprite(loadingBar);\r\n\r\n        this.load.image(\"gametitle\", \"gametitle.png\");\r\n        this.load.spritesheet(\"settings\", \"settings.png\", 364, 364);\r\n\tthis.load.image(\"background\", \"background.png\");\r\n    },\r\n    create: function () {\r\n       this.state.start(\"GameTitle\");\r\n    }\r\n};\r\n\r\nTheGame.MainMenu = function (game) {\r\n};\r\n\r\nTheGame.MainMenu.prototype = {\r\n    create: function () {\r\n        this.background = this.add.image(0, 0, \"background\");\r\n        this.background.height = this.game.height;\r\n        this.background.width = this.game.width;\r\n\t\t\r\n        this.title = this.game.add.image(this.world.centerX, this.world.centerY - this.game.height \/ 3, \"gametitle\");\r\n        this.title.anchor.setTo(0.5);\r\n        this.scaleSprite(this.title, this.game.width, this.game.height \/ 3, 50, 1);\r\n\t\t\t\r\n        this.playButton = this.game.add.button(this.world.centerX, this.world.centerY, \"settings\", this.playTheGame, this);\r\n        this.playButton.anchor.setTo(0.5);\r\n        this.playButton.frame = 0;\r\n        this.playButton.clicked = false;\r\n        this.scaleSprite(this.playButton, this.game.width, this.game.height \/ 3, 50, 1);\r\n\r\n        this.infoButton = this.game.add.button(this.world.centerX - TheGame.Params.iconSize \/ 2 , this.world.centerY + this.game.height \/ 3, \"settings\", this.viewGameHelp, this);\r\n        this.infoButton.anchor.setTo(0.5);\r\n        this.infoButton.frame = 4;\r\n        this.infoButton.clicked = false;\r\n        this.scaleSprite(this.infoButton, this.game.width, this.game.height \/ 3, 50, 0.5);\r\n        this.infoButton.x = this.world.centerX - this.infoButton.width \/ 2;\r\n\t\t\r\n        this.audioButton = this.game.add.button(this.world.centerX + TheGame.Params.iconSize \/ 2 , this.world.centerY + this.game.height \/ 3, \"settings\", this.setAudio, this);\r\n        this.audioButton.anchor.setTo(0.5);\r\n        this.audioButton.frame = 2;\r\n        this.audioButton.clicked = false;\r\n        this.scaleSprite(this.audioButton, this.game.width, this.game.height \/ 3, 50, 0.5);\r\n        this.audioButton.x = this.world.centerX + this.infoButton.width \/ 2;\r\n       },\r\n\tscaleSprite: function (sprite, availableSpaceWidth, availableSpaceHeight, padding, scaleMultiplier) {\r\n\t\tvar scale = this.getSpriteScale(sprite._frame.width, sprite._frame.height, availableSpaceWidth, availableSpaceHeight, padding);\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) {\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} \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\r\n\t\tthis.scaleSprite(this.title, width, height \/ 3, 50, 1);\r\n\t\tthis.title.x = this.world.centerX;\r\n\t\tthis.title.y = this.world.centerY - height \/ 3;\r\n\r\n\t\tthis.scaleSprite(this.playButton, width, height \/ 3, 50, 1);\r\n\t\tthis.playButton.x = this.world.centerX;\r\n\t\tthis.playButton.y = this.world.centerY ;\r\n\r\n\t\tthis.scaleSprite(this.infoButton, width, height \/ 3, 50, 0.5);\r\n\t\tthis.infoButton.x = this.world.centerX - this.infoButton.width \/ 2;\r\n\t\tthis.infoButton.y = this.world.centerY + height \/ 3;\r\n\r\n\t\tthis.scaleSprite(this.audioButton, width, height \/ 3, 50, 0.5);\r\n\t\tthis.audioButton.x = this.world.centerX + this.audioButton.width \/ 2;\r\n\t\tthis.audioButton.y = this.world.centerY + height \/ 3;\r\n\r\n\t},\r\n    playTheGame: function (button) {\r\n        if (!button.clicked) {\r\n            button.clicked = true;\r\n        }\r\n    },\r\n    viewGameHelp: function (button) {\r\n        if (!button.clicked) {\r\n            button.clicked = true;\r\n        }\r\n    },\r\n    setAudio: function (button) {\r\n        if (!button.clicked) {\r\n            button.clicked = true;\r\n        }\r\n    }\r\n};\r\n\r\nvar mygame;\r\nwindow.onload = function () {\r\n\tmygame = new Phaser.Game(TheGame.Params.baseWidth, TheGame.Params.height, Phaser.AUTO);\t\r\n\tmygame.state.add(\"Boot\", TheGame.Boot);\r\n\tmygame.state.add(\"Loading\", TheGame.Loading);\r\n\tmygame.state.add(\"GameTitle\", TheGame.MainMenu);\r\n\tmygame.state.start(\"Boot\");\r\n}\r\n<\/pre>\n<p>The\u00a0results from the code above for Portrait and Landscape orientations are shown below. You can\u00a0check demo on your device <a href=\"https:\/\/www.netexl.com\/blog\/demo\/1\/index.html\" target=\"_blank\">here<\/a>.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-74\" src=\"https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2017\/03\/ResponsiveGameTitle2-178x300.png\" alt=\"Making of a responsive game in Phaser\" width=\"178\" height=\"300\" srcset=\"https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2017\/03\/ResponsiveGameTitle2-178x300.png 178w, https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2017\/03\/ResponsiveGameTitle2-119x200.png 119w, https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2017\/03\/ResponsiveGameTitle2.png 367w\" sizes=\"auto, (max-width: 178px) 100vw, 178px\" \/><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-73\" src=\"https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2017\/03\/ResponsiveGameTitle1-300x145.png\" alt=\"Making of a responsive game in Phaser\" width=\"300\" height=\"145\" srcset=\"https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2017\/03\/ResponsiveGameTitle1-300x145.png 300w, https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2017\/03\/ResponsiveGameTitle1-280x136.png 280w, https:\/\/www.netexl.com\/blog\/wp-content\/uploads\/2017\/03\/ResponsiveGameTitle1.png 681w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/p>\n<p>Now let us analyze important pieces in the code.<\/p>\n<h3>Background<\/h3>\n<p>First thing we do in the &#8220;<em>Main Menu<\/em>&#8221; state is to set a background. It&#8217;s up to you to choose an optimized background. The important thing to note here is that after adding background at the\u00a0top left corner we set width and height of the background to the game&#8217;s width and height. This ensures that we do not have any blank space in the game if it is opened in a resolution higher than the base resolution we chose for the game. Equally important is to select a background which can easily blend with other elements on the screen without getting distorted. If we choose to use\u00a0a background which will get distorted when stretched or squeezed then we must have multiple background images and should load the one best fit according to the device resolution. Here we have simplified that work by simply choosing a background which will look fine even when stretched or squeezed.<\/p>\n<pre class=\"lang:default decode:true\">this.background = this.add.image(0, 0, \"background\");\r\nthis.background.height = this.game.height;\r\nthis.background.width = this.game.width;<\/pre>\n<h3>Scaling of assets<\/h3>\n<p>Remember how we divided the total available space and allocated to individual elements.\u00a0We planned all assets&#8217; sizes in proportion to the base game resolution. We don&#8217;t want to apply same scale to all assets since on mobile devices we want to keep the elements as big as it can fit into the allocated space so for different aspect ratios individual elements may take different space and look as big as they can to the user. We are going simply add elements\u00a0to the game and then scale it accordingly. Let us look at <em>getSpriteScale<\/em> method which does pretty much everything we need to do. It takes original element sizes and available space to fit that element in and then calculates a scale. We look at both height and width of the element\u00a0to see which part is exceeding the available space, take that scale and apply to both width and height to scale entire element in proportion. If element can fit in the space available then there is no need to scale. Just use\u00a0the element in its original size and put it\u00a0in its space.<\/p>\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<p>There is a <em>devicePixelRatio<\/em> which comes into the picture for mobile devices. We will take a look at that in a separate article. I will just summarize the need to use it here. Mobile devices actually have more pixels than the logical resolution we define in our code so if we are coding for 200px, a device with devicePixelRatio of 2 would display it using 400px. For pc devicePixelRatio is 1 so desktops are fine with using\u00a0logical pixels. Mobile devices having devicePixelRatio greater than 1 automatically convert\u00a0logical value accordingly to the devicePixelRatio. We should consider this value in calculation of the scale so that we have maximum utilization of the available space in terms of pixels.<\/p>\n<pre class=\"lang:default decode:true\">\tgetSpriteScale: function (spriteWidth, spriteHeight, availableSpaceWidth, availableSpaceHeight, minPadding) {\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} \r\n\t\treturn ratio * currentDevicePixelRatio;\t\r\n\t},<\/pre>\n<h3>Resizing<\/h3>\n<p>Another important consideration is how the game should be scaled when resized or when the orientation is changed on mobile devices. Look at the code below. <strong><em>RESIZE<\/em> <\/strong>option in Phaser has a special method which gets called every time the parent container changes in size. This method gets new width and height available for the game. All we do here is to calculate new scale values for our assets and position those assets to their new (x, y) coordinates.<\/p>\n<pre class=\"lang:default decode:true\">\tresize: function (width, height) {\r\n\t\tthis.background.height = height;\r\n\t\tthis.background.width = width;\r\n\r\n\t\tthis.scaleSprite(this.title, width, height \/ 3, 50, 1);\r\n\t\tthis.title.x = this.world.centerX;\r\n\t\tthis.title.y = this.world.centerY - height \/ 3;\r\n\r\n\t\tthis.scaleSprite(this.playButton, width, height \/ 3, 50, 1);\r\n\t\tthis.playButton.x = this.world.centerX;\r\n\t\tthis.playButton.y = this.world.centerY ;\r\n\r\n\t\tthis.scaleSprite(this.infoButton, width, height \/ 3, 50, 0.5);\r\n\t\tthis.infoButton.x = this.world.centerX - this.infoButton.width \/ 2;\r\n\t\tthis.infoButton.y = this.world.centerY + height \/ 3;\r\n\r\n\t\tthis.scaleSprite(this.audioButton, width, height \/ 3, 50, 0.5);\r\n\t\tthis.audioButton.x = this.world.centerX + this.audioButton.width \/ 2;\r\n\t\tthis.audioButton.y = this.world.centerY + height \/ 3;\r\n\r\n\t},<\/pre>\n<p>You need to take a note of \u00a0how we are calculating values from the game center using game world&#8217;s centerX and centerY properties. For &#8220;Main Menu&#8221; screen there are not many elements to manage and I think it is much safer to start from the center and then calculate position of individual\u00a0elements\u00a0relative to that. You can of course start the calculation from top left corner whichever is easier for you to visualize your game.<\/p>\n<h3>Next<\/h3>\n<p>For &#8220;Main Menu&#8221; screen we chose the same layout for all orientations. In next article we will be looking at the actual game screen and will use different layouts for different orientations.<\/p>\n<p><a href=\"https:\/\/www.netexl.com\/blog\/making-of-a-responsive-game-in-phaser-part-3\/\">Making of a responsive game in Phaser: Part\u00a03<\/a><\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In part one\u00a0of this series we did\u00a0a &#8220;Main Menu&#8221;\u00a0screen design for our game which can be read\u00a0here. Now its time to dive into the code. In order to reach to the &#8220;Main Menu&#8221; screen, we are going to use 3 states. The first state will be &#8220;Boot&#8221; state which is going to set the scale mode to RESIZE. Its also going to load an image which will be displayed while rest of the assets are loaded in the next state\u00a0which will be\u00a0called &#8220;Loading&#8221; state. Once all assets are loaded in the &#8220;Loading&#8221; state, we move to &#8220;Main Menu&#8221; state where all action\u00a0happens. var TheGame = { }; TheGame.Params = { baseWidth: 1920, baseHeight: 1080, iconSize: 364 }; TheGame.Boot = function (game) { }; TheGame.Boot.prototype = { init: function () { this.scale.scaleMode = Phaser.ScaleManager.RESIZE; }, preload: function () { this.load.image(&#8220;loading&#8221;, &#8220;loading.png&#8221;); }, create: function () { this.state.start(&#8220;Loading&#8221;); } }; TheGame.Loading =[&#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-61","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\/61","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=61"}],"version-history":[{"count":5,"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/posts\/61\/revisions"}],"predecessor-version":[{"id":1167,"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/posts\/61\/revisions\/1167"}],"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=61"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/categories?post=61"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/tags?post=61"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}