littlejsengine 1.14.19 → 1.14.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/littlejs.d.ts +118 -18
  2. package/dist/littlejs.esm.js +506 -199
  3. package/dist/littlejs.esm.min.js +1 -1
  4. package/dist/littlejs.js +493 -199
  5. package/dist/littlejs.min.js +1 -1
  6. package/dist/littlejs.release.js +468 -200
  7. package/examples/electron/game.js +1 -41
  8. package/examples/electron/index.html +2 -2
  9. package/examples/htmlMenu/game.js +1 -1
  10. package/examples/index.html +12 -6
  11. package/examples/module/game.js +5 -7
  12. package/examples/particles/index.html +6 -9
  13. package/examples/platformer/gameCharacter.js +1 -1
  14. package/examples/platformer/gameEffects.js +7 -2
  15. package/examples/platformer/gameObjects.js +3 -3
  16. package/examples/shorts/animation.js +2 -2
  17. package/examples/shorts/base.html +4 -1
  18. package/examples/shorts/cameraDrag.js +22 -0
  19. package/examples/shorts/debugDraw.js +38 -0
  20. package/examples/shorts/fps.js +90 -0
  21. package/examples/shorts/helloWorld.js +1 -1
  22. package/examples/shorts/hillGlideGame.js +1 -1
  23. package/examples/shorts/landerGame.js +1 -1
  24. package/examples/shorts/postProcess.js +1 -1
  25. package/examples/shorts/sequencer.js +3 -3
  26. package/examples/shorts/spaceGame.js +1 -1
  27. package/examples/shorts/spriteAtlas.js +6 -6
  28. package/examples/shorts/starfield.js +1 -1
  29. package/examples/shorts/tileRaycast.js +39 -0
  30. package/examples/shorts/tiles.png +0 -0
  31. package/examples/shorts/tiltedView.js +1 -1
  32. package/examples/starter/game.js +5 -7
  33. package/examples/starter/index.html +2 -2
  34. package/examples/typescript/game.js +3 -2
  35. package/examples/typescript/game.ts +5 -7
  36. package/examples/uiSystem/game.js +2 -2
  37. package/package.json +1 -1
  38. package/src/engine.js +3 -3
  39. package/src/engineAudio.js +13 -13
  40. package/src/engineDebug.js +27 -0
  41. package/src/engineDraw.js +68 -20
  42. package/src/engineExport.js +13 -0
  43. package/src/engineInput.js +14 -16
  44. package/src/engineObject.js +31 -6
  45. package/src/engineParticles.js +36 -15
  46. package/src/engineRelease.js +2 -1
  47. package/src/engineSettings.js +20 -1
  48. package/src/engineTileLayer.js +62 -59
  49. package/src/engineUtilities.js +206 -58
  50. package/src/engineWebGL.js +13 -8
  51. package/examples/shorts/raycasting.js +0 -79
@@ -10,7 +10,7 @@
10
10
 
11
11
  'use strict';
12
12
 
13
- /** A shortcut to get Math.PI
13
+ /** The value of PI
14
14
  * @type {number}
15
15
  * @default Math.PI
16
16
  * @memberof Utilities */
@@ -20,25 +20,80 @@ const PI = Math.PI;
20
20
  * @param {number} value
21
21
  * @return {number}
22
22
  * @memberof Utilities */
23
- function abs(value) { return Math.abs(value); }
23
+ const abs = Math.abs;
24
+
25
+ /** Returns floored value of value passed in
26
+ * @param {number} value
27
+ * @return {number}
28
+ * @memberof Utilities */
29
+ const floor = Math.floor;
30
+
31
+ /** Returns ceiled value of value passed in
32
+ * @param {number} value
33
+ * @return {number}
34
+ * @memberof Utilities */
35
+ const ceil = Math.ceil;
36
+
37
+ /** Returns rounded value passed in
38
+ * @param {number} value
39
+ * @return {number}
40
+ * @memberof Utilities */
41
+ const round = Math.round;
24
42
 
