-
Notifications
You must be signed in to change notification settings - Fork 3
/
doc.html
737 lines (727 loc) · 32.1 KB
/
doc.html
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
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Coding Club Project</title>
<style>
body{
background-color: #001145;
color: #ffffff;
background-image: url(pagebg.png);
padding:0;
margin:0;
font-size: 16px;
}
#gotop{
position: fixed;
top: 0;
right: 0;
background-color: #000000;
border: 1px solid #0000ff;
width: 78px;
height: 21px;
padding: 6px;
z-index: 1;
}
#content{
position: absolute;
margin: auto;
left: 0;
right: 0;
width: 800px;
}
#bars{
position: absolute;
width: 100%;
min-height: 100%;
border-right: 1px solid #2222ff;
border-left: 1px solid #2222ff;
}
#inner{
margin-left: 50px;
width: 700px;
}
.center{
text-align: center;
}
.code{
width: 700px;
background-color: #000000;
font-family: Courier;
color: #8888ff;
border-bottom: 40px solid #000000;
}
b{ font-weight: normal; font-family: Courier; font-size: 16px; }
.vr{ height: 12px; }
.vr-big{ height: 80px; }
a{ color: #88ff88; }
a:hover{ color: #ffffff; }
a:active{ color: #88ff88; }
</style>
</head>
<body>
<div id="top"></div>
<div id="gotop">
<a href="#top">Go to Top</a>
</div>
<div id="content"><div id="bars"><div id="inner">
<div class="center">
<h1>How The Game Works</h1>
<h4>This serves as the documentation for the game and also a tutorial you can follow along to make the game yourself, or make an entirely different game with its mechanics</h4>
</div>
<div class="vr"></div>
<h4 id="sections">Skip to a Section:</h4>
<a href="#canvas">01. The Canvas</a><br>
<a href="#drawing">02. Drawing</a><br>
<a href="#input">03. Keyboard Input</a><br>
<a href="#objects">04. Game Loop & Objects</a><br>
<a href="#physics">05. Spaceship Physics</a><br>
<a href="#wrapping">06. Screen Wrapping</a><br>
<a href="#shooting">07. Shooting</a><br>
<a href="#asteroids">08. Asteroids</a><br>
<a href="#collision">09. Collision</a><br>
<a href="#screens">10. Title/Game/End Screens</a><br>
<div class="vr-big"></div>
<a href="#canvas"><h2 id="canvas">The Canvas</h2></a>
<p>
Everything rendered in the game is done using the HTML5 Canvas. Canvases can be drawn to and cleared with JavaScript. You can create a canvas using the <b>canvas</b> HTML tag as shown below:
</p>
<div class="code">
<canvas id="can" width="0" height="0"><br>
 Your browser does not support the HTML5 canvas<br>
</canvas><br>
</div>
<p>
The text inside the tag is shown if the client's browser is outdated and does not support the canvas. The <b>width</b> and <b>height</b> attributes specify the dimensions of the canvas in pixels. Notice that there is a distinction between these values and the CSS width and height of the canvas. In this example, the dimensions are set to 0x0 because they will be changed later with JS.
</p>
<p>
To interact with the canvas in JS, we must reference it with <b>document.getElementById()</b>. To draw to it, we must get the 2D context of the canvas.
</p>
<div class="code">
var cv = document.getElementById("can");<br>
cv.width = GAME_WIDTH;<br>
cv.height = GAME_HEIGHT;<br>
var c = cv.getContext("2d", {alpha: false});<br>
</div>
<p>
The context of the canvas is obtained with <b>cv.getContext("2d", {alpha: false})</b>. Passing <b>{alpha: false}</b> removes the transparent background of the canvas to improve performance. It is optional. I named the context variable <b>c</b> so it can be typed easily, because we will be using it to draw to the canvas often.
</p>
<div class="vr-big"></div>
<a href="#drawing"><h2 id="drawing">Drawing</h2></a>
<p>
Drawing to the canvas is done using the 2D context we obtained earlier. Image files can be drawn by passing an <b>Image</b> variable along with the <b>x</b> and <b>y</b> coordinates to draw to. Example:
</p>
<div class="code">
var sImage = new Image();<br>
sImage.src = "path/to/image.png";<br>
<br>
c.drawImage(sImage, 0, 0);<br>
</div>
<p>
This will draw an image to the top left corner of the canvas (position 0,0). If you run this code unaltered, however, you likely won't see anything appear, because you must wait for images to load before drawing them. This can be done by setting the <b>onload</b> value of the image to a function that checks if all images you plan on using have loaded. This is how images are loaded in the game:
</p>
<div class="code">
var imagesLoaded = 0;<br>
var imagesNeeded = 7;<br>
var imagesToLoad = [sSpaceship, sBullet, sAsteroid, sAlert1, sAlert2, sBg1, sBg2];<br>
<br>
function imageLoaded()<br>
{<br>
 if ((++imagesLoaded) >= imagesNeeded)<br>
  gmTitle();<br>
}<br>
<br>
for (var i = 0; i < imagesToLoad.length; ++i)<br>
{<br>
 imagesToLoad[i].onload = imageLoaded;<br>
}<br>
</div>
<p>
This code waits for all images to load, then calls <b>gmTitle()</b> which starts the game. Feel free to optimize this code if you plan on reusing it. I haven't optimized the code in this case because it has little impact on performance. What does have impact on performance, however, is drawing the images themselves.
</p>
<p>
To draw a rotated image (which is done with the spaceship), you must translate and rotate the canvas context before calling <b>drawImage</b>. In the game, this is done with this function:
</p>
<div class="code">
function oSpaceshipDraw(x, y)<br>
{<br>
 c.save();<br>
 c.translate(x, y);<br>
 c.rotate(this.dir);<br>
 c.drawImage(sSpaceship, -this.w / 2, -this.h / 2);<br>
 c.restore();<br>
}<br>
</div>
<p>
<b>c.save()</b> saves the current state of the canvas context. Then, the context is translated by the x and y coordinates of the ship, and rotated. The x and y coordinates used in <b>drawImage</b> are relative to the x and y coordinates we translated the context by. In this case, the negatives of the half of the width and height of the ship are passed to position the ship in the center of the x and y coordinates we are drawing to. Finally, <b>c.restore()</b> is called, which undoes all of the translations and rotations, so that next time we draw something, it isn't translated and rotated somewhere random.
</p>
<p>
To avoid rotated image bluriness, as done in the game, the canvas context variable <b>imageSmoothingEnabled</b> must be set to <b>false</b>.
</p>
<div class="code">
c.imageSmoothingEnabled = false;
</div>
<div class="vr-big"></div>
<a href="#input"><h2 id="input">Keyboard Input</h2></a>
<p>
To detect keyboard input, two event listeners are added to the document that check for key presses and releases. These are added with the <b>document</b> function <b>addEventListener</b> which takes two parameters. These include a string specifying the type of event to listen for, in this case <b>"keydown"</b> or <b>"keyup"</b>, and a function to call when the event is activated, which an event object is automatically passed to.
</p>
<p>
The <b>keyCode</b> variable of the event object contains an integer that represents the ID of the key being pressed or released. A switch statement is used to check which key code corresponds to which key variable. They key variables are set to 1 when activated in the <b>keydown</b> event and 0 in the <b>keyup</b> event.
</p>
<p>
To find the key code of a desired key, you can find a table online containing all key codes, or if you prefer to not visit any other sites, write a function yourself that prints the key code of the last pressed key.
</p>
<div class="code">
// Keyboard Input<br>
var kl = 0, kr = 0, ku = 0, kd = 0, k1 = 0, k2 = 0;<br>
<br>
// Listen for keyboard input<br>
document.addEventListener("keydown", function(e)<br>
{<br>
 switch (e.keyCode)<br>
 {<br>
  case 37:{kl=1;e.preventDefault();break;}<br>
  case 39:{kr=1;e.preventDefault();break;}<br>
  case 40:{kd=1;e.preventDefault();break;}<br>
  case 38:{ku=1;e.preventDefault();break;}<br>
<br>
  case 90:{k1=1;break;}<br>
  case 13:{k2=1;break;}<br>
 }<br>
});<br>
document.addEventListener("keyup", function(e)<br>
{<br>
 switch (e.keyCode)<br>
 {<br>
  case 37:{kl=0;break;}<br>
  case 39:{kr=0;break;}<br>
  case 40:{kd=0;break;}<br>
  case 38:{ku=0;break;}<br>
<br>
  case 90:{k1=0;break;}<br>
 }<br>
});<br>
</div>
<p>
Key codes <b>37</b> through <b>40</b> represent the arrow keys. When they are activated in the <b>keydown</b> event, the function <b>preventDefault</b> of the event object is called to prevent the default action of these keys, which scroll around the page. Without calling this function, the player would unintentionally scroll around the webpage when trying to play, which makes games nearly unplayable.
</p>
<p>
With these two <b>keydown</b> and <b>keyup</b> events we can now detect when keys are held down, but not when they are initially pressed. To implement key press detection, the function <b>stopKeyRepeat</b> is used, which simply sets the keys we only want to detect initial presses on to <b>0</b>.
</p>
<div class="code">
function stopKeyRepeat()<br>
{<br>
 k2 = 0;<br>
}<br>
</div>
<p>
This function is called every game loop after objects are done updating. When the keys it affects are pressed and set to <b>1</b>, the game objects will run their update function once before the key is set back to <b>0</b>, only activated upon another initial press of the key. The game loop is a function that evaluates every game frame, and it will be explained in the next section.
</p>
<div class="vr-big"></div>
<a href="#objects"><h2 id="objects">Game Loop & Objects</h2></a>
<p>
In the game, the game loop is contained in the function <b>gmLoop</b>. This function executes a single frame of the game, and then calls the function <b>requestAnimationFrame</b> to call itself again in 1/60 of a second, resulting in game frames being drawn at 60 frames per second.
</p>
<p>
To render a frame, the <b>gmLoop</b> function clears the canvas, draws the background, executes the update function of all objects that may draw themselves, and finally executes more code that isn't tied to any object, including the spawning of asteroids and drawing of the current score.
</p>
<div class="code">
function gmLoop()<br>
{<br>
 // Request another frame<br>
 requestAnimationFrame(gmLoop);<br>
<br>
 // Clear Screen<br>
 c.clearRect(0,0,cv.width,cv.height); <br>
<br>
 // Draw Background<br>
 drawBg();<br>
<br>
 // Update Instances<br>
 for (var i = 0; i < MAX_INSTANCES; ++i)<br>
  if (inst[i] != null)<br>
   inst[i].ud();<br>
<br>
 // Spawn Asteroids<br>
 if (spawning && (--spawnTime) <= 0)<br>
 {<br>
   // Spawn Asteroid<br>
   var x = Math.random() * GAME_WIDTH;<br>
   var y = Math.random() * GAME_HEIGHT;<br>
   new makeInst(x, y, 3);<br>
  // Reset Spawn Time<br>
   spawnTimeReset -= SPAWN_TIME_DEC;<br>
   if (spawnTimeReset < SPAWN_TIME_MIN)<br>
    spawnTimeReset = SPAWN_TIME_MIN;<br>
   spawnTime = spawnTimeReset;<br>
 }<br>
<br>
 // Keyboard Input<br>
 stopKeyRepeat();<br>
<br>
 // Show Framerate & Score<br>
 updateFramerate();<br>
<br>
 c.fillStyle = TEXT_COLOR_GREEN;<br>
 c.font = "16px Courier";<br>
 c.textAlign = "left";<br>
 c.fillText("FPS: " + fps, 6, 16);<br>
 c.fillText("SCORE: " + score, 6, 32);<br>
 c.fillText("HISCORE: " + highScore, 6, 48);<br>
}<br>
</div>
<p>
Object instances are kept in the <b>inst</b> array. To update them, the <b>inst</b> array is looped through, and the update function of each instance, <b>ud</b>, is executed. The update functions contain the code executed by each instance every frame. These usually include moving the instance, checking for collisions, and drawing the instance.
</p>
<p>
New object instances are created using the function <b>makeInst</b> as a constructor. You can see this in action in the code example above, where asteroids are spawned with this line:
</p>
<div class="code">
new makeInst(x, y, 3);
</div>
<p>
The third argument of <b>makeInst</b> is the ID of the object type being created. 0 is used for the creation of the player's ship, 1 is used for bullets, 2 is used for asteroids, and so forth. 3 is passed to this <b>makeInst</b> in this example because it creates the alert objects that flash for a few seconds before spawning an asteroid.
</p>
<p>
Let's take a deeper look into the code of <b>makeInst</b>.
</p>
<div class="code">
function makeInst(x, y, o)<br>
{<br>
 // Skip past instance indexes that are full or have persistent objects<br>
 while (inst[instNext] != null && inst[instNext].ps != null)<br>
 {<br>
  if ((++instNext) >= MAX_INSTANCES)<br>
  instNext = 0;<br>
 }<br>
 <br>
 // Add instance to instance array<br>
 inst[instNext] = this;<br>
 this.id = instNext;<br>
 if ((++instNext) >= MAX_INSTANCES)<br>
  instNext = 0;<br>
 <br>
 // Set x & y position<br>
 this.x = x;<br>
 this.y = y;<br>
 <br>
 this.o = o;<br>
  <br>
 // Execute object constructor<br>
 switch (o)<br>
 {<br>
  case 0:<br>
  {<br>
   this.xforces = new Array(PLAYER_FORCE_COUNT).fill(0);<br>
   this.yforces = new Array(PLAYER_FORCE_COUNT).fill(0);<br>
   this.force = 0;<br>
   this.forceSpeed = PLAYER_SPEED_FORCE;<br>
   this.turnSpeed = PLAYER_SPEED_TURN;<br>
   this.bulletSpeed = PLAYER_SPEED_BULLET;<br>
   this.shootCooldownReset = 10;<br>
   this.shootCooldown = 0;<br>
   this.dir = 0;<br>
   this.w = sSpaceship.width;<br>
   this.h = sSpaceship.height;<br>
   <br>
   this.worldWrap = oWorldWrap;<br>
   this.shoot = oSpaceshipShoot;<br>
   this.draw = oSpaceshipDraw;<br>
   this.drawWrap = oDrawWrap;<br>
   this.hit = oCollideWithLarger;<br>
   <br>
   this.ps = true;<br>
   this.ud = oSpaceshipUD;<br>
   break;<br>
  }<br>
  // More cases are in the source code but not shown here<br>
 }<br>
}<br>
</div>
<p>
Object instances are added to the <b>inst</b> array as soon as the <b>makeInst</b> function is called. If there is no room left in the array for new instances, the instance being created will overwrite another instance as long as the instance's <b>ps</b> variable, which indicates persistence, is not set to <b>true</b>. The object-specific variables and update function are determined by the switch statement.
</p>
<p>
Sometimes different objects execute the same sets of code, such as collision checking and screen wrapping. These are stored in functions so that they don't have to be written over and over again for each object. For an object instance to call these functions properly, a reference to one of them must be stored in an instance variable, and then called from that reference. This is demonstrated in the following code:
</p>
<div class="code">
// Get a reference to the function<br>
this.shoot = oSpaceshipShoot;<br>
<br>
// Call the function<br>
this.shoot();<br>
</div>
<p>
Calling the function without a reference causes the keyword <b>this</b> to not reference the object instance. It is valid to call functions without a reference that do not need to reference the instance calling the object, such as <b>gmEnd</b>, which simply ends the game without accessing any instance variables, though this is rarely done.
</p>
<p>
To summarize, <b>gameLoop</b> is called 60 times a second to draw a new game frame and update object instances by calling their <b>ud</b> function.
</p>
<div class="vr-big"></div>
<a href="#physics"><h2 id="physics">Spaceship Physics</h2></a>
<p>
The spaceship object keeps track of the direction it points in with its <b>dir</b> variable, and keeps track of its momentum in two arrays, <b>xforces</b> and <b>yforces</b>, both of equal length. When the up key is held, the object adds the amount of pixels it should move horizontally and vertically in <b>xforces</b> and <b>yforces</b> respectively, this is calculated with sine and cosine waves with the direction of the ship as input.
</p>
<div class="code">
// Move ship<br>
if (ku)<br>
{<br>
 this.xforces[this.force] = Math.cos(this.dir) * this.forceSpeed;<br>
 this.yforces[this.force] = Math.sin(this.dir) * this.forceSpeed;<br>
<br>
 ++this.force;<br>
 if (this.force >= PLAYER_FORCE_COUNT)<br>
  this.force = 0;<br>
}<br>
</div>
<p>
The more the direction is turned to 0 or pi radians, the more the ship will move horizontally, and the more the direction is turned to pi/2 or 3pi/2 radians, the more the ship will move vertically. The forces added are multipled by <b>forceSpeed</b> to control the speed that the ship picks up each frame.
</p>
<p>
Indexes of the <b>xforces</b> and <b>yforces</b> arrays are accessed with the variable <b>force</b>. Each time forces are added, <b>force</b> increments to move to the next index, and loops back to 0 when the end of the arrays, stored in <b>PLAYER_FORCE_COUNT</b>, are reached.
</p>
<p>
The forces are finally applied to the ship and move it in the loop below, which is executed regardless of whether or not the up key is held down.
</p>
<div class="code">
// Apply movement<br>
for (var i = 0; i < PLAYER_FORCE_COUNT; ++i)<br>
{<br>
 this.x += this.xforces[i];<br>
 this.y += this.yforces[i];<br>
}<br>
</div>
<p>
In the end, this creates a system where forces applied in past frames still move the ship until the ship either moves for long enough that the forces are overwritten, or the ship moves in the opposite direction to balance the force. This is why the ship "drifts" ingame instead of staying still or immediately changing the direction of movement once any amount of force is added.
</p>
<div class="vr-big"></div>
<a href="#wrapping"><h2 id="wrapping">Screen Wrapping</h2></a>
<p>
To keep the player and asteroids within the bounds of the screen, the x and y values of the objects are reset once they reach the edge of the screen.
</p>
<div class="code">
function oWorldWrap()<br>
{<br>
 if (this.x > GAME_WIDTH)<br>
  this.x = GAME_WIDTH - this.x;<br>
 else if (this.x < 0)<br>
  this.x = GAME_WIDTH + this.x;<br>
 <br>
 if (this.y > GAME_HEIGHT)<br>
  this.y = GAME_HEIGHT - this.y; <br>
 else if (this.y < 0)<br>
  this.y = GAME_HEIGHT + this.y;<br>
}<br>
</div>
<p>
When the x and y values are reset, they are positioned relative to how far the object has move past the bounds of the screen. For example, if the screen with is 600 pixels and the object's x position is at 650, the x value will be reset to 0 plus 50 because it traveled 50 pixels beyond the screen. This results in no loss of movement when wrapping around the screen.
</p>
<p>
The code above properly handles the coordinates of objects, but we still need to draw objects while they are wrapping from one side of the screen to the next to make wrapping entirely seamless. This is accomplished by drawing the objects in all 8 directions around them with the following code:
</p>
<div class="code">
function oDrawWrap()<br>
{<br>
 var xstop = this.x + GAME_WIDTH;<br>
 var ystop = this.y + GAME_HEIGHT;<br>
<br>
 for (var x = this.x - GAME_WIDTH - 1; x <= xstop; x += GAME_WIDTH)<br>
 {<br>
  for (var y = this.y - GAME_HEIGHT - 1; y <= ystop; y += GAME_HEIGHT)<br>
  {<br>
   this.draw(x, y);<br>
  }<br>
 }<br>
}<br>
</div>
<p>
Each object is spaced horizontally by the screen width, and spaced vertically by the screen height. This effect causes objects to appear on top of the screen when wrapping from the bottom, on the left of the screen when wrapping from the right, and so forth.
</p>
<p>
This draws a total of 9 images, so it should be used sparingly, as drawing is a computationally expensive operation. It also has room for optimization, as it can be edited to only draw objects based on the direction they are moving in.
</p>
<div class="vr-big"></div>
<a href="#shooting"><h2 id="shooting">Shooting</h2></a>
<p>
This section covers the mechanics of bullet firing by the spaceship object that the player controls. Bullets, like the spaceship itself, are objects created with <b>makeInst</b> that have speeds initialized upon their creation which go unchanged until the object is destroyed. The following function is used by spaceship instances to create them.
</p>
<div class="code">
function oSpaceshipShoot()<br>
{<br>
 var bullet = new makeInst(this.x, this.y, 1);<br>
 bullet.hsp = Math.cos(this.dir) * this.bulletSpeed;<br>
 bullet.vsp = Math.sin(this.dir) * this.bulletSpeed;<br>
}<br>
</div>
<p>
Bullets use two variables to move each frame, <b>hsp</b> and <b>vsp</b> which control the bullets' horizontal speed and vertical speed respectively. These are used instead of a direction and speed variable to control bullet movement because they only need to be calculated once, while direction calculations must be performed every frame to move a bullet. This improves performance, especially when many bullets objects are in action at once.
</p>
<p>
The rate at which bullets are fired are dependent on two variables in the spaceship object, <b>shootCooldown</b> and <b>shootCooldownReset</b>. <b>shootCooldown</b> is decremented each frame in the spaceship's <b>ud</b> function until it reaches <b>0</b>. When the Z key is pressed or held at this point, a bullet will be fired, and <b>shootCooldown</b> will be set to <b>shootCooldownReset</b>. The player must wait until <b>shootCooldown</b> reaches <b>0</b> to fire another bullet, or let go of the Z key to immediately set the cooldown to <b>0</b>. Below is the code that the spaceship runs to perform all of this:
</p>
<div class="code">
// Shooting<br>
if (this.shootCooldown == 0)<br>
{<br>
 if (k1)<br>
 {<br>
  this.shoot();<br>
  this.shootCooldown = this.shootCooldownReset;<br>
 }<br>
}<br>
else<br>
{<br>
 --this.shootCooldown;<br>
 if (!k1)<br>
  this.shootCooldown = 0;<br>
}<br>
</div>
<a href="#asteroids"><h2 id="asteroids">Asteroids</h2></a>
<p>
Asteroids are spawned by alert objects, which are spawned by an if statement found in <b>gmLoop</b> shown below. The spawning is influenced by three global variables: <b>spawning</b>, <b>spawnTime</b>, and <b>spawnTimeReset</b>.
</p>
<p>
<b>spawning</b> is a boolean that controls whether or not asteroids can spawn.
</p>
<p>
<b>spawnTime</b> is an integer that decrements each frame until it reaches <b>0</b>, at which point an alert object will spawn.
</p>
<p>
<b>spawnTimeReset</b> is the value that <b>spawnTime</b> is reset to upon reaching <b>0</b>, and it decreases by <b>SPAWN_TIME_DEC</b> each time an alert object is created. This shortens the amount of time between alert spawns until <b>spawnTimeReset</b> reaches the minimum spawn time, <b>SPAWN_TIME_MIN</b>.
</p>
<div class="code">
// Spawn Asteroids<br>
if (spawning && (--spawnTime) <= 0)<br>
{<br>
 // Spawn Asteroid<br>
 var x = Math.random() * GAME_WIDTH;<br>
 var y = Math.random() * GAME_HEIGHT;<br>
 new makeInst(x, y, 3);<br>
<br>
 // Reset Spawn Time<br>
 spawnTimeReset -= SPAWN_TIME_DEC;<br>
 if (spawnTimeReset < SPAWN_TIME_MIN)<br>
  spawnTimeReset = SPAWN_TIME_MIN;<br>
 spawnTime = spawnTimeReset;<br>
}<br>
</div>
<p>
Alert objects draw a blinking exclamation mark symbol onto the screen before spawning an asteroid. This is done to give the player time to react before each asteroid spawns. Their update function, shown below, is very simple:
</p>
<div class="code">
function oAlertUD()<br>
{<br>
 // Duration<br>
 if ((--this.dur) == 0)<br>
 {<br>
  // Spawn an asteroid, then delete self<br>
  new makeInst(this.x, this.y, 2);<br>
  killInst(this);<br>
 }<br>
<br>
 // Animation<br>
 if ((--this.frames) == 0)<br>
 {<br>
  this.frames = ALERT_FRAMES;<br>
  this.frame1 = !this.frame1;<br>
 }<br>
<br>
 // Drawing<br>
 c.drawImage(this.frame1 ? sAlert1 : sAlert2, this.x, this.y);<br>
}<br>
</div>
<p>
Asteroids are also very simple. They wrap around the screen with the same functions as spaceships, and move similarly to bullets with <b>hsp</b> and <b>vsp</b> values, however, these variables are initialized in <b>makeInst</b> rather than being set by another instance, i.e., spaceship instances setting bullet speeds. Here is their update function:
</p>
<div class="code">
function oAsteroidUD()<br>
{<br>
 // Move<br>
 this.x += this.hsp;<br>
 this.y += this.vsp;<br>
<br>
 // Wrap around world<br>
 this.worldWrap();<br>
<br>
 // Check for collision with a bullet<br>
 var hitby;<br>
 if ((hitby = this.hit(1, this.x - this.halfWidth, this.y - this.halfHeight, 64, 64, 0, 0, 10, 10)) != null)<br>
 {<br>
  if ((--this.hp) == 0)<br>
  {<br>
   ++score;<br>
   killInst(this);<br>
  }<br>
  killInst(hitby);<br>
 }<br>
<br>
 // Draw<br>
 this.drawWrap();<br>
}<br>
</div>
<p>
The only complex piece of code is the collision checking for bullets, which will be explained in the next section.
</p>
<div class="vr-big"></div>
<a href="#collision"><h2 id="collision">Collision</h2></a>
<p>
Collision between object instances is done with the function <b>checkRect</b>, which checks for a collision between two rectangles. It takes 8 parameters: the x, y, width, and height of two different rectangles, and returns a boolean. The only precondition is that the variables for the smaller rectangle is passed first.
</p>
<div class="code">
function checkRect(x1,y1,w1,h1,x2,y2,w2,h2)<br>
{<br>
 var x12 = x1 + w1;<br>
 var y12 = y1 + h1;<br>
 var x22 = x2 + w2;<br>
 var y22 = y2 + h2;<br>
<br>
 if (x1 >= x2 && x1 <= x22)<br>
 {<br>
  //Left Side Hit<br>
  if (y1 >= y2 && y1 <= y22)<br>
  {<br>
   //Top Side Hit<br>
   return true;<br>
  }<br>
  if (y12 >= y2 && y12 <= y22)<br>
  {<br>
   // Bottom Side Hit<br>
   return true;<br>
  }<br>
 }<br>
 else if (x12 >= x2 && x12 <= x22)<br>
 {<br>
  // Right Side Hit<br>
  if (y1 >= y2 && y1 <= y22)<br>
  {<br>
   //Top Side Hit<br>
   return true;<br>
  }<br>
  if (y12 >= y2 && y12 <= y22)<br>
  {<br>
   // Bottom Side Hit<br>
   return true;<br>
  }<br>
 }<br>
 return false;<br>
}<br>
</div>
<p>
This function is used by another two functions: <b>oCollideWithSmaller</b> and <b>oCollideWithLarger</b>. These functions are called by object instances to check for collisions between all instances of another type of object. This is done by looping through <b>inst</b> to find all instances of the object type and calling <b>checkRect</b> with their variables. If a collision is found, the instance collided with is returned, otherwise <b>null</b> is returned. Here is <b>oCollideWithSmaller</b>:
</p>
<div class="code">
function oCollideWithSmaller(o, x1, y1, w1, h1, s1, s2, w2, h2)<br>
{ <br>
 var other; <br>
 for (var i = 0; i < MAX_INSTANCES; ++i)<br>
 {<br>
  other = inst[i];<br>
  if (other != null && other.o == o)<br>
  {<br>
   if (checkRect(other.x + s1, other.y + s2, w2, h2, x1, y1, w1, h1))<br>
    return other;<br>
  }<br>
 }<br>
 return null;<br>
}<br>
</div>
<p>
The function <b>oCollideWithLarger</b> is identical to <b>oCollideWithSmaller</b> except the order of rectangles passed to <b>checkRect</b> is reversed. This is because the functionality of <b>checkRect</b> depends on the smaller rectangle being passed first. The two "collide with" functions exist to check for collisions with objects that are smaller than the calling instance or larger.
</p>
<p>
The reason two "collide with" functions exist despite much of their code being the same is to improve performance. If there was only one "collide with" function that required an extra if statement to determine which set of objects was smaller, performance would suffer, because that if statement would be run for every single instance of the object type being searched for.
</p>
<div class="vr-big"></div>
<a href="#screens"><h2 id="screens">Title/Game/End Screens</h2></a>
<p>
This section covers the code behind the multiple "screens" of the game, those being the title screen, gameplay screen, and game over screen.
</p>
<p>
The title screen is run by calling the function <b>gmTitle</b>. This simply creates a title screen object and starts the game loop.
</p>
<div class="code">
function gmTitle()<br>
{<br>
 new makeInst(0, 0, 4);<br>
 gmLoop();<br>
}<br>
</div>
<p>
The update function of the title object draws the title text and starts the game when enter is pressed by calling <b>gmStart</b>.
</p>
<div class="code">
function oTitleUD()<br>
{<br>
 c.textAlign = "center";<br>
 c.font = "20px Courier";<br>
 c.fillStyle = TEXT_COLOR_GREEN;<br>
 c.fillText("the game finished loading. yay.", GAME_WIDTH_HALF, GAME_HEIGHT_HALF - 120);<br>
 c.fillStyle = TEXT_COLOR_WHITE;<br>
 c.fillText("Bad Asteroid Game", GAME_WIDTH_HALF, GAME_HEIGHT_HALF - 60);<br>
 c.fillStyle = TEXT_COLOR_GREEN;<br>
 c.fillText("by Luke Lawlor", GAME_WIDTH_HALF, GAME_HEIGHT_HALF - 30);<br>
 c.fillText("Press Enter to Play", GAME_WIDTH_HALF, GAME_HEIGHT_HALF + 20);<br>
<br>
 if (k2)<br>
  gmStart();<br>
}<br>
</div>
<p>
The function <b>gmStart</b> destroys all object instances by filling <b>inst</b> with null values, starts the spawning of asteroids, sets the score to <b>0</b>, and creates a spaceship object.
</p>
<div class="code">
function gmStart()<br>
{<br>
 inst.fill(null);<br>
 spawning = true;<br>
 spawnTimeReset = SPAWN_TIME_START;<br>
 spawnTime = 10;<br>
 score = 0;<br>
 new makeInst(100, 80, 0);<br>
}<br>
</div>
<p>
The final screen changing function is <b>gmEnd</b>. This destroys all object instances, records the high score, stops the spawning of asteroids, and creates a game over screen object.
</p>
<div class="code">
function gmEnd()<br>
{<br>
 if (score > highScore)<br>
  highScore = score;<br>
 inst.fill(null);<br>
 spawning = false;<br>
 new makeInst(0, 0, 5);<br>
}<br>
</div>
<p>
The game over screen object draws the game over text and restarts the game by calling <b>gmStart</b>, just as the title screen object does.
</p>
<div class="code">
function oEndUD()<br>
{<br>
 c.textAlign = "center";<br>
 c.font = "20px Courier";<br>
 c.fillStyle = TEXT_COLOR_WHITE;<br>
 c.fillText("G A M E O V E R", GAME_WIDTH_HALF, GAME_HEIGHT_HALF - 60);<br>
 c.fillStyle = TEXT_COLOR_GREEN;<br>
 c.fillText("Score: " + score, GAME_WIDTH_HALF, GAME_HEIGHT_HALF - 30);<br>
 c.fillText("High Score: " + highScore, GAME_WIDTH_HALF, GAME_HEIGHT_HALF - 12);<br>
 c.fillText("Press Enter to Replay", GAME_WIDTH_HALF, GAME_HEIGHT_HALF + 20);<br>
<br>
 if (k2)<br>
  gmStart();<br>
}<br>
</div>
<div class="vr-big"></div>
<div class="center">
You've reached the end of this tutorial-ish thing!<br>
Hopefully you learned something!<br><br>
<a href="index.html">Go back to the game</a>
<div class="vr-big"></div>
<img src="vim.gif"><img src="linux.gif"><img src="firefox.gif">
<br>
<small>Vim good Linux good Firefox somewhat good</small>
</div>
</div></div></div></body>
</html>