{"id":1648,"date":"2019-03-02T04:25:18","date_gmt":"2019-03-02T04:25:18","guid":{"rendered":"https:\/\/blog.hassler.ec\/wp\/?p=1648"},"modified":"2019-02-28T03:34:37","modified_gmt":"2019-02-28T03:34:37","slug":"how-to-build-a-simple-game-in-the-browser-with-phaser-3-and-typescript","status":"publish","type":"post","link":"https:\/\/blog.hassler.ec\/wp\/2019\/03\/02\/how-to-build-a-simple-game-in-the-browser-with-phaser-3-and-typescript\/","title":{"rendered":"How to build a simple game in the browser with Phaser 3 and TypeScript"},"content":{"rendered":"<section class=\"section section--body section--first\">\n<div class=\"section-content\">\n<div class=\"section-inner sectionLayout--insetColumn\">\n<h1 id=\"397e\" class=\"graf graf--h3 graf--leading graf--title\"><img decoding=\"async\" class=\"progressiveMedia-image js-progressiveMedia-image aligncenter\" style=\"font-size: 14px;\" src=\"https:\/\/cdn-images-1.medium.com\/max\/800\/1*m16cMnrn60vR49N8Sj1liA.jpeg\" data-src=\"https:\/\/cdn-images-1.medium.com\/max\/800\/1*m16cMnrn60vR49N8Sj1liA.jpeg\"><\/h1>\n<figure id=\"94e2\" class=\"graf graf--figure graf-after--h3\"><figcaption class=\"imageCaption\">Photo by&nbsp;<a class=\"markup--anchor markup--figure-anchor\" href=\"https:\/\/unsplash.com\/photos\/a0TJ3hy-UD8?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText\" target=\"_blank\" rel=\"noopener\" data-href=\"https:\/\/unsplash.com\/photos\/a0TJ3hy-UD8?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText\">Phil Botha<\/a>&nbsp;on&nbsp;<a class=\"markup--anchor markup--figure-anchor\" href=\"https:\/\/unsplash.com\/collections\/3995048\/stars\/e08862541511fcb17f0de3d4a555bff8?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText\" target=\"_blank\" rel=\"noopener\" data-href=\"https:\/\/unsplash.com\/collections\/3995048\/stars\/e08862541511fcb17f0de3d4a555bff8?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText\">Unsplash<\/a><\/figcaption><\/figure>\n<p id=\"d407\" class=\"graf graf--p graf-after--figure\">I\u2019m a developer advocate and a backend developer, and my frontend development expertise is relatively weak. A while ago I wanted to have some fun and make a game in a browser; I chose Phaser 3 as a framework (it looks quite popular these days) and TypeScript as a language (because I prefer static typing over dynamic). It turned out that you need to do some boring stuff to make it all work, so I wrote this tutorial to help the other people like me get started faster.<\/p>\n<h3 id=\"b494\" class=\"graf graf--h3 graf-after--p\">Preparing the environment<\/h3>\n<h4 id=\"b76e\" class=\"graf graf--h4 graf-after--h3\">IDE<\/h4>\n<p id=\"9d82\" class=\"graf graf--p graf-after--h4\">Choose your development environment. You can always use plain old Notepad if you wish, but I would suggest using something more helpful. As for me, I prefer developing pet projects in Emacs, therefore I have installed&nbsp;<a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/github.com\/ananthakumaran\/tide\" target=\"_blank\" rel=\"noopener\" data-href=\"https:\/\/github.com\/ananthakumaran\/tide\">tide<\/a>and followed the instructions to set it up.<\/p>\n<h4 id=\"3e76\" class=\"graf graf--h4 graf-after--p\">Node<\/h4>\n<p id=\"1071\" class=\"graf graf--p graf-after--h4\">If we were developing on JavaScript, we would be perfectly fine to start coding without all these preparation steps. However, as we want to use TypeScript, we have to set up the infrastructure to make the future development as fast as possible. Thus we need to install node and npm.<\/p>\n<p id=\"ba98\" class=\"graf graf--p graf-after--p\">As I write this tutorial, I use&nbsp;<a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/nodejs.org\/en\/\" target=\"_blank\" rel=\"noopener\" data-href=\"https:\/\/nodejs.org\/en\/\">node 10.13.0<\/a>&nbsp;and&nbsp;<a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/www.npmjs.com\/\" target=\"_blank\" rel=\"noopener\" data-href=\"https:\/\/www.npmjs.com\/\">npm 6.4.1<\/a>. Please note that the versions in the frontend world update extremely fast, so you simply take the latest stable versions. I strongly recommend using&nbsp;<a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/github.com\/creationix\/nvm\" target=\"_blank\" rel=\"noopener\" data-href=\"https:\/\/github.com\/creationix\/nvm\">nvm<\/a>&nbsp;instead of installing node and npm manually; it will save you a lot of time and nerves.<\/p>\n<h3 id=\"bd0d\" class=\"graf graf--h3 graf-after--p\">Setting up the&nbsp;project<\/h3>\n<h4 id=\"b4c9\" class=\"graf graf--h4 graf-after--h3\">Project structure<\/h4>\n<p id=\"c044\" class=\"graf graf--p graf-after--h4\">We will use npm for building the project, so to start the project go to an empty folder and run&nbsp;<code class=\"markup--code markup--p-code\">npm init<\/code>. npm will ask you several questions about your project properties and then create a&nbsp;<code class=\"markup--code markup--p-code\">package.json<\/code>&nbsp;file. It will look something like this:<\/p>\n<pre id=\"697e\" class=\"graf graf--pre graf-after--p\">{\n  \"name\": \"Starfall\",\n  \"version\": \"0.1.0\",\n  \"description\": \"Starfall game (Phaser 3 + TypeScript)\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" &amp;&amp; exit 1\"\n  },\n  \"author\": \"Mariya Davydova\",\n  \"license\": \"MIT\"\n}<\/pre>\n<h4 id=\"4b03\" class=\"graf graf--h4 graf-after--pre\">Packages<\/h4>\n<p id=\"a0e4\" class=\"graf graf--p graf-after--h4\">Install the packages we need with the following command:<\/p>\n<p id=\"c77c\" class=\"graf graf--p graf-after--p\"><code class=\"markup--code markup--p-code\">npm install -D typescript webpack webpack-cli ts-loader phaser live-server<\/code><\/p>\n<p id=\"c5dd\" class=\"graf graf--p graf-after--p\"><code class=\"markup--code markup--p-code\">-D<\/code>&nbsp;option (a.k.a.&nbsp;<code class=\"markup--code markup--p-code\">--save-dev<\/code>) makes npm add these packages to the list of dependencies in&nbsp;<code class=\"markup--code markup--p-code\">package.json<\/code>&nbsp;automatically:<\/p>\n<pre id=\"a9b1\" class=\"graf graf--pre graf-after--p\">\"devDependencies\": {\n   \"live-server\": \"^1.2.1\",\n   \"phaser\": \"^3.15.1\",\n   \"ts-loader\": \"^5.3.0\",\n   \"typescript\": \"^3.1.6\",\n   \"webpack\": \"^4.26.0\",\n   \"webpack-cli\": \"^3.1.2\"\n }<\/pre>\n<h4 id=\"0d45\" class=\"graf graf--h4 graf-after--pre\">Webpack<\/h4>\n<p id=\"a8fc\" class=\"graf graf--p graf-after--h4\">Webpack will run the TypeScript compiler and collect the bunch of resulting JS files as well as libraries into one minified JS so that we can include it in our page.<\/p>\n<p id=\"c399\" class=\"graf graf--p graf-after--p\">Add&nbsp;<code class=\"markup--code markup--p-code\">webpack.config.js<\/code>&nbsp;near your&nbsp;<code class=\"markup--code markup--p-code\">project.json<\/code>:<\/p>\n<pre id=\"032f\" class=\"graf graf--pre graf-after--p\">const path = require('path');<\/pre>\n<pre id=\"b559\" class=\"graf graf--pre graf-after--pre\">module.exports = {\n  entry: '.\/src\/app.ts',\n  module: {\n    rules: [\n      {\n        test: \/\\.tsx?$\/,\n        use: 'ts-loader',\n        exclude: \/node_modules\/\n      }\n    ]\n  },\n  resolve: {\n    extensions: [ '.ts', '.tsx', '.js' ]\n  },\n  output: {\n    filename: 'app.js',\n    path: path.resolve(__dirname, 'dist')\n  },\n  mode: 'development'\n};<\/pre>\n<p id=\"f46d\" class=\"graf graf--p graf-after--pre\">Here we see that webpack has to get the sources starting from&nbsp;<code class=\"markup--code markup--p-code\">src\/app.ts<\/code>(which we\u2019ll add very soon) and collect everything in&nbsp;<code class=\"markup--code markup--p-code\">dist\/app.js<\/code>&nbsp;file.<\/p>\n<h4 id=\"7fc7\" class=\"graf graf--h4 graf-after--p\">TypeScript<\/h4>\n<p id=\"d17c\" class=\"graf graf--p graf-after--h4\">We also need a small configuration file for the TypeScript compiler (<code class=\"markup--code markup--p-code\">tsconfig.json<\/code>) where we explain which JS version we want the sources to be compiled to and where to find those sources:<\/p>\n<pre id=\"e42e\" class=\"graf graf--pre graf-after--p\">{\n  \"compilerOptions\": {\n    \"target\": \"es5\"\n  },\n  \"include\": [\n    \"src\/*\"\n  ]\n}<\/pre>\n<h4 id=\"02b9\" class=\"graf graf--h4 graf-after--pre\">TypeScript definitions<\/h4>\n<p id=\"7271\" class=\"graf graf--p graf-after--h4\">TypeScript is a statically typed language. Therefore, it requires type definitions for the compilation. At the time of writing this tutorial, the definitions for Phaser 3 were not yet available as the npm package, so you may need to&nbsp;<a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/github.com\/photonstorm\/phaser3-docs\/blob\/master\/typescript\/phaser.d.ts\" target=\"_blank\" rel=\"noopener\" data-href=\"https:\/\/github.com\/photonstorm\/phaser3-docs\/blob\/master\/typescript\/phaser.d.ts\">download them<\/a>&nbsp;from the official repository and put the file in the&nbsp;<code class=\"markup--code markup--p-code\">src<\/code>subdirectory of your project.<\/p>\n<h4 id=\"55a4\" class=\"graf graf--h4 graf-after--p\">Scripts<\/h4>\n<p id=\"3236\" class=\"graf graf--p graf-after--h4\">We have almost finished the project set up. At this moment you should have created&nbsp;<code class=\"markup--code markup--p-code\">package.json<\/code>,&nbsp;<code class=\"markup--code markup--p-code\">webpack.config.js<\/code>, and&nbsp;<code class=\"markup--code markup--p-code\">tsconfig.json<\/code>, and added&nbsp;<code class=\"markup--code markup--p-code\">src\/phaser.d.ts<\/code>. The last thing we need to do before starting to write code is to explain what exactly npm has to do with the project. We update the&nbsp;<code class=\"markup--code markup--p-code\">scripts<\/code>&nbsp;section of the&nbsp;<code class=\"markup--code markup--p-code\">package.json<\/code>&nbsp;as follows:<\/p>\n<pre id=\"c90e\" class=\"graf graf--pre graf-after--p\">\"scripts\": {\n<strong class=\"markup--strong markup--pre-strong\">  \"build\": \"webpack\",\n  \"start\": \"webpack --watch &amp; live-server --port=8085\"\n<\/strong>}<\/pre>\n<p id=\"c116\" class=\"graf graf--p graf-after--pre\">When you execute&nbsp;<code class=\"markup--code markup--p-code\">npm build<\/code>, the&nbsp;<code class=\"markup--code markup--p-code\">app.js<\/code>&nbsp;file will be built according to the webpack configuration. And when you run&nbsp;<code class=\"markup--code markup--p-code\">npm start<\/code>, you won\u2019t have to bother about the build process: as soon as you save any source, webpack will rebuild the app and the&nbsp;<a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/www.npmjs.com\/package\/live-server\" target=\"_blank\" rel=\"noopener\" data-href=\"https:\/\/www.npmjs.com\/package\/live-server\">live-server<\/a>&nbsp;will reload it in your default browser. The app will be hosted at&nbsp;<a class=\"markup--anchor markup--p-anchor\" href=\"http:\/\/127.0.0.1:8085\/\" target=\"_blank\" rel=\"noopener\" data-href=\"http:\/\/127.0.0.1:8085\/\">http:\/\/127.0.0.1:8085\/<\/a>.<\/p>\n<h3 id=\"a617\" class=\"graf graf--h3 graf-after--p\">Getting started<\/h3>\n<p id=\"68cb\" class=\"graf graf--p graf-after--h3\">Now that we have set up the infrastructure (the part I personally hate when starting a project), we can finally start coding. In this step we\u2019ll do a straightforward thing: draw a dark blue rectangle in our browser window. Using a big game development framework for this is a little bit of\u2026 hmmm\u2026 overkill. Still, we\u2019ll need it on the next steps.<\/p>\n<p id=\"3791\" class=\"graf graf--p graf-after--p\">Let me briefly explain the main concepts of Phaser 3. The game is an instance of the&nbsp;<code class=\"markup--code markup--p-code\">Phaser.Game<\/code>&nbsp;class (or its descendant). Each game contains one or more instances of&nbsp;<code class=\"markup--code markup--p-code\">Phaser.Scene<\/code>&nbsp;descendants. Each scene contains several objects, either static or dynamic, and represents a logical part of the game. For example, our trivial game will have three scenes: the welcome screen, the game itself, and the score screen.<\/p>\n<p id=\"eabb\" class=\"graf graf--p graf-after--p\">Let\u2019s start coding.<\/p>\n<p id=\"0013\" class=\"graf graf--p graf-after--p\">First, create a minimalistic HTML container for the game. Make an&nbsp;<code class=\"markup--code markup--p-code\">index.html<\/code>file, which contains the following code:<\/p>\n<pre id=\"54cd\" class=\"graf graf--pre graf-after--p\">&lt;!DOCTYPE html&gt;\n&lt;html&gt;\n  &lt;head&gt;\n    &lt;title&gt;Starfall&lt;\/title&gt;\n    &lt;script src=\"dist\/app.js\"&gt;&lt;\/script&gt;\n  &lt;\/head&gt;\n  &lt;body&gt;\n    &lt;div id=\"game\"&gt;&lt;\/div&gt;\n  &lt;\/body&gt;\n&lt;\/html&gt;<\/pre>\n<p id=\"d9b3\" class=\"graf graf--p graf-after--pre\">There are only two essential parts here: the first one is a&nbsp;<code class=\"markup--code markup--p-code\">script<\/code>&nbsp;entry which says that we are going to use our built file here, and the second one is a&nbsp;<code class=\"markup--code markup--p-code\">div<\/code>entry which will be the game container.<\/p>\n<p id=\"4e42\" class=\"graf graf--p graf-after--p\">Now create a file&nbsp;<code class=\"markup--code markup--p-code\">src\/app.ts<\/code>&nbsp;with the following code:<\/p>\n<pre id=\"a0c7\" class=\"graf graf--pre graf-after--p\">import \"phaser\";<\/pre>\n<pre id=\"53d8\" class=\"graf graf--pre graf-after--pre\">const config: GameConfig = {\n  title: \"Starfall\",\n  width: 800,\n  height: 600,\n  parent: \"game\"\n  backgroundColor: \"#18216D\"\n};<\/pre>\n<pre id=\"418d\" class=\"graf graf--pre graf-after--pre\">export class StarfallGame extends Phaser.Game {\n  constructor(config: GameConfig) {\n    super(config);\n  }\n}<\/pre>\n<pre id=\"9665\" class=\"graf graf--pre graf-after--pre\">window.onload = () =&gt; {\n  var game = new StarfallGame(config);\n};<\/pre>\n<p id=\"4af8\" class=\"graf graf--p graf-after--pre\">This code is self-explanatory. GameConfig has a lot of various properties, you can check them out&nbsp;<a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/photonstorm.github.io\/phaser3-docs\/global.html#GameConfig\" target=\"_blank\" rel=\"noopener\" data-href=\"https:\/\/photonstorm.github.io\/phaser3-docs\/global.html#GameConfig\">here<\/a>&nbsp;.<\/p>\n<p id=\"ff88\" class=\"graf graf--p graf-after--p\">And now you can finally run&nbsp;<code class=\"markup--code markup--p-code\">npm start<\/code>. If everything was done correctly on this and previous steps, you should see something as simple as this in your browser:<\/p>\n<figure id=\"9682\" class=\"graf graf--figure graf-after--p\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\"><\/div>\n<div class=\"progressiveMedia js-progressiveMedia graf-image is-canvasLoaded is-imageLoaded\" data-image-id=\"1*1ecSa8Bs5zX6TQRq60qr6w.png\" data-width=\"960\" data-height=\"720\" data-action=\"zoom\" data-action-value=\"1*1ecSa8Bs5zX6TQRq60qr6w.png\" data-scroll=\"native\"><canvas class=\"progressiveMedia-canvas js-progressiveMedia-canvas\" width=\"75\" height=\"55\"><\/canvas><img decoding=\"async\" class=\"progressiveMedia-image js-progressiveMedia-image\" src=\"https:\/\/cdn-images-1.medium.com\/max\/800\/1*1ecSa8Bs5zX6TQRq60qr6w.png\" data-src=\"https:\/\/cdn-images-1.medium.com\/max\/800\/1*1ecSa8Bs5zX6TQRq60qr6w.png\"><\/div>\n<\/div><figcaption class=\"imageCaption\">Yes, this is a blue&nbsp;screen.<\/figcaption><\/figure>\n<h3 id=\"1d6c\" class=\"graf graf--h3 graf-after--figure\">Making the stars&nbsp;fall<\/h3>\n<p id=\"1ad4\" class=\"graf graf--p graf-after--h3\">We have created an elementary application. Now it\u2019s time to add a scene where something will happen. Our game will be simple: the stars will fall to the ground, and the goal will be to catch as many as possible.<\/p>\n<p id=\"8702\" class=\"graf graf--p graf-after--p\">To achieve this goal create a new file,&nbsp;<code class=\"markup--code markup--p-code\">gameScene.ts<\/code>, and add the following code:<\/p>\n<pre id=\"0191\" class=\"graf graf--pre graf-after--p\">import \"phaser\";<\/pre>\n<pre id=\"a55e\" class=\"graf graf--pre graf-after--pre\">export class GameScene extends Phaser.Scene {<\/pre>\n<pre id=\"193b\" class=\"graf graf--pre graf-after--pre\">constructor() {\n    super({\n      key: \"GameScene\"\n    });\n  }<\/pre>\n<pre id=\"2907\" class=\"graf graf--pre graf-after--pre\">init(params): void {\n    \/\/ TODO\n  }<\/pre>\n<pre id=\"4c87\" class=\"graf graf--pre graf-after--pre\">preload(): void {\n    \/\/ TODO\n  }\n  \n  create(): void {\n    \/\/ TODO\n  }<\/pre>\n<pre id=\"f5dc\" class=\"graf graf--pre graf-after--pre\">update(time): void {\n    \/\/ TODO\n  }\n};<\/pre>\n<p id=\"325b\" class=\"graf graf--p graf-after--pre\">Constructor here contains a key under which other scenes may call this scene.<\/p>\n<p id=\"ba42\" class=\"graf graf--p graf-after--p\">You see here stubs for four methods. Let me briefly explain the difference between then:<\/p>\n<ul class=\"postList\">\n<li id=\"1e4a\" class=\"graf graf--li graf-after--p\"><code class=\"markup--code markup--li-code\">init([params])<\/code>&nbsp;is called when the scene starts; this function may accept parameters, which are passed from other scenes or game by calling&nbsp;<code class=\"markup--code markup--li-code\">scene.start(key, [params])<\/code><\/li>\n<li id=\"b2d4\" class=\"graf graf--li graf-after--li\"><code class=\"markup--code markup--li-code\">preload()<\/code>&nbsp;is called before the scene objects are created, and it contains loading assets; these assets are cached, so when the scene is restarted, they are not reloaded<\/li>\n<li id=\"d360\" class=\"graf graf--li graf-after--li\"><code class=\"markup--code markup--li-code\">create()<\/code>&nbsp;is called when the assets are loaded and usually contains creation of the main game objects (background, player, obstacles, enemies, etc.)<\/li>\n<li id=\"e0d7\" class=\"graf graf--li graf-after--li\"><code class=\"markup--code markup--li-code\">update([time])<\/code>&nbsp;is called every tick and contains the dynamic part of the scene \u2014 everything that moves, flashes, etc.<\/li>\n<\/ul>\n<p id=\"2b9b\" class=\"graf graf--p graf-after--li\">To be sure that we don\u2019t forget it later, let\u2019s quickly add the following lines in the&nbsp;<code class=\"markup--code markup--p-code\">game.ts<\/code>:<\/p>\n<pre id=\"3851\" class=\"graf graf--pre graf-after--p\">import \"phaser\";\n<strong class=\"markup--strong markup--pre-strong\">import { GameScene } from \".\/gameScene\";<\/strong><\/pre>\n<pre id=\"66a8\" class=\"graf graf--pre graf-after--pre\">const config: GameConfig = {\n  title: \"Starfall\",\n  width: 800,\n  height: 600,\n  parent: \"game\",\n<strong class=\"markup--strong markup--pre-strong\">  scene: [GameScene],\n  physics: {\n    default: \"arcade\",\n    arcade: {\n      debug: false\n    }\n  },\n<\/strong>  backgroundColor: \"#000033\"\n};\n...<\/pre>\n<p id=\"1589\" class=\"graf graf--p graf-after--pre\">Our game now knows about the game scene. If the game config contains a list of scenes then the first one is started when the game is begun, and all others are created but not started until explicitly called.<\/p>\n<p id=\"c96a\" class=\"graf graf--p graf-after--p\">We have also added arcade physics here. It is required to make our stars fall.<\/p>\n<p id=\"77bc\" class=\"graf graf--p graf-after--p\">Now we can put flesh on the bones of our game scene.<\/p>\n<p id=\"6dc2\" class=\"graf graf--p graf-after--p\">First, we declare some properties and objects we\u2019re gonna need:<\/p>\n<pre id=\"fe03\" class=\"graf graf--pre graf-after--p\">export class GameScene extends Phaser.Scene {\n  delta: number;\n  lastStarTime: number;\n  starsCaught: number;\n  starsFallen: number;\n  sand: Phaser.Physics.Arcade.StaticGroup;\n  info: Phaser.GameObjects.Text;\n...<\/pre>\n<p id=\"ab85\" class=\"graf graf--p graf-after--pre\">Then, we initialize numbers:<\/p>\n<pre id=\"9190\" class=\"graf graf--pre graf-after--p\">init(\/*params: any*\/): void {\n    this.delta = 1000;\n    this.lastStarTime = 0;\n    this.starsCaught = 0;\n    this.starsFallen = 0;\n  }<\/pre>\n<p id=\"bd23\" class=\"graf graf--p graf-after--pre\">Now, we load a couple of images:<\/p>\n<pre id=\"1d62\" class=\"graf graf--pre graf-after--p\">preload(): void {\n    this.load.setBaseURL(\n      \"https:\/\/raw.githubusercontent.com\/mariyadavydova\/\" +\n      \"starfall-phaser3-typescript\/master\/\");\n    this.load.image(\"star\", \"assets\/star.png\");\n    this.load.image(\"sand\", \"assets\/sand.jpg\");\n  }<\/pre>\n<p id=\"e508\" class=\"graf graf--p graf-after--pre\">After that, we can prepare our static components. We will create the ground, where the stars will fall, and the text informing us about the current score:<\/p>\n<pre id=\"d16f\" class=\"graf graf--pre graf-after--p\">create(): void {\n    this.sand = this.physics.add.staticGroup({\n      key: 'sand',\n      frameQuantity: 20\n    });\n    Phaser.Actions.PlaceOnLine(this.sand.getChildren(),\n      new Phaser.Geom.Line(20, 580, 820, 580));\n    this.sand.refresh();<\/pre>\n<pre id=\"1318\" class=\"graf graf--pre graf-after--pre\">this.info = this.add.text(10, 10, '',\n      { font: '24px Arial Bold', fill: '#FBFBAC' });\n  }<\/pre>\n<p id=\"f1c3\" class=\"graf graf--p graf-after--pre\">A group in Phaser 3 is a way to create a bunch of the objects you want to control together. There two types of objects: static and dynamic. As you may guess, static objects don\u2019t move (ground, walls, various obstacles), while dynamic ones do the job (Mario, ships, missiles).<\/p>\n<p id=\"b6ff\" class=\"graf graf--p graf-after--p\">We create a static group of the ground pieces. Those pieces are placed along the line. Please note that the line is divided into 20 equal sections (not 19 as you\u2019ve may have expected), and the ground tiles are placed on each section at the left end with the tile center located at that point (I hope this explains those numbers). We also have to call&nbsp;<code class=\"markup--code markup--p-code\">refresh()<\/code>&nbsp;to update the group bounding box (otherwise, the collisions will be checked against the default location, which is the top left corner of the scene).<\/p>\n<p id=\"6f49\" class=\"graf graf--p graf-after--p\">If you check out your application in the browser now, you should see something like this:<\/p>\n<figure id=\"3790\" class=\"graf graf--figure graf-after--p\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\"><\/div>\n<div class=\"progressiveMedia js-progressiveMedia graf-image is-canvasLoaded is-imageLoaded\" data-image-id=\"1*GvOFAilcNMp0FnOTr_QqsA.png\" data-width=\"960\" data-height=\"720\" data-action=\"zoom\" data-action-value=\"1*GvOFAilcNMp0FnOTr_QqsA.png\" data-scroll=\"native\"><canvas class=\"progressiveMedia-canvas js-progressiveMedia-canvas\" width=\"75\" height=\"55\"><\/canvas><img decoding=\"async\" class=\"progressiveMedia-image js-progressiveMedia-image\" src=\"https:\/\/cdn-images-1.medium.com\/max\/800\/1*GvOFAilcNMp0FnOTr_QqsA.png\" data-src=\"https:\/\/cdn-images-1.medium.com\/max\/800\/1*GvOFAilcNMp0FnOTr_QqsA.png\"><\/div>\n<\/div><figcaption class=\"imageCaption\">Blue screen evolution<\/figcaption><\/figure>\n<p id=\"530e\" class=\"graf graf--p graf-after--figure\">We have finally reached the most dynamic part of this scene \u2014 <code class=\"markup--code markup--p-code\">update()<\/code>function, where the stars fall. This function is called somewhere around once in 60 ms. We want to emit a new falling star every second. We won\u2019t use a dynamic group for this, as the lifecycle of each star will be short: it will be destroyed either by user click or by colliding with the ground. Therefore inside the&nbsp;<code class=\"markup--code markup--p-code\">emitStar()<\/code>&nbsp;function we create a new star and add the processing of two events:&nbsp;<code class=\"markup--code markup--p-code\">onClick()<\/code>&nbsp;and&nbsp;<code class=\"markup--code markup--p-code\">onCollision()<\/code>.<\/p>\n<pre id=\"4636\" class=\"graf graf--pre graf-after--p\">update(time: number): void {\n    var diff: number = time - this.lastStarTime;\n    if (diff &gt; this.delta) {\n      this.lastStarTime = time;\n      if (this.delta &gt; 500) {\n        this.delta -= 20;\n      }\n      this.emitStar();\n    }\n    this.info.text =\n      this.starsCaught + \" caught - \" +\n      this.starsFallen + \" fallen (max 3)\";\n  }<\/pre>\n<pre id=\"80bd\" class=\"graf graf--pre graf-after--pre\">private onClick(star: Phaser.Physics.Arcade.Image): () =&gt; void {\n    return function () {\n      star.setTint(0x00ff00);\n      star.setVelocity(0, 0);\n      this.starsCaught += 1;\n      this.time.delayedCall(100, function (star) {\n        star.destroy();\n      }, [star], this);\n    }\n  }<\/pre>\n<pre id=\"3295\" class=\"graf graf--pre graf-after--pre\">private onFall(star: Phaser.Physics.Arcade.Image): () =&gt; void {\n    return function () {\n      star.setTint(0xff0000);\n      this.starsFallen += 1;\n      this.time.delayedCall(100, function (star) {\n        star.destroy();\n      }, [star], this);\n    }\n  }<\/pre>\n<pre id=\"64d3\" class=\"graf graf--pre graf-after--pre\">private emitStar(): void {\n    var star: Phaser.Physics.Arcade.Image;\n    var x = Phaser.Math.Between(25, 775);\n    var y = 26;\n    star = this.physics.add.image(x, y, \"star\");<\/pre>\n<pre id=\"167e\" class=\"graf graf--pre graf-after--pre\">star.setDisplaySize(50, 50);\n    star.setVelocity(0, 200);\n    star.setInteractive();<\/pre>\n<pre id=\"9a3f\" class=\"graf graf--pre graf-after--pre\">star.on('pointerdown', this.onClick(star), this);\n    this.physics.add.collider(star, this.sand, \n      this.onFall(star), null, this);\n  }<\/pre>\n<p id=\"751d\" class=\"graf graf--p graf-after--pre\">Finally, we have a game! It doesn\u2019t have a win condition yet. We\u2019ll add it in the last part of our tutorial.<\/p>\n<figure id=\"8293\" class=\"graf graf--figure graf-after--p\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\"><\/div>\n<div class=\"progressiveMedia js-progressiveMedia graf-image is-canvasLoaded is-imageLoaded\" data-image-id=\"1*tjX0ikNYl-UFJQnOkQIeOA.png\" data-width=\"960\" data-height=\"720\" data-action=\"zoom\" data-action-value=\"1*tjX0ikNYl-UFJQnOkQIeOA.png\" data-scroll=\"native\"><canvas class=\"progressiveMedia-canvas js-progressiveMedia-canvas\" width=\"75\" height=\"55\"><\/canvas><img decoding=\"async\" class=\"progressiveMedia-image js-progressiveMedia-image\" src=\"https:\/\/cdn-images-1.medium.com\/max\/800\/1*tjX0ikNYl-UFJQnOkQIeOA.png\" data-src=\"https:\/\/cdn-images-1.medium.com\/max\/800\/1*tjX0ikNYl-UFJQnOkQIeOA.png\"><\/div>\n<\/div><figcaption class=\"imageCaption\">I\u2019m bad at catching&nbsp;stars\u2026<\/figcaption><\/figure>\n<h3 id=\"3c57\" class=\"graf graf--h3 graf-after--figure\">Wrapping it all&nbsp;up<\/h3>\n<p id=\"9193\" class=\"graf graf--p graf-after--h3\">Usually, a game consists of several scenes. Even if the gameplay is simple, you need an opening scene (containing at the very least the \u2018Play!\u2019 button) and a closing one (showing the result of your game session, like the score or the maximum level reached). Let\u2019s add these scenes to our application.<\/p>\n<p id=\"dc88\" class=\"graf graf--p graf-after--p\">In our case, they will be pretty similar, as I don\u2019t want to pay too much attention to the graphic design of the game. After all, this a programming tutorial.<\/p>\n<p id=\"8bf9\" class=\"graf graf--p graf-after--p\">The welcome scene will have the following code in&nbsp;<code class=\"markup--code markup--p-code\">welcomeScene.ts<\/code>. Note that when a user clicks somewhere on this scene, a game scene will appear.<\/p>\n<pre id=\"ba8a\" class=\"graf graf--pre graf-after--p\">import \"phaser\";<\/pre>\n<pre id=\"2911\" class=\"graf graf--pre graf-after--pre\">export class WelcomeScene extends Phaser.Scene {\n  title: Phaser.GameObjects.Text;\n  hint: Phaser.GameObjects.Text;<\/pre>\n<pre id=\"9df4\" class=\"graf graf--pre graf-after--pre\">constructor() {\n    super({\n      key: \"WelcomeScene\"\n    });\n  }<\/pre>\n<pre id=\"2b9a\" class=\"graf graf--pre graf-after--pre\">create(): void {\n    var titleText: string = \"Starfall\";\n    this.title = this.add.text(150, 200, titleText,\n      { font: '128px Arial Bold', fill: '#FBFBAC' });<\/pre>\n<pre id=\"8a2f\" class=\"graf graf--pre graf-after--pre\">var hintText: string = \"Click to start\";\n    this.hint = this.add.text(300, 350, hintText,\n      { font: '24px Arial Bold', fill: '#FBFBAC' });<\/pre>\n<pre id=\"8371\" class=\"graf graf--pre graf-after--pre\">this.input.on('pointerdown', function (\/*pointer*\/) {\n      this.scene.start(\"GameScene\");\n    }, this);\n  }\n};<\/pre>\n<p id=\"ee90\" class=\"graf graf--p graf-after--pre\">The score scene will look almost the same, leading to the welcome scene on click (<code class=\"markup--code markup--p-code\">scoreScene.ts<\/code>).<\/p>\n<pre id=\"e000\" class=\"graf graf--pre graf-after--p\">import \"phaser\";<\/pre>\n<pre id=\"0665\" class=\"graf graf--pre graf-after--pre\">export class ScoreScene extends Phaser.Scene {\n  score: number;\n  result: Phaser.GameObjects.Text;\n  hint: Phaser.GameObjects.Text;<\/pre>\n<pre id=\"ded2\" class=\"graf graf--pre graf-after--pre\">constructor() {\n    super({\n      key: \"ScoreScene\"\n    });\n  }<\/pre>\n<pre id=\"978e\" class=\"graf graf--pre graf-after--pre\">init(params: any): void {\n    this.score = params.starsCaught;\n  }<\/pre>\n<pre id=\"ea27\" class=\"graf graf--pre graf-after--pre\">create(): void {\n    var resultText: string = 'Your score is ' + this.score + '!';\n    this.result = this.add.text(200, 250, resultText,\n      { font: '48px Arial Bold', fill: '#FBFBAC' });<\/pre>\n<pre id=\"51c5\" class=\"graf graf--pre graf-after--pre\">var hintText: string = \"Click to restart\";\n    this.hint = this.add.text(300, 350, hintText,\n      { font: '24px Arial Bold', fill: '#FBFBAC' });<\/pre>\n<pre id=\"d528\" class=\"graf graf--pre graf-after--pre\">this.input.on('pointerdown', function (\/*pointer*\/) {\n      this.scene.start(\"WelcomeScene\");\n    }, this);\n  }\n};<\/pre>\n<p id=\"fdf5\" class=\"graf graf--p graf-after--pre\">We need to update our main application file now: add these scenes and make the&nbsp;<code class=\"markup--code markup--p-code\">WelcomeScene<\/code>&nbsp;to be the first in the list:<\/p>\n<pre id=\"ef63\" class=\"graf graf--pre graf-after--p\">import \"phaser\";\n<strong class=\"markup--strong markup--pre-strong\">import { WelcomeScene } from \".\/welcomeScene\";\n<\/strong>import { GameScene } from \".\/gameScene\";\n<strong class=\"markup--strong markup--pre-strong\">import { ScoreScene } from \".\/scoreScene\";<\/strong><\/pre>\n<pre id=\"fca1\" class=\"graf graf--pre graf-after--pre\">const config: GameConfig = {\n  ...\n<strong class=\"markup--strong markup--pre-strong\">  scene: [WelcomeScene, GameScene, ScoreScene],\n<\/strong>  ...<\/pre>\n<p id=\"6ace\" class=\"graf graf--p graf-after--pre\">Have you noticed what is missing? Right, we do not call the&nbsp;<code class=\"markup--code markup--p-code\">ScoreScene<\/code>&nbsp;from anywhere yet! Let\u2019s call it when the player has missed the third star:<\/p>\n<pre id=\"3b79\" class=\"graf graf--pre graf-after--p\">private onFall(star: Phaser.Physics.Arcade.Image): () =&gt; void {\n    return function () {\n      star.setTint(0xff0000);\n      this.starsFallen += 1;\n      this.time.delayedCall(100, function (star) {\n        star.destroy();\n<strong class=\"markup--strong markup--pre-strong\">        if (this.starsFallen &gt; 2) {\n          this.scene.start(\"ScoreScene\", \n            { starsCaught: this.starsCaught });\n        }\n<\/strong>      }, [star], this);\n    }\n  }<\/pre>\n<p id=\"1dc2\" class=\"graf graf--p graf-after--pre graf--trailing\">Finally, our Starfall game looks like a real game \u2014 it starts, ends, and even has a goal to archive (how many stars can you catch?).<\/p>\n<\/div>\n<\/div>\n<\/section>\n<section class=\"section section--body section--last\">\n<div class=\"section-divider\">\n<hr class=\"section-divider\">\n<\/div>\n<div class=\"section-content\">\n<div class=\"section-inner sectionLayout--insetColumn\">\n<p id=\"2fc0\" class=\"graf graf--p graf--leading\">I hope this tutorial is as useful for you as it was for me when I wrote it&nbsp;\ud83d\ude42 Any feedback is highly appreciated!<\/p>\n<h4 id=\"0375\" class=\"graf graf--p graf-after--p graf--trailing\">The source code for this tutorial may be found&nbsp;<a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/github.com\/mariyadavydova\/starfall-phaser3-typescript\" target=\"_blank\" rel=\"noopener\" data-href=\"https:\/\/github.com\/mariyadavydova\/starfall-phaser3-typescript\">here<\/a>.<\/h4>\n<\/div>\n<p><a href=\"https:\/\/github.com\/mariyadavydova\/starfall-phaser3-typescript\"><strong>https:\/\/github.com\/mariyadavydova\/starfall-phaser3-typescript<\/strong><\/a><\/p>\n<\/div>\n<p>&nbsp;<\/p>\n<p>Source:&nbsp;<a href=\"https:\/\/medium.freecodecamp.org\/how-to-build-a-simple-game-in-the-browser-with-phaser-3-and-typescript-bdc94719135\"><strong>https:\/\/medium.freecodecamp.org\/how-to-build-a-simple-game-in-the-browser-with-phaser-3-and-typescript-bdc94719135<\/strong><\/a><\/p>\n<p>Written by<\/p>\n<p>&nbsp;<\/p>\n<div class=\"u-tableCell\">\n<div class=\"u-relative u-inlineBlock u-flex0\"><img decoding=\"async\" class=\"avatar-image avatar-image--small alignleft\" src=\"https:\/\/cdn-images-1.medium.com\/fit\/c\/60\/60\/0*ZVn8ab2XXbDdb6nc.\" alt=\"Go to the profile of Mariya Davydova\"><\/p>\n<div class=\"avatar-halo u-absolute u-textColorGreenNormal svgIcon\"><a class=\"link link--primary u-accentColor--hoverTextNormal\" dir=\"auto\" style=\"font-family: inherit; font-size: 24.5px; font-weight: bold;\" title=\"Go to the profile of Mariya Davydova\" href=\"https:\/\/medium.freecodecamp.org\/@mariyadavydova\" rel=\"author cc:attributionUrl\" aria-label=\"Go to the profile of Mariya Davydova\" data-user-id=\"910806bad728\" data-collection-slug=\"free-code-camp\">Mariya Davydova<\/a><\/div>\n<\/div>\n<\/div>\n<div class=\"u-tableCell u-verticalAlignMiddle u-breakWord u-paddingLeft15\">\n<div class=\"ui-caption u-textColorGreenNormal u-fontSize13 u-tintSpectrum u-accentColor--textNormal u-marginBottom7\">Medium member since Feb 2018<\/div>\n<p class=\"ui-body u-fontSize14 u-lineHeightBaseSans u-textColorDark u-marginBottom4\">Developer advocate&nbsp;<a title=\"Twitter profile for @JetBrains\" href=\"http:\/\/twitter.com\/JetBrains\" target=\"_blank\" rel=\"noopener\">@JetBrains<\/a>, needlewoman, blogger, mom<\/p>\n<\/div>\n<\/section>\n<p>&nbsp;<\/p>\n<div class=\"u-tableCell \"><a class=\"link u-baseColor--link avatar avatar--roundedRectangle\" title=\"Go to freeCodeCamp.org\" href=\"https:\/\/medium.freecodecamp.org\/?source=footer_card\" aria-label=\"Go to freeCodeCamp.org\" data-action-source=\"footer_card\" data-collection-slug=\"free-code-camp\"><img decoding=\"async\" class=\"avatar-image u-size60x60 alignleft\" src=\"https:\/\/cdn-images-1.medium.com\/fit\/c\/60\/60\/1*MotlWcSa2n6FrOx3ul89kw.png\" alt=\"freeCodeCamp.org\"><\/a><\/div>\n<div class=\"u-tableCell u-verticalAlignMiddle u-breakWord u-paddingLeft15\">\n<h3 class=\"ui-h3 u-fontSize18 u-lineHeightTighter u-marginBottom4\"><a class=\"link link--primary u-accentColor--hoverTextNormal\" href=\"https:\/\/medium.freecodecamp.org\/?source=footer_card\" rel=\"collection\" data-action-source=\"footer_card\" data-collection-slug=\"free-code-camp\">freeCodeCamp.org<\/a><\/h3>\n<p class=\"ui-body u-fontSize14 u-lineHeightBaseSans u-textColorDark u-marginBottom4\">Stories worth reading about programming and technology from our open source community.<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Photo by&nbsp;Phil Botha&nbsp;on&nbsp;Unsplash I\u2019m a developer advocate and a backend developer, and my frontend development expertise is relatively weak. A [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":1650,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"categories":[12,88,44,47,29,115],"tags":[],"class_list":["post-1648","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-bloghassler-ec","category-design","category-javascript","category-medium","category-programacion","category-progresisive-web-apps"],"_links":{"self":[{"href":"https:\/\/blog.hassler.ec\/wp\/wp-json\/wp\/v2\/posts\/1648","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.hassler.ec\/wp\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.hassler.ec\/wp\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.hassler.ec\/wp\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.hassler.ec\/wp\/wp-json\/wp\/v2\/comments?post=1648"}],"version-history":[{"count":2,"href":"https:\/\/blog.hassler.ec\/wp\/wp-json\/wp\/v2\/posts\/1648\/revisions"}],"predecessor-version":[{"id":1652,"href":"https:\/\/blog.hassler.ec\/wp\/wp-json\/wp\/v2\/posts\/1648\/revisions\/1652"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.hassler.ec\/wp\/wp-json\/wp\/v2\/media\/1650"}],"wp:attachment":[{"href":"https:\/\/blog.hassler.ec\/wp\/wp-json\/wp\/v2\/media?parent=1648"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.hassler.ec\/wp\/wp-json\/wp\/v2\/categories?post=1648"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.hassler.ec\/wp\/wp-json\/wp\/v2\/tags?post=1648"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}