25
43
  /** Returns lowest value passed in
26
44
  * @param {...number} values
27
45
  * @return {number}
28
46
  * @memberof Utilities */
29
- function min(...values) { return Math.min(...values); }
47
+ const min = Math.min;
30
48
 
31
49
  /** Returns highest value passed in
32
50
  * @param {...number} values
33
51
  * @return {number}
34
52
  * @memberof Utilities */
35
- function max(...values) { return Math.max(...values); }
53
+ const max = Math.max;
36
54
 
37
55
  /** Returns the sign of value passed in
38
56
  * @param {number} value
39
57
  * @return {number}
40
58
  * @memberof Utilities */
41
- function sign(value) { return Math.sign(value); }
59
+ const sign = Math.sign;
60
+
61
+ /** Returns hypotenuse of values passed in
62
+ * @param {...number} values
63
+ * @return {number}
64
+ * @memberof Utilities */
65
+ const hypot = Math.hypot;
66
+
67
+ /** Returns log2 of value passed in
68
+ * @param {number} value
69
+ * @return {number}
70
+ * @memberof Utilities */
71
+ const log2 = Math.log2;
72
+
73
+ /** Returns sin of value passed in
74
+ * @param {number} value
75
+ * @return {number}
76
+ * @memberof Utilities */
77
+ const sin = Math.sin;
78
+
79
+ /** Returns cos of value passed in
80
+ * @param {number} value
81
+ * @return {number}
82
+ * @memberof Utilities */
83
+ const cos = Math.cos;
84
+
85
+ /** Returns tan of value passed in
86
+ * @param {number} value
87
+ * @return {number}
88
+ * @memberof Utilities */
89
+ const tan = Math.tan;
90
+
91
+ /** Returns atan2 of values passed in
92
+ * @param {number} y
93
+ * @param {number} x
94
+ * @return {number}
95
+ * @memberof Utilities */
96
+ const atan2 = Math.atan2;
42
97
 
43
98
  /** Returns first parm modulo the second param, but adjusted so negative numbers work as expected
44
99
  * @param {number} dividend
@@ -143,7 +198,7 @@ function isPowerOfTwo(value) { return !(value & (value - 1)); }
143
198
  * @param {number} value
144
199
  * @return {number}
145
200
  * @memberof Utilities */
146
- function nearestPowerOfTwo(value) { return 2**Math.ceil(Math.log2(value)); }
201
+ function nearestPowerOfTwo(value) { return 2**ceil(log2(value)); }
147
202
 
148
203
  /** Returns true if two axis aligned bounding boxes are overlapping
149
204
  * this can be used for simple collision detection between objects
@@ -211,7 +266,7 @@ function isIntersecting(start, end, pos, size)
211
266
  * @return {number} - Value waving between 0 and amplitude
212
267
  * @memberof Utilities */
213
268
  function wave(frequency=1, amplitude=1, t=time, offset=0)
214
- { return amplitude/2 * (1 - Math.cos(offset + t*frequency*2*PI)); }
269
+ { return amplitude/2 * (1 - cos(offset + t*frequency*2*PI)); }
215
270
 
216
271
  /** Formats seconds to mm:ss style for display purposes
217
272
  * @param {number} t - time in seconds
@@ -250,6 +305,96 @@ function isNumber(n) { return typeof n === 'number' && !isNaN(n); }
250
305
  * @memberof Utilities */
251
306
  function isString(s) { return s !== undefined && s !== null && typeof s.toString() === 'string'; }
252
307
 
