1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
|
---
title: 2D maze game with device orientation
slug: Games/Tutorials/HTML5_Gamedev_Phaser_Device_Orientation
translation_of: Games/Tutorials/HTML5_Gamedev_Phaser_Device_Orientation
---
<div>{{GamesSidebar}}</div>
<div>{{IncludeSubnav("/en-US/docs/Games")}}</div>
<div class="summary">
<p>在本教程中,我们将介绍构建 HTML5 移动游戏的过程。 本游戏使用 <a href="https://developer.mozilla.org/en-US/Apps/Build/gather_and_modify_data/responding_to_device_orientation_changes">Device Orientation</a> 和 <a href="https://developer.mozilla.org/en-US/docs/Web/Guide/API/Vibration">Vibration </a><strong>APIs</strong> 来增强游戏玩法,并使用 <a class="external external-icon" href="http://phaser.io/">Phaser</a> 框架构建。 为了充分理解本教程建议您先学习基础的JavaScript 知识。</p>
</div>
<h2 id="Example_game">Example game</h2>
<p>在本教程结束时,您将有一个功能齐全的游戏demo:<a href="http://orb.enclavegames.com/">Cyber Orb </a>。如下所示:</p>
<p><img alt="A 2D game board featuring a small yellow ball. There is a large black hole for the ball to escape down, and a number of barriers blocking the ball from escaping." src="https://mdn.mozillademos.org/files/10297/cyber-orb.png" style="display: block; height: 450px; margin: 0px auto; width: 300px;"></p>
<h2 id="Phaser_framework">Phaser framework</h2>
<p><a href="http://phaser.io/">Phaser</a> 是构建桌面和移动 HTML5 游戏的框架。它非常新,但由于热情的社区参与开发过程它同时也是快速增长的。您能够在<a href="https://github.com/photonstorm/phaser"> GitHub </a>查看它的开放源代码,阅读<a href="http://docs.phaser.io/"> 在线文档</a> 并浏览大量 <a href="http://examples.phaser.io/">示例 </a>。Phaser 框架为您提供了一组工具,这些工具将加快开发速度,并帮助处理完成游戏所需的一般任务,因此您可以专注于游戏创意本身。</p>
<h2 id="Starting_with_the_project">Starting with the project</h2>
<p>您能够在GitHub看到它的源代码 <a href="https://github.com/EnclaveGames/Cyber-Orb">Cyber Orb</a>。文件夹结构非常简单:起点是 <code>index.html</code>。 我们在该文件中初始化框架并设置 html 元素 {{htmlelement("canvas")}} 以呈现游戏。</p>
<p><img alt="Screenshot of the GitHub repository with the Cyber Orb game code, listing the folders and the files in the main structure." src="https://mdn.mozillademos.org/files/10357/cyber-orb-github.png" style="height: 423px; width: 620px;"></p>
<p>您可以在您最喜爱的浏览器中打开index文件以启动并尝试游戏。目录中还有三个文件夹:</p>
<ul>
<li><code>img</code>: 我们将在游戏中使用的所有图片。</li>
<li><code>src</code>:定义了游戏中所有源代码的 JavaScript文件。</li>
<li><code>audio:</code> 在游戏中使用的声音文件。</li>
</ul>
<h2 id="Setting_up_the_Canvas">Setting up the Canvas</h2>
<p>We will be rendering our game on Canvas, but we won't do it manually — this will be taken care of by the framework. Let’s set it up: our starting point is the <code>index.html</code> file with the following content. You can create this yourself if you want to follow along:</p>
<pre class="brush: html"><!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Cyber Orb demo</title>
<style> body { margin: 0; background: #333; } </style>
<script src="src/phaser-arcade-physics.2.2.2.min.js"></script>
<script src="src/Boot.js"></script>
<script src="src/Preloader.js"></script>
<script src="src/MainMenu.js"></script>
<script src="src/Howto.js"></script>
<script src="src/Game.js"></script>
</head>
<body>
<script>
(function() {
var game = new Phaser.Game(320, 480, Phaser.CANVAS, 'game');
game.state.add('Boot', Ball.Boot);
game.state.add('Preloader', Ball.Preloader);
game.state.add('MainMenu', Ball.MainMenu);
game.state.add('Howto', Ball.Howto);
game.state.add('Game', Ball.Game);
game.state.start('Boot');
})();
</script>
</body>
</html></pre>
<p>So far we have a simple HTML website with some basic content in the <code><head></code> section: charset, title, CSS styling and the inclusion of the JavaScript files. The <code><body></code> contains initialization of the Phaser framework and the definitions of the game states.</p>
<pre class="brush: js">var game = new Phaser.Game(320, 480, Phaser.CANVAS, 'game');</pre>
<p>The line above will initialize the Phaser instance — the arguments are the width of the Canvas, height of the Canvas, rendering method (we're using <code>CANVAS</code>, but there are also <code>WEBGL</code> and <code>AUTO</code> options available) and the optional ID of the DOM container we want to put the Canvas in. If there's nothing specified in that last argument or the element is not found, the Canvas will be added to the <body> tag. Without the framework, to add the Canvas element to the page, you would have to write something like this inside the <body> tag:</p>
<pre class="brush: html"><canvas id='game' width='320' height='480'></canvas></pre>
<p>The important thing to remember is that the framework is setting up helpful methods to speed up a lot of things like image manipulation or assets management, which would be a lot harder to do manually.</p>
<div class="note">
<p><strong>Note</strong>: You can read the <a href="http://gamedevelopment.tutsplus.com/tutorials/getting-started-with-phaser-building-monster-wants-candy--cms-21723">Building Monster Wants Candy</a> article for the in-depth introduction to the basic Phaser-specific functions and methods.</p>
</div>
<p>Back to game states: the line below is adding a new state called <code>Boot</code> to the game:</p>
<pre class="brush: html">game.state.add('Boot', Ball.Boot);</pre>
<p>The first value is the name of the state and the second one is the object we want to assign to it. The <code>start</code> method is starting the given state and making it active. Let's see what the states are actually.</p>
<h2 id="Managing_game_states">Managing game states</h2>
<p>The states in Phaser are separate parts of the game logic; in our case we’re loading them from independent JavaScript files for better maintainability. The basic states used in this game are: <code>Boot</code>, <code>Preloader</code>, <code>MainMenu</code>, <code>Howto</code> and <code>Game</code>. <code>Boot</code> will take care of initializing a few settings, <code>Preloader</code> will load all of the assets like graphics and audio, <code>MainMenu</code> is the menu with the start button, <code>Howto</code> shows the "how to play" instructions and the <code>Game</code> state lets you actually play the game. Let's quickly go though the content of those states.</p>
<h3 id="Boot.js">Boot.js</h3>
<p>The <code>Boot</code> state is the first one in the game.</p>
<pre class="brush: js">var Ball = {
_WIDTH: 320,
_HEIGHT: 480
};
Ball.Boot = function(game) {};
Ball.Boot.prototype = {
preload: function() {
this.load.image('preloaderBg', 'img/loading-bg.png');
this.load.image('preloaderBar', 'img/loading-bar.png');
},
create: function() {
this.game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
this.game.scale.pageAlignHorizontally = true;
this.game.scale.pageAlignVertically = true;
this.game.state.start('Preloader');
}
};</pre>
<p>The main <code>Ball</code> object is defined and we're adding two variables called <code>_WIDTH</code> and <code>_HEIGHT</code> that are the width and the height of the game Canvas — they will help us position the elements on the screen. We're loading two images first that will be used later in the <code>Preload</code> state to show the progress of loading all the other assets. The <code>create</code> function holds some basic configuration: we're setting up the scaling and alignment of the Canvas, and moving on to the <code>Preload</code> state when everything's ready.</p>
<h3 id="Preloader.js">Preloader.js</h3>
<p>The <code>Preloader</code> state takes care of loading all the assets:</p>
<pre class="brush: js">Ball.Preloader = function(game) {};
Ball.Preloader.prototype = {
preload: function() {
this.preloadBg = this.add.sprite((Ball._WIDTH-297)*0.5, (Ball._HEIGHT-145)*0.5, 'preloaderBg');
this.preloadBar = this.add.sprite((Ball._WIDTH-158)*0.5, (Ball._HEIGHT-50)*0.5, 'preloaderBar');
this.load.setPreloadSprite(this.preloadBar);
this.load.image('ball', 'img/ball.png');
// ...
this.load.spritesheet('button-start', 'img/button-start.png', 146, 51);
// ...
this.load.audio('audio-bounce', ['audio/bounce.ogg', 'audio/bounce.mp3', 'audio/bounce.m4a']);
},
create: function() {
this.game.state.start('MainMenu');
}
};</pre>
<p>There are single images, spritesheets and audio files loaded by the framework. In this state the <code>preloadBar</code> is showing the progress on the screen. That progress of the loaded assets is visualized by the framework with the use of one image. With every asset loaded you can see more of the <code>preloadBar</code> image: from 0% to 100%, updated on every frame. After all the assets are loaded, the <code>MainMenu</code> state is launched.</p>
<h3 id="MainMenu.js">MainMenu.js</h3>
<p>The <code>MainMenu</code> state shows the main menu of the game, where you can start playing by clicking the button.</p>
<pre class="brush: js">Ball.MainMenu = function(game) {};
Ball.MainMenu.prototype = {
create: function() {
this.add.sprite(0, 0, 'screen-mainmenu');
this.gameTitle = this.add.sprite(Ball._WIDTH*0.5, 40, 'title');
this.gameTitle.anchor.set(0.5,0);
this.startButton = this.add.button(Ball._WIDTH*0.5, 200, 'button-start', this.startGame, this, 2, 0, 1);
this.startButton.anchor.set(0.5,0);
this.startButton.input.useHandCursor = true;
},
startGame: function() {
this.game.state.start('Howto');
}
};</pre>
<p>To create a new button there's <code>add.button</code> method with the following list of optional arguments:</p>
<ul>
<li>Top absolute position on Canvas in pixels.</li>
<li>Left absolute position on Canvas in pixels.</li>
<li>Name of the image asset the button is using.</li>
<li>Function that will be executed when someone clicks the button.</li>
<li>The execution context.</li>
<li>Frame from the image asset used as the button's "hover" state.</li>
<li>Frame from the image asset used as the button's "normal" or "out" state.</li>
<li>Frame from the image asset used as the button's "click" or "down" state.</li>
</ul>
<p>The <code>anchor.set</code> is setting up the anchor point on the button for which all the calculations of the position are applied. In our case it's anchored half the way from the left edge and at the start of the top edge, so it can be easily horizontally centered on the screen without the need to know its width.</p>
<p>When the start button is pressed, instead of jumping directly into the action the game will show the screen with the information on how to play the game.</p>
<h3 id="Howto.js">Howto.js</h3>
<pre class="brush: js">Ball.Howto = function(game) {
};
Ball.Howto.prototype = {
create: function() {
this.buttonContinue = this.add.button(0, 0, 'screen-howtoplay', this.startGame, this);
},
startGame: function() {
this.game.state.start('Game');
}
};</pre>
<p>The <code>Howto</code> state shows the gameplay instructions on the screen before starting the game. After clicking the screen the actual game is launched.</p>
<h3 id="Game.js">Game.js</h3>
<p>The <code>Game</code> state from the <code>Game.js</code> file is where all the magic happens. All the initialization is in the <code>create()</code> function (launched once at the beginning of the game). After that some functionality will require further code to control — we will write our own functions to handle more complicated tasks. In particular, take note of the <code>update()</code> function (executed at every frame), which updates things such as the ball position.</p>
<pre class="brush: js">Ball.Game = function(game) {};
Ball.Game.prototype = {
create: function() {},
initLevels: function() {},
showLevel: function(level) {},
updateCounter: function() {},
managePause: function() {},
manageAudio: function() {},
update: function() {},
wallCollision: function() {},
handleOrientation: function(e) {},
finishLevel: function() {}
};</pre>
<p>The <code>create</code> and <code>update</code> functions are framework-specific, while others will be our own creations:</p>
<ul>
<li><code>initLevels</code> initializes the level data.</li>
<li><code>showLevel</code> prints the level data on the screen.</li>
<li><code>updateCounter</code> updates the time spent playing each level and records the total time spent playing the game..</li>
<li><code>managePause</code> pauses and resumes the game.</li>
<li><code>manageAudio</code> turns the audio on and off.</li>
<li><code>wallCollision</code> is executed when the ball hits the walls or other objects.</li>
<li><code>handleOrientation</code> is the function bound to the event responsible for the Device Orientation API, providing the motion controls when the game is running on a mobile device with appropriate hardware.</li>
<li><code>finishLevel</code> loads a new level when the current level is completed, or finished the game if the final level is completed.</li>
</ul>
<h4 id="Adding_the_ball_and_its_motion_mechanics">Adding the ball and its motion mechanics</h4>
<p>First, let’s go to the <code>create()</code> function, initialize the ball object itself and assign a few properties to it:</p>
<pre class="brush: js">this.ball = this.add.sprite(this.ballStartPos.x, this.ballStartPos.y, 'ball');
this.ball.anchor.set(0.5);
this.physics.enable(this.ball, Phaser.Physics.ARCADE);
this.ball.body.setSize(18, 18);
this.ball.body.bounce.set(0.3, 0.3);</pre>
<p>Here we’re adding a sprite at the given place on the screen and using the <code>'ball'</code> image from the loaded graphic assets. We’re also setting the anchor for any physics calculations to the middle of the ball, enabling the Arcade physics engine (which handles all the physics for the ball movement), and setting the size of the body for the collision detection. The <code>bounce</code> property is used to set the bounciness of the ball when it hits the obstacles.</p>
<h4 id="Controlling_the_ball">Controlling the ball</h4>
<p>It’s cool to have the ball ready to be thrown around in the play area, but it’s also important to be able to actually move it! Now we will add the ability to control the ball with the keyboard on the desktop devices, and then we will move to the implementation of the Device Orientation API. Let’s focus on the keyboard first by adding the following to the <code>create()</code> function :</p>
<pre class="brush: js">this.keys = this.game.input.keyboard.createCursorKeys();</pre>
<p>As you can see there’s a special Phaser function called <code>createCursorKeys()</code>, which will give us an object with event handlers for the four arrow keys to play with: up, down, left and right.</p>
<p>Next we will add the following code to the <code>update()</code> function, so it will be fired on every frame. The <code>this.keys</code> object will be checked against player input, so the ball can react accordingly with the predefined force:</p>
<pre class="brush: js">if(this.keys.left.isDown) {
this.ball.body.velocity.x -= this.movementForce;
}
else if(this.keys.right.isDown) {
this.ball.body.velocity.x += this.movementForce;
}
if(this.keys.up.isDown) {
this.ball.body.velocity.y -= this.movementForce;
}
else if(this.keys.down.isDown) {
this.ball.body.velocity.y += this.movementForce;
}</pre>
<p>That way we can check which key is pressed at the given frame and apply the defined force to the ball, thus increase the velocity in the proper direction.</p>
<h4 id="Implementing_the_Device_Orientation_API">Implementing the Device Orientation API</h4>
<p>Probably the most interesting part of the game is its usage of the <strong>Device Orientation API</strong> for control on mobile devices. Thanks to this you can play the game by tilting the device in the direction you want the ball to roll. Here’s the code from the <code>create()</code> function responsible for this:</p>
<pre class="brush: js">window.addEventListener("deviceorientation", this.handleOrientation, true);</pre>
<p>We’re adding an event listener to the <code>"deviceorientation"</code> event and binding the <code>handleOrientation</code> function which looks like this:</p>
<pre class="brush: js">handleOrientation: function(e) {
var x = e.gamma;
var y = e.beta;
Ball._player.body.velocity.x += x;
Ball._player.body.velocity.y += y;
},</pre>
<p>The more you tilt the device, the more force is applied to the ball, therefore the faster it moves (the velocity is higher).</p>
<p><img alt="An explanation of the X, Y and Z axes of a Flame mobile device with the Cyber Orb game demo on the screen." src="https://mdn.mozillademos.org/files/10369/cyber-orb-flame-orientation.png" style="height: 480px; width: 620px;"></p>
<div class="note">
<p><strong>Note</strong>: To find more out about implementing device orientation and what raw code would look like, read <a href="/en-US/Apps/Build/gather_and_modify_data/responding_to_device_orientation_changes">Keep it level: responding to device orientation changes</a>.</p>
</div>
<h4 id="Adding_the_hole">Adding the hole</h4>
<p>The main objective in the game is to move the ball from the starting position to the ending position: a hole in the ground. Implementation looks very similar to the part where we created the ball, and it's also added in the <code>create()</code> function of our <code>Game</code> state:</p>
<pre class="brush: js">this.hole = this.add.sprite(Ball._WIDTH*0.5, 90, 'hole');
this.physics.enable(this.hole, Phaser.Physics.ARCADE);
this.hole.anchor.set(0.5);
this.hole.body.setSize(2, 2);</pre>
<p>The difference is that our hole’s body will not move when we hit it with the ball and will have the collision detection calculated (which will be discussed later on in this article).</p>
<h4 id="Building_the_block_labyrinth">Building the block labyrinth</h4>
<p>To make the game harder and more interesting we will add some obstacles between the ball and the exit. We could use a level editor, but for the sake of this tutorial let's create something on our own.</p>
<p>To hold the block information we'll use a level data array: for each block we'll store the top and left absolute positions in pixels (<code>x</code> and <code>y</code>) and the type of the block — horizontal or vertical (<code>t</code> with the <code>'w'</code> value meaning width and <code>'h'</code> meaning height). Then, to load the level we'll parse the data and show the blocks specific for that level. In the <code>initLevels</code> function we have:</p>
<pre class="brush: js">this.levelData = [
[
{ x: 96, y: 224, t: 'w' }
],
[
{ x: 72, y: 320, t: 'w' },
{ x: 200, y: 320, t: 'h' },
{ x: 72, y: 150, t: 'w' }
],
// ...
];</pre>
<p>Every array element holds a collection of blocks with an <code>x</code> and <code>y</code> position and <code>t</code> value for each. After <code>levelData</code>, but still in the <code>initLevels</code> function, we're adding the blocks into an array in the <code>for</code> loop using some of the framework-specific methods:</p>
<pre class="brush: js">for(var i=0; i<this.maxLevels; i++) {
var newLevel = this.add.group();
newLevel.enableBody = true;
newLevel.physicsBodyType = Phaser.Physics.ARCADE;
for(var e=0; e<this.levelData[i].length; e++) {
var item = this.levelData[i][e];
newLevel.create(item.x, item.y, 'element-'+item.t);
}
newLevel.setAll('body.immovable', true);
newLevel.visible = false;
this.levels.push(newLevel);
}</pre>
<p>First, <code>add.group()</code> is used to create a new group of items. Then the <code>ARCADE</code> body type is set for that group to enable physics calculations. The <code>newLevel.create</code> method creates new items in the group with starting left and top positions, and its own image. If you don't want to loop through the list of items again to add a property to every single one explicitly, you can use <code>setAll</code> on a group to apply it to all the items in that group.</p>
<p>The objects are stored in the <code>this.levels</code> array, which is by default invisible. To load specific levels, we make sure the previous levels are hidden, and show the current one:</p>
<pre class="brush: js">showLevel: function(level) {
var lvl = level | this.level;
if(this.levels[lvl-2]) {
this.levels[lvl-2].visible = false;
}
this.levels[lvl-1].visible = true;
}</pre>
<p>Thanks to that the game gives the player a challenge - now he have to roll the ball across the play area and guide it through the labyrinth built from the blocks. It's just an example of loading the levels, and there are only 5 of them just to showcase the idea, but you can work on expanding that on your own.</p>
<h4 id="Collision_detection">Collision detection</h4>
<p>At this point we've got the ball that is controlled by the player, the hole to reach and the obstacles blocking the way. There’s a problem though — our game doesn’t have any collision detection yet, so nothing happens when the ball hits the blocks — it just goes through. Let’s fix it! The good news is that the framework will take care of calculating the collision detection, we only have to specify the colliding objects in the <code>update()</code> function:</p>
<pre class="brush: js">this.physics.arcade.collide(this.ball, this.borderGroup, this.wallCollision, null, this);
this.physics.arcade.collide(this.ball, this.levels[this.level-1], this.wallCollision, null, this);</pre>
<p>This will tell the framework to execute the <code>wallCollision</code> function when the ball hits any of the walls. We can use the <code>wallCollision</code> function to add any functionality we want like playing the bounce sound and implementing the <strong>Vibration API</strong>.</p>
<h4 id="Adding_the_sound">Adding the sound</h4>
<p>Among the preloaded assets there was an audio track (in various formats for browser compatibility), which we can use now. It has to be defined in the <code>create()</code> function first:</p>
<pre class="brush: js">this.bounceSound = this.game.add.audio('audio-bounce');</pre>
<p>If the status of the audio is <code>true</code> (so the sounds in the game are enabled), we can play it in the <code>wallCollision</code> function:</p>
<pre class="brush: js">if(this.audioStatus) {
this.bounceSound.play();
}</pre>
<p>That's all — loading and playing the sounds is easy with Phaser.</p>
<h4 id="Implementing_the_Vibration_API">Implementing the Vibration API</h4>
<p>When collision detection works as expected let's add some special effects with the help from the Vibration API.</p>
<p><img alt="A visualization of the vibrations of a Flame mobile device with the Cyber Orb game demo on the screen." src="https://mdn.mozillademos.org/files/10371/cyber-orb-flame-vibration.png" style="height: 480px; width: 620px;"></p>
<p>The best way to use it in our case is to vibrate the phone every time the ball hits the walls — inside the <code>wallCollision</code> function:</p>
<pre class="brush: js">if("vibrate" in window.navigator) {
window.navigator.vibrate(100);
}</pre>
<p>If the <code>vibrate</code> method is supported by the browser and available in the <code>window.navigator</code> object, vibrate the phone for 100 miliseconds. That's it!</p>
<h4 id="Adding_the_elapsed_time">Adding the elapsed time</h4>
<p>To improve replayability and give players the option to compete with each other we will store the elapsed time — players can then try to improve on their best game completion time. To implement this we have to create a variable for storing the actual number of seconds elapsed from the start of the game, and to show it for the player in the game. Let’s define the variables in the <code>create</code> function first:</p>
<pre class="brush: js">this.timer = 0; // time elapsed in the current level
this.totalTimer = 0; // time elapsed in the whole game</pre>
<p>Then, right after that, we can initialize the necessary text objects to display this information to the user:</p>
<pre class="brush: js">this.timerText = this.game.add.text(15, 15, "Time: "+this.timer, this.fontBig);
this.totalTimeText = this.game.add.text(120, 30, "Total time: "+this.totalTimer, this.fontSmall);</pre>
<p>We’re defining the top and left positions of the text, the content that will be shown and the styling applied to the text. We have this printed out on the screen, but it would be good to update the values every second:</p>
<pre class="brush: js">this.time.events.loop(Phaser.Timer.SECOND, this.updateCounter, this);</pre>
<p>This loop, also in the <code>create</code> function, will execute the <code>updateCounter</code> function every single second from the beginning of the game, so we can apply the changes accordingly. This is how the complete <code>updateCounter</code> function looks:</p>
<pre class="brush: js">updateCounter: function() {
this.timer++;
this.timerText.setText("Time: "+this.timer);
this.totalTimeText.setText("Total time: "+(this.totalTimer+this.timer));
},</pre>
<p>As you can see we’re incrementing the <code>this.timer</code> variable and updating the content of the text objects with the current values on each iteration, so the player sees the elapsed time.</p>
<h4 id="Finishing_the_level_and_the_game">Finishing the level and the game</h4>
<p>The ball is rolling on the screen, the timer is working and we have the hole created that we have to reach. Now let’s set up the possibility to actually finish the level! The following line in the <code>update()</code> function adds a listener that fires when the ball gets to the hole.</p>
<pre class="brush: js">this.physics.arcade.overlap(this.ball, this.hole, this.finishLevel, null, this);</pre>
<p>This works similarly to the <code>collide</code> method explained earlier. When the ball overlaps with the hole (instead of colliding), the <code>finishLevel</code> function is executed:</p>
<pre class="brush: js">finishLevel: function() {
if(this.level >= this.maxLevels) {
this.totalTimer += this.timer;
alert('Congratulations, game completed!\nTotal time of play: '+this.totalTimer+' seconds!');
this.game.state.start('MainMenu');
}
else {
alert('Congratulations, level '+this.level+' completed!');
this.totalTimer += this.timer;
this.timer = 0;
this.level++;
this.timerText.setText("Time: "+this.timer);
this.totalTimeText.setText("Total time: "+this.totalTimer);
this.levelText.setText("Level: "+this.level+" / "+this.maxLevels);
this.ball.body.x = this.ballStartPos.x;
this.ball.body.y = this.ballStartPos.y;
this.ball.body.velocity.x = 0;
this.ball.body.velocity.y = 0;
this.showLevel();
}
},</pre>
<p>If the current level is equal to the maximum number of levels (in this case 5), then the game is finished — you'll get a congratulations message along with the number of seconds elapsed through the whole game, and a button to press that takes you to the main menu.</p>
<p>If the current level is lower than 5, all the neccesary variables are reset and the next level is loaded.</p>
<h2 id="Ideas_for_new_features">Ideas for new features</h2>
<p>This is merely a working demo of a game that could have lots of additional features. We can for example add power-ups to collect along the way that will make our ball roll faster, stop the timer for a few seconds or give the ball special powers to go through obstacles. There’s also room for the traps which will slow the ball down or make it more difficult to reach the hole. You can create more levels of increasing difficulty. You can even implement achievements, leaderboards and medals for different actions in the game. There are endless possibilities — they only depend on your imagination.</p>
<h2 id="Summary">Summary</h2>
<p>I hope this tutorial will help you dive into 2D game development and inspire you to create awesome games on your own. You can play the demo game <a href="http://orb.enclavegames.com/">Cyber Orb</a> and check out its <a href="https://github.com/EnclaveGames/Cyber-Orb">source code on GitHub</a>.</p>
<p>HTML5 gives us raw tools, the frameworks built on top of it are getting faster and better, so now is a great time get into web game development. In this tutorial we used Phaser, but there are a number of <a href="http://html5devstarter.enclavegames.com/#frameworks">other frameworks</a> worth considering too like <a href="http://impactjs.com/">ImpactJS</a>, <a href="https://www.scirra.com/construct2">Construct 2</a> or <a href="http://playcanvas.com/">PlayCanvas</a> — it depends on your preferences, coding skills (or lack thereof), project scale, requirements and other aspects. You should check them all out and decide which one suits your needs best.</p>
|