308
+ /**
309
+ * @callback LineTestFunction - Checks if a position is colliding
310
+ * @param {Vector2} pos
311
+ * @memberof Draw
312
+ */
313
+
314
+ /**
315
+ * Casts a ray and returns position of the first collision found, or undefined if none are found
316
+ * @param {Vector2} posStart
317
+ * @param {Vector2} posEnd
318
+ * @param {LineTestFunction} testFunction - Check if colliding
319
+ * @param {Vector2} [normal] - Optional vector to store the normal
320
+ * @return {Vector2|undefined} - Position of the collision or undefined if none found
321
+ * @memberof Utilities */
322
+ function lineTest(posStart, posEnd, testFunction, normal)
323
+ {
324
+ ASSERT(isVector2(posStart), 'posStart must be a vec2');
325
+ ASSERT(isVector2(posEnd), 'posEnd must be a vec2');
326
+ ASSERT(typeof testFunction === 'function', 'testFunction must be a function');
327
+ ASSERT(!normal || isVector2(normal), 'normal must be a vec2');
328
+
329
+ // get ray direction and length
330
+ const dx = posEnd.x - posStart.x;
331
+ const dy = posEnd.y - posStart.y;
332
+ const totalLength = hypot(dx, dy);
333
+ if (!totalLength)
334
+ return;
335
+
336
+ // current integer cell we are in
337
+ const pos = posStart.floor();
338
+
339
+ // normalize ray direction
340
+ const dirX = dx / totalLength;
341
+ const dirY = dy / totalLength;
342
+
343
+ // step direction in grid
344
+ const stepX = sign(dirX);
345
+ const stepY = sign(dirY);
346
+
347
+ // distance along the ray to cross one full cell in X or Y
348
+ const tDeltaX = dirX ? abs(1 / dirX) : Infinity;
349
+ const tDeltaY = dirY ? abs(1 / dirY) : Infinity;
350
+
351
+ // distance along the ray from start to the first grid boundary
352
+ const nextGridX = stepX > 0 ? pos.x + 1 : pos.x;
353
+ const nextGridY = stepY > 0 ? pos.y + 1 : pos.y;
354
+ const tMaxX = dirX ? (nextGridX - posStart.x) / dirX : Infinity;
355
+ const tMaxY = dirY ? (nextGridY - posStart.y) / dirY : Infinity;
356
+
357
+ // use line drawing algorithm to test for collisions
358
+ let t = 0, tX = tMaxX, tY = tMaxY, wasX = tDeltaX < tDeltaY;
359
+ while (t < totalLength)
360
+ {
361
+ if (testFunction(pos))
362
+ {
363
+ // set hit point
364
+ const hitPos = vec2(posStart.x + dirX*t, posStart.y + dirY*t);
365
+
366
+ // move inside of tile if on positive edge
367
+ const e = 1e-9;
368
+ if (wasX)
369
+ {
370
+ if (stepX < 0)
371
+ hitPos.x -= e;
372
+ }
373
+ if (stepY < 0)
374
+ hitPos.y -= e;
375
+
376
+ // set normal
377
+ if (normal)
378
+ wasX ? normal.set(-stepX,0) : normal.set(0,-stepY);
379
+ return hitPos;
380
+ }
381
+
382
+ // advance to the next grid boundary
383
+ if (wasX = tX < tY)
384
+ {
385
+ pos.x += stepX;
386
+ t = tX;
387
+ tX += tDeltaX;
388
+ }
389
+ else
390
+ {
391
+ pos.y += stepY;
392
+ t = tY;
393
+ tY += tDeltaY;
394
+ }
395
+ }
396
+ }
397
+
253
398
  ///////////////////////////////////////////////////////////////////////////////
254
399
 
255
400
  /** Random global functions
@@ -268,7 +413,7 @@ function rand(valueA=1, valueB=0) { return valueB + Math.random() * (valueA-valu
268
413
  * @param {number} [valueB]
269
414
  * @return {number}
270
415
  * @memberof Random */
271
- function randInt(valueA, valueB=0) { return Math.floor(rand(valueA,valueB)); }
416
+ function randInt(valueA, valueB=0) { return floor(rand(valueA,valueB)); }
272
417
 
273
418
  /** Randomly returns true or false given the chance of true passed in
274
419
  * @param {number} [chance]
@@ -347,7 +492,7 @@ class RandomGenerator
347
492
  * @param {number} valueA
348
493
  * @param {number} [valueB]
349
494
  * @return {number} */
350
- int(valueA, valueB=0) { return Math.floor(this.float(valueA, valueB)); }
495
+ int(valueA, valueB=0) { return floor(this.float(valueA, valueB)); }
351
496
 
352
497
  /** Randomly returns true or false given the chance of true passed in
353
498
  * @param {number} [chance]
@@ -374,6 +519,39 @@ class RandomGenerator
374
519
  * @return {Vector2} */
375
520
  vec2(valueA=1, valueB=0)
376
521
  { return vec2(this.float(valueA, valueB), this.float(valueA, valueB)); }
522
+
523
+ /** Returns a random color between the two passed in colors, combine components if linear
524
+ * @param {Color} [colorA=(1,1,1,1)]
525
+ * @param {Color} [colorB=(0,0,0,1)]
526
+ * @param {boolean} [linear]
527
+ * @return {Color} */
528
+ randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear=false)
529
+ {
530
+ return linear ? colorA.lerp(colorB, this.float()) :
531
+ new Color(
532
+ this.float(colorA.r,colorB.r),
533
+ this.float(colorA.g,colorB.g),
534
+ this.float(colorA.b,colorB.b),
535
+ this.float(colorA.a,colorB.a));
536
+ }
537
+
538
+ /** Returns a new color that has each component randomly adjusted
539
+ * @param {Color} color
540
+ * @param {number} [amount]
541
+ * @param {number} [alphaAmount]
542
+ * @return {Color} */
543
+ mutateColor(color, amount=.05, alphaAmount=0)
544
+ {
545
+ ASSERT_NUMBER_VALID(amount);
546
+ ASSERT_NUMBER_VALID(alphaAmount);
547
+ return new Color
548
+ (
549
+ color.r + this.float(amount, -amount),
550
+ color.g + this.float(amount, -amount),
551
+ color.b + this.float(amount, -amount),
552
+ color.a + this.float(alphaAmount, -alphaAmount)
553
+ ).clamp();
554
+ }
377
555
  }
378
556
 
379
557
  ///////////////////////////////////////////////////////////////////////////////
@@ -525,7 +703,7 @@ class Vector2
525
703
 
526
704
  /** Returns the clockwise angle of this vector, up is angle 0
527
705
  * @return {number} */
528
- angle() { return Math.atan2(this.x, this.y); }
706
+ angle() { return atan2(this.x, this.y); }
529
707
 
530
708
  /** Sets this vector with clockwise angle and length passed in
531
709
  * @param {number} [angle]
@@ -535,8 +713,8 @@ class Vector2
535
713
  {
536
714
  ASSERT_NUMBER_VALID(angle);
537
715
  ASSERT_NUMBER_VALID(length);
538
- this.x = length*Math.sin(angle);
539
- this.y = length*Math.cos(angle);
716
+ this.x = length*sin(angle);
717
+ this.y = length*cos(angle);
540
718
  return this;
541
719
  }
542
720
 
@@ -546,7 +724,7 @@ class Vector2
546
724
  rotate(angle)
547
725
  {
548
726
  ASSERT_NUMBER_VALID(angle);
549
- const c = Math.cos(-angle), s = Math.sin(-angle);
727
+ const c = cos(-angle), s = sin(-angle);
550
728
  return new Vector2(this.x*c - this.y*s, this.x*s + this.y*c);
551
729
  }
552
730
 
@@ -578,7 +756,7 @@ class Vector2
578
756
 
579
757
  /** Returns a copy of this vector with each axis floored
580
758
  * @return {Vector2} */
581
- floor() { return new Vector2(Math.floor(this.x), Math.floor(this.y)); }
759
+ floor() { return new Vector2(floor(this.x), floor(this.y)); }
582
760
 
583
761
  /** Returns new vec2 with modded values
584
762
  * @param {number} [divisor]
@@ -886,67 +1064,67 @@ class Color
886
1064
  /** Color - White #ffffff
887
1065
  * @type {Color}
888
1066
  * @memberof Utilities */
889
- const WHITE = protectEngineConstant(rgb());
1067
+ const WHITE = debugProtectConstant(rgb());
890
1068
 
891
1069
  /** Color - Clear White #757474ff with 0 alpha
892
1070
  * @type {Color}
893
1071
  * @memberof Utilities */
894
- const CLEAR_WHITE = protectEngineConstant(rgb(1,1,1,0));
1072
+ const CLEAR_WHITE = debugProtectConstant(rgb(1,1,1,0));
895
1073
 
896
1074
  /** Color - Black #000000
897
1075
  * @type {Color}
898
1076
  * @memberof Utilities */
899
- const BLACK = protectEngineConstant(rgb(0,0,0));
1077
+ const BLACK = debugProtectConstant(rgb(0,0,0));
900
1078
 
901
1079
  /** Color - Clear Black #000000 with 0 alpha
902
1080
  * @type {Color}
903
1081
  * @memberof Utilities */
904
- const CLEAR_BLACK = protectEngineConstant(rgb(0,0,0,0));
1082
+ const CLEAR_BLACK = debugProtectConstant(rgb(0,0,0,0));
905
1083
 
906
1084
  /** Color - Gray #808080
907
1085
  * @type {Color}
908
1086
  * @memberof Utilities */
909
- const GRAY = protectEngineConstant(rgb(.5,.5,.5));
1087
+ const GRAY = debugProtectConstant(rgb(.5,.5,.5));
910
1088
 
911
1089
  /** Color - Red #ff0000
912
1090
  * @type {Color}
913
1091
  * @memberof Utilities */
914
- const RED = protectEngineConstant(rgb(1,0,0));
1092
+ const RED = debugProtectConstant(rgb(1,0,0));
915
1093
 
916
1094
  /** Color - Orange #ff8000
917
1095
  * @type {Color}
918
1096
  * @memberof Utilities */
919
- const ORANGE = protectEngineConstant(rgb(1,.5,0));
1097
+ const ORANGE = debugProtectConstant(rgb(1,.5,0));
920
1098
 
921
1099
  /** Color - Yellow #ffff00
922
1100
  * @type {Color}
923
1101
  * @memberof Utilities */
924
- const YELLOW = protectEngineConstant(rgb(1,1,0));
1102
+ const YELLOW = debugProtectConstant(rgb(1,1,0));
925
1103
 
926
1104
  /** Color - Green #00ff00
927
1105
  * @type {Color}
928
1106
  * @memberof Utilities */
929
- const GREEN = protectEngineConstant(rgb(0,1,0));
1107
+ const GREEN = debugProtectConstant(rgb(0,1,0));
930
1108
 
931
1109
  /** Color - Cyan #00ffff
932
1110
  * @type {Color}
933
1111
  * @memberof Utilities */
934
- const CYAN = protectEngineConstant(rgb(0,1,1));
1112
+ const CYAN = debugProtectConstant(rgb(0,1,1));
935
1113
 
936
1114
  /** Color - Blue #0000ff
937
1115
  * @type {Color}
938
1116
  * @memberof Utilities */
939
- const BLUE = protectEngineConstant(rgb(0,0,1));
1117
+ const BLUE = debugProtectConstant(rgb(0,0,1));
940
1118
 
941
1119
  /** Color - Purple #8000ff
942
1120
  * @type {Color}
943
1121
  * @memberof Utilities */
944
- const PURPLE = protectEngineConstant(rgb(.5,0,1));
1122
+ const PURPLE = debugProtectConstant(rgb(.5,0,1));
945
1123
 
946
1124
  /** Color - Magenta #ff00ff
947
1125
  * @type {Color}
948
1126
  * @memberof Utilities */
949
- const MAGENTA = protectEngineConstant(rgb(1,0,1));
1127
+ const MAGENTA = debugProtectConstant(rgb(1,0,1));
950
1128
 
951
1129
  ///////////////////////////////////////////////////////////////////////////////
952
1130
 
@@ -1009,39 +1187,9 @@ class Timer
1009
1187
 
1010
1188
  /** Returns this timer expressed as a string
1011
1189
  * @return {string} */
1012
- toString() { return this.isSet() ? Math.abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ) : 'unset'; }
1190
+ toString() { return this.isSet() ? abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ) : 'unset'; }
1013
1191
 
1014
1192
  /** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
1015
1193
  * @return {number} */
1016
1194
  valueOf() { return this.get(); }
1017
- }
1018
-
1019
- ///////////////////////////////////////////////////////////////////////////////
1020
- // Helper functions used by the engine
1021
-
1022
- // make color constants immutable with debug assertions
1023
- function protectEngineConstant(obj)
1024
- {
1025
- if (debug)
1026
- {
1027
- // get properties and store original values
1028
- const props = Object.keys(obj), values = {};
1029
- props.forEach(prop => values[prop] = obj[prop]);
1030
-
1031
- // replace with getters/setters that assert
1032
- props.forEach(prop =>
1033
- {
1034
- Object.defineProperty(obj, prop, {
1035
- get: () => values[prop],
1036
- set: (value) =>
1037
- {
1038
- ASSERT(false, `Cannot modify engine constant. Attempted to set constant (${obj}) property '${prop}' to '${value}'.`);
1039
- },
1040
- enumerable: true
1041
- });
1042
- });
1043
- }
1044
-
1045
- // freeze the object to prevent adding new properties
1046
- return Object.freeze(obj);
1047
1195
  }
@@ -29,7 +29,7 @@ let glContext;
29
29
  let glAntialias = true;
30
30
 
31
31
  // WebGL internal variables not exposed to documentation
32
- let glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount, glTextureInfos;
32
+ let glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount, glTextureInfos, glCanBeEnabled = true;
33
33
 
34
34
  // WebGL internal constants
35
35
  const gl_ARRAY_BUFFER_SIZE = 5e5;
@@ -48,7 +48,11 @@ function glInit()
48
48
  // keep set of texture infos so they can be restored if context is lost
49
49
  glTextureInfos = new Set;
50
50
 
51
- if (!glEnable || headlessMode) return;
51
+ if (!glEnable || headlessMode)
52
+ {
53
+ glCanBeEnabled = false;
54
+ return;
55
+ }
52
56
 
53
57
  // create the canvas and textures
54
58
  glCanvas = document.createElement('canvas');
@@ -59,10 +63,11 @@ function glInit()
59
63
  console.warn('WebGL2 not supported, falling back to 2D canvas rendering!');
60
64
  glCanvas = glContext = undefined;
61
65
  glEnable = false;
66
+ glCanBeEnabled = false;
62
67
  return;
63
68
  }
64
69
 
65
- // create the WebGL canvas
70
+ // attach the WebGL canvas
66
71
  const rootElement = mainCanvas.parentElement;
67
72
  rootElement.appendChild(glCanvas);
68
73
 
@@ -85,7 +90,7 @@ function glInit()
85
90
  });
86
91
  glCanvas.addEventListener('webglcontextrestored', ()=>
87
92
  {
88
- glEnable = true; // disable WebGL rendering
93
+ glEnable = true; // re-enable WebGL rendering
89
94
  glCanvas.style.display = ''; // show the gl canvas
90
95
  LOG('WebGL context restored, reinitializing...');
91
96
 
@@ -237,8 +242,8 @@ function glPreRender()
237
242
  const s = vec2(2*cameraScale).divide(mainCanvasSize);
238
243
  const rotatedCam = cameraPos.rotate(-cameraAngle);
239
244
  const p = vec2(-1).subtract(rotatedCam.multiply(s));
240
- const ca = Math.cos(cameraAngle);
241
- const sa = Math.sin(cameraAngle);
245
+ const ca = cos(cameraAngle);
246
+ const sa = sin(cameraAngle);
242
247
  const transform = [
243
248
  s.x * ca, s.y * sa, 0, 0,
244
249
  -s.x * sa, s.y * ca, 0, 0,
@@ -543,8 +548,8 @@ function glDrawPointsTransform(points, rgba, x, y, sx, sy, angle, tristrip=true)
543
548
  // transform the point
544
549
  const px = p.x*sx;
545
550
  const py = p.y*sy;
546
- const sa = Math.sin(-angle);
547
- const ca = Math.cos(-angle);
551
+ const sa = sin(-angle);
552
+ const ca = cos(-angle);
548
553
  pointsOut.push(vec2(x + ca*px - sa*py, y + sa*px + ca*py));
549
554
  }
550
555
  const drawPoints = tristrip ? glPolyStrip(pointsOut) : pointsOut;
@@ -1,79 +0,0 @@
1
- let playerPos = vec2(), playerAngle = 0;
2
-
3
- // a simple function to generate a level
4
- const levelTest = (X,Y)=> ((X|0)**3&(Y|0)**2)%30>5;
5
-
6
- // cast a ray and return the distance to the first wall hit (0-1)
7
- function raycast(pos, angle)
8
- {
9
- const maxDistance = 40, delta = .05;
10
- const rayX = delta*Math.sin(angle);
11
- const rayY = delta*Math.cos(angle);
12
- let posX = pos.x, posY = pos.y;
13
- for (let distance = 0; distance < maxDistance; distance += delta)
14
- {
15
- if (levelTest(posX, posY))
16
- return distance/maxDistance;
17
- posX += rayX;
18
- posY += rayY;
19
- }
20
- return 1;
21
- }
22
-
23
- function gameUpdate()
24
- {
25
- // update camera angle with mouse pointer lock
26
- if (mouseWasPressed(0))
27
- pointerLockRequest();
28
- if (keyWasPressed('Escape'))
29
- pointerLockExit()
30
- if (pointerLockIsActive() || isTouchDevice)
31
- playerAngle += mouseDelta.x * .003;
32
-
33
- // update player movement, prevent walking through walls
34
- let velocity = keyDirection();
35
- if (velocity.y && mouseIsDown(0))
36
- velocity.y = 1; // forward movement with mouse
37
- velocity = velocity.rotate(playerAngle).scale(.1);
38
- const newPos = playerPos.add(velocity);
39
- if (!levelTest(newPos.x, newPos.y))
40
- playerPos = newPos;
41
- }
42
-
43
- function gameRender()
44
- {
45
- {
46
- // draw horizontal slizes to create floor and ceiling
47
- const h = 9;
48
- let pos = vec2(), size = vec2(39, .15), color = rgb();
49
- for (let y=-h; y<h; y+=.1)
50
- {
51
- const p = 1.01-abs(y/h)
52
- pos.y = y;
53
- color.setHSLA(.1, y>0?0:.5, .7-p*.7);
54
- drawRect(pos, size, color);
55
- }
56
- }
57
- {
58
- // draw vertical slices to create the walls
59
- const w = 20;
60
- const pos = vec2(), size = vec2(.15), color = rgb();
61
- for (let x=-w; x<w; x+=.1)
62
- {
63
- const p = raycast(playerPos, playerAngle + x/w);
64
- pos.x = x;
65
- size.y = .4/p;
66
- color.setHSLA(.6, .5, .7-p*.7);
67
- drawRect(pos, size, color);
68
- }
69
- }
70
- // draw instructions and pointer lock status
71
- const instructions = pointerLockIsActive() ?
72
- 'Mouse Control Active - ESC To Exit' :
73
- 'Click To Enable Mouse Control';
74
- const textPos = vec2(mainCanvasSize.x/2, 50);
75
- const textSize = 24;
76
- const textColor = pointerLockIsActive() ? GREEN : WHITE;
77
- if (!isTouchDevice)
78
- drawTextScreen(instructions, textPos, textSize, textColor);
79
- }