littlejsengine 1.11.17 → 1.12.6

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 (73) hide show
  1. package/dist/box2d.wasm.js +630 -0
  2. package/dist/box2d.wasm.wasm +0 -0
  3. package/dist/littlejs.d.ts +158 -81
  4. package/dist/littlejs.esm.js +882 -793
  5. package/dist/littlejs.esm.min.js +1 -1
  6. package/dist/littlejs.js +3048 -175
  7. package/dist/littlejs.min.js +1 -1
  8. package/dist/littlejs.release.js +3047 -172
  9. package/examples/box2d/game.js +82 -56
  10. package/examples/box2d/gameObjects.js +114 -107
  11. package/examples/box2d/index.html +2 -7
  12. package/examples/box2d/scenes.js +72 -78
  13. package/examples/box2d/tiles.png +0 -0
  14. package/examples/breakout/game.js +63 -28
  15. package/examples/breakout/gameObjects.js +45 -47
  16. package/examples/breakout/index.html +1 -5
  17. package/examples/breakoutTutorial/game.js +36 -29
  18. package/examples/breakoutTutorial/index.html +1 -3
  19. package/examples/empty/game.js +5 -2
  20. package/examples/empty/index.html +1 -3
  21. package/examples/htmlMenu/game.js +24 -15
  22. package/examples/htmlMenu/index.html +5 -7
  23. package/examples/index.html +24 -17
  24. package/examples/module/game.js +32 -34
  25. package/examples/module/index.html +1 -1
  26. package/examples/particles/index.html +27 -22
  27. package/examples/platformer/game.js +62 -45
  28. package/examples/platformer/gameCharacter.js +42 -34
  29. package/examples/platformer/gameEffects.js +82 -75
  30. package/examples/platformer/gameLevel.js +67 -38
  31. package/examples/platformer/{gameLevelData.js → gameLevelData.json} +1 -11
  32. package/examples/platformer/gameObjects.js +70 -64
  33. package/examples/platformer/gamePlayer.js +12 -7
  34. package/examples/platformer/index.html +1 -9
  35. package/examples/puzzle/game.js +59 -40
  36. package/examples/puzzle/index.html +1 -3
  37. package/examples/shorts/base.html +3 -2
  38. package/examples/shorts/box2d.js +46 -0
  39. package/examples/shorts/box2dCar.js +49 -0
  40. package/examples/shorts/particles.js +1 -1
  41. package/examples/shorts/platformer.js +1 -1
  42. package/examples/shorts/postProcess.js +45 -0
  43. package/examples/shorts/tileLayer.js +5 -6
  44. package/examples/shorts/tiles.png +0 -0
  45. package/examples/shorts/uiSystem.js +39 -0
  46. package/examples/starter/game.js +9 -10
  47. package/examples/starter/index.html +13 -13
  48. package/examples/stress/index.html +44 -44
  49. package/examples/typescript/build.js +17 -18
  50. package/examples/typescript/game.js +32 -34
  51. package/examples/typescript/game.ts +32 -34
  52. package/examples/typescript/index.html +1 -1
  53. package/examples/uiSystem/game.js +34 -31
  54. package/examples/uiSystem/index.html +1 -5
  55. package/package.json +1 -1
  56. package/plugins/box2d.js +28 -30
  57. package/plugins/pluginExport.js +2 -3
  58. package/plugins/uiSystem.js +9 -3
  59. package/reference.md +29 -27
  60. package/src/engine.js +10 -11
  61. package/src/engineAudio.js +24 -15
  62. package/src/engineBuild.js +25 -8
  63. package/src/engineDebug.js +1 -3
  64. package/src/engineExport.js +3 -6
  65. package/src/engineInput.js +6 -4
  66. package/src/engineObject.js +18 -14
  67. package/src/engineSettings.js +6 -6
  68. package/src/engineTileLayer.js +179 -96
  69. package/src/engineUtilities.js +16 -0
  70. package/src/engineWebGL.js +13 -1
  71. package/examples/typescript/build/dist/littlejs.esm.js +0 -4834
  72. package/examples/typescript/build/examples/typescript/build.js +0 -24
  73. package/examples/typescript/build/examples/typescript/game.js +0 -102
@@ -2,75 +2,47 @@
2
2
  * LittleJS Tile Layer System
3
3
  * - Caches arrays of tiles to off screen canvas for fast rendering
4
4
  * - Unlimited numbers of layers, allocates canvases as needed
5
- * - Interfaces with EngineObject for collision
6
- * - Collision layer is separate from visible layers
7
- * - It is recommended to have a visible layer that matches the collision
8
5
  * - Tile layers can be drawn to using their context with canvas2d
9
6
  * - Drawn directly to the main canvas without using WebGL
7
+ * - Tile layers can also have collision with EngineObjects
10
8
  * @namespace TileCollision
11
9
  */
12
10
 
13
11
  'use strict';
14
12
 
15
- /** The tile collision layer grid, use setTileCollisionData and getTileCollisionData to access
16
- * @type {Array<number>}
17
- * @memberof TileCollision */
18
- let tileCollision = [];
19
-
20
- /** Size of the tile collision layer 2d grid
21
- * @type {Vector2}
22
- * @memberof TileCollision */
23
- let tileCollisionSize = vec2();
13
+ ///////////////////////////////////////////////////////////////////////////////
14
+ // Tile Layer System
24
15
 
25
- /** Clear and initialize tile collision
26
- * @param {Vector2} size - width and height of tile collision 2d grid
16
+ /** Keep track of all tile layers with collision
17
+ * @type {Array<TileCollisionLayer>}
27
18
  * @memberof TileCollision */
28
- function initTileCollision(size)
29
- {
30
- tileCollisionSize = size;
31
- tileCollision = [];
32
- for (let i=tileCollision.length = tileCollisionSize.area(); i--;)
33
- tileCollision[i] = 0;
34
- }
35
-
36
- /** Set tile collision data for a given cell in the grid
37
- * @param {Vector2} pos
38
- * @param {number} [data]
39
- * @memberof TileCollision */
40
- function setTileCollisionData(pos, data=0)
41
- {
42
- pos.arrayCheck(tileCollisionSize) && (tileCollision[(pos.y|0)*tileCollisionSize.x+pos.x|0] = data);
43
- }
19
+ let tileCollisionLayers = [];
44
20
 
45
21
  /** Get tile collision data for a given cell in the grid
46
- * @param {Vector2} pos
47
- * @return {number}
48
- * @memberof TileCollision */
22
+ * @param {Vector2} pos
23
+ * @return {number}
24
+ * @memberof TileCollision */
49
25
  function getTileCollisionData(pos)
50
26
  {
51
- return pos.arrayCheck(tileCollisionSize) ? tileCollision[(pos.y|0)*tileCollisionSize.x+pos.x|0] : 0;
27
+ // check all tile collision layers
28
+ for (const layer of tileCollisionLayers)
29
+ if (pos.arrayCheck(layer.size))
30
+ return layer.getCollisionData(pos);
31
+ return 0;
52
32
  }
53
33
 
54
- /** Check if collision with another object should occur
34
+ /** Check if a tile layer collides with another object
55
35
  * @param {Vector2} pos
56
36
  * @param {Vector2} [size=(0,0)]
57
37
  * @param {EngineObject} [object]
58
- * @return {boolean}
38
+ * @return {TileCollisionLayer}
59
39
  * @memberof TileCollision */
60
40
  function tileCollisionTest(pos, size=vec2(), object)
61
41
  {
62
- const minX = max(pos.x - size.x/2|0, 0);
63
- const minY = max(pos.y - size.y/2|0, 0);
64
- const maxX = min(pos.x + size.x/2, tileCollisionSize.x);
65
- const maxY = min(pos.y + size.y/2, tileCollisionSize.y);
66
- for (let y = minY; y < maxY; ++y)
67
- for (let x = minX; x < maxX; ++x)
68
- {
69
- const tileData = tileCollision[y*tileCollisionSize.x+x];
70
- if (tileData && (!object || object.collideWithTile(tileData, vec2(x, y))))
71
- return true;
72
- }
73
- return false;
42
+ // check all tile collision layers
43
+ for (const layer of tileCollisionLayers)
44
+ if (layer.collisionTest(pos, size, object))
45
+ return layer;
74
46
  }
75
47
 
76
48
  /** Return the center of first tile hit, undefined if nothing was hit.
@@ -82,49 +54,17 @@ function tileCollisionTest(pos, size=vec2(), object)
82
54
  * @memberof TileCollision */
83
55
  function tileCollisionRaycast(posStart, posEnd, object)
84
56
  {
85
- // test if a ray collides with tiles from start to end
86
- // todo: a way to get the exact hit point, it must still be inside the hit tile
87
- const delta = posEnd.subtract(posStart);
88
- const totalLength = delta.length();
89
- const normalizedDelta = delta.normalize();
90
- const unit = vec2(abs(1/normalizedDelta.x), abs(1/normalizedDelta.y));
91
- const flooredPosStart = posStart.floor();
92
-
93
- // setup iteration variables
94
- let pos = flooredPosStart;
95
- let xi = unit.x * (delta.x < 0 ? posStart.x - pos.x : pos.x - posStart.x + 1);
96
- let yi = unit.y * (delta.y < 0 ? posStart.y - pos.y : pos.y - posStart.y + 1);
97
-
98
- while (true)
57
+ // check all tile collision layers
58
+ for (const layer of tileCollisionLayers)
99
59
  {
100
- // check for tile collision
101
- const tileData = getTileCollisionData(pos);
102
- if (tileData && (!object || object.collideWithTile(tileData, pos)))
103
- {
104
- debugRaycast && debugLine(posStart, posEnd, '#f00', .02);
105
- debugRaycast && debugPoint(pos.add(vec2(.5)), '#ff0');
106
- return pos.add(vec2(.5));
107
- }
108
-
109
- // check if past the end
110
- if (xi > totalLength && yi > totalLength)
111
- break;
112
-
113
- // get coordinates of the next tile to check
114
- if (xi > yi)
115
- pos.y += sign(delta.y), yi += unit.y;
116
- else
117
- pos.x += sign(delta.x), xi += unit.x;
60
+ const hitPos = layer.collisionRaycast(posStart, posEnd, object)
61
+ if (hitPos)
62
+ return hitPos;
118
63
  }
119
-
120
- debugRaycast && debugLine(posStart, posEnd, '#00f', .02);
121
64
  }
122
65
 
123
- ///////////////////////////////////////////////////////////////////////////////
124
- // Tile Layer Rendering System
125
-
126
66
  /**
127
- * Tile layer data object stores info about how to render a tile
67
+ * Tile layer data object stores info about how to draw a tile
128
68
  * @example
129
69
  * // create tile layer data with tile index 0 and random orientation and color
130
70
  * const tileIndex = 0;
@@ -156,28 +96,27 @@ class TileLayerData
156
96
  clear() { this.tile = this.direction = 0; this.mirror = false; this.color = new Color; }
157
97
  }
158
98
 
99
+ ///////////////////////////////////////////////////////////////////////////////
159
100
  /**
160
101
  * Tile Layer - cached rendering system for tile layers
161
102
  * - Each Tile layer is rendered to an off screen canvas
162
103
  * - To allow dynamic modifications, layers are rendered using canvas 2d
163
104
  * - Some devices like mobile phones are limited to 4k texture resolution
164
- * - So with 16x16 tiles this limits layers to 256x256 on mobile devices
105
+ * - For with 16x16 tiles this limits layers to 256x256 on mobile devices
165
106
  * @extends EngineObject
166
107
  * @example
167
- * // create tile collision and visible tile layer
168
- * initTileCollision(vec2(200,100));
169
- * const tileLayer = new TileLayer();
108
+ * const tileLayer = new TileLayer(vec2(), vec2(200,100));
170
109
  */
171
110
  class TileLayer extends EngineObject
172
111
  {
173
112
  /** Create a tile layer object
174
- * @param {Vector2} [position=(0,0)] - World space position
175
- * @param {Vector2} [size=tileCollisionSize] - World space size
176
- * @param {TileInfo} [tileInfo] - Tile info for layer
177
- * @param {Vector2} [scale=(1,1)] - How much to scale this layer when rendered
178
- * @param {number} [renderOrder] - Objects are sorted by renderOrder
113
+ * @param {Vector2} [position=(0,0)] - World space position
114
+ * @param {Vector2} [size=(1,1)] - World space size
115
+ * @param {TileInfo} [tileInfo] - Tile info for layer
116
+ * @param {Vector2} [scale=(1,1)] - How much to scale this layer when rendered
117
+ * @param {number} [renderOrder] - Objects are sorted by renderOrder
179
118
  */
180
- constructor(position, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderOrder=0)
119
+ constructor(position, size, tileInfo=tile(), scale=vec2(1), renderOrder=0)
181
120
  {
182
121
  super(position, size, tileInfo, 0, undefined, renderOrder);
183
122
 
@@ -189,6 +128,10 @@ class TileLayer extends EngineObject
189
128
  this.scale = scale;
190
129
  /** @property {boolean} - If true this layer will render to overlay canvas and appear above all objects */
191
130
  this.isOverlay = false;
131
+ // set no friction by default, applied friction is max of both objects
132
+ this.friction = 0;
133
+ // set no elasticity by default, applied elasticity is max of both objects
134
+ this.elasticity = 0;
192
135
 
193
136
  // init tile data
194
137
  this.data = [];
@@ -385,4 +328,144 @@ class TileLayer extends EngineObject
385
328
  * @param {number} [angle=0] */
386
329
  drawRect(pos, size, color, angle)
387
330
  { this.drawTile(pos, size, undefined, color, angle); }
331
+ }
332
+
333
+ ///////////////////////////////////////////////////////////////////////////////
334
+ /**
335
+ * Tile Collision Layer - a tile layer with collision
336
+ * - adds collision data and functions to TileLayer
337
+ * - there can be multiple tile collision layers
338
+ * - tile collison layers should not overlap each other
339
+ * @extends TileLayer
340
+ */
341
+ class TileCollisionLayer extends TileLayer
342
+ {
343
+ /** Create a tile layer object
344
+ * @param {Vector2} [position=(0,0)] - World space position
345
+ * @param {Vector2} [size=(0,0)] - World space size
346
+ * @param {TileInfo} [tileInfo] - Tile info for layer
347
+ * @param {number} [renderOrder] - Objects are sorted by renderOrder
348
+ */
349
+ constructor(position, size, tileInfo=tile(), renderOrder=0)
350
+ {
351
+ const scale = vec2(1); // collision layers are not scaled
352
+ super(position, size.floor(), tileInfo, scale, renderOrder);
353
+
354
+ /** @property {Array<number>} - The tile collision grid */
355
+ this.collisionData = [];
356
+ this.initCollision(this.size);
357
+
358
+ // keep track of all collision layers
359
+ tileCollisionLayers.push(this);
360
+ }
361
+
362
+ /** Destroy this collision layer */
363
+ destroy()
364
+ {
365
+ if (this.destroyed)
366
+ return;
367
+
368
+ // remove from collision layers array and destroy
369
+ const index = tileCollisionLayers.indexOf(this);
370
+ ASSERT(index >= 0, 'tile collision layer not found in array');
371
+ tileCollisionLayers.splice(index, 1);
372
+ super.destroy();
373
+ }
374
+
375
+ /** Clear and initialize tile collision to new size
376
+ * @param {Vector2} size - width and height of tile collision 2d grid */
377
+ initCollision(size)
378
+ {
379
+ this.size = size.floor();
380
+ this.collisionData = [];
381
+ this.collisionData.length = size.area();
382
+ this.collisionData.fill(0);
383
+ }
384
+
385
+ /** Set tile collision data for a given cell in the grid
386
+ * @param {Vector2} pos
387
+ * @param {number} [data] */
388
+ setCollisionData(pos, data=1)
389
+ {
390
+ const i = (pos.y|0)*this.size.x + pos.x|0;
391
+ pos.arrayCheck(this.size) && (this.collisionData[i] = data);
392
+ }
393
+
394
+ /** Get tile collision data for a given cell in the grid
395
+ * @param {Vector2} pos
396
+ * @return {number} */
397
+ getCollisionData(pos)
398
+ {
399
+ const i = (pos.y|0)*this.size.x + pos.x|0;
400
+ return pos.arrayCheck(this.size) ? this.collisionData[i] : 0;
401
+ }
402
+
403
+ /** Check if collision with another object should occur
404
+ * @param {Vector2} pos
405
+ * @param {Vector2} [size=(0,0)]
406
+ * @param {EngineObject} [object]
407
+ * @return {boolean} */
408
+ collisionTest(pos, size=vec2(), object)
409
+ {
410
+ const minX = max(pos.x - size.x/2|0, 0);
411
+ const minY = max(pos.y - size.y/2|0, 0);
412
+ const maxX = min(pos.x + size.x/2, this.size.x);
413
+ const maxY = min(pos.y + size.y/2, this.size.y);
414
+ for (let y = minY; y < maxY; ++y)
415
+ for (let x = minX; x < maxX; ++x)
416
+ {
417
+ // check if the object should collide with this tile
418
+ const tileData = this.collisionData[y*this.size.x+x];
419
+ if (tileData && (!object || object.collideWithTile(tileData, vec2(x, y))))
420
+ return true;
421
+ }
422
+ return false;
423
+ }
424
+
425
+ /** Return the center of first tile hit, undefined if nothing was hit.
426
+ * This does not return the exact intersection, but the center of the tile hit.
427
+ * @param {Vector2} posStart
428
+ * @param {Vector2} posEnd
429
+ * @param {EngineObject} [object]
430
+ * @return {Vector2} */
431
+ collisionRaycast(posStart, posEnd, object)
432
+ {
433
+ // test if a ray collides with tiles from start to end
434
+ // todo: a way to get the exact hit point, it must still be inside the hit tile
435
+ const delta = posEnd.subtract(posStart);
436
+ const totalLength = delta.length();
437
+ const normalizedDelta = delta.normalize();
438
+ const unit = vec2(abs(1/normalizedDelta.x), abs(1/normalizedDelta.y));
439
+ const flooredPosStart = posStart.floor();
440
+
441
+ // setup iteration variables
442
+ let pos = flooredPosStart;
443
+ let xi = unit.x * (delta.x < 0 ? posStart.x - pos.x : pos.x - posStart.x + 1);
444
+ let yi = unit.y * (delta.y < 0 ? posStart.y - pos.y : pos.y - posStart.y + 1);
445
+
446
+ // use line drawing algorithm to test for collisions
447
+ while (true)
448
+ {
449
+ // check for tile collision
450
+ const tileData = this.getCollisionData(pos);
451
+ if (tileData && (!object || object.collideWithTile(tileData, pos)))
452
+ {
453
+ debugRaycast && debugLine(posStart, posEnd, '#f00', .02);
454
+ debugRaycast && debugPoint(pos.add(vec2(.5)), '#ff0');
455
+ return pos.add(vec2(.5));
456
+ }
457
+
458
+ // check if past the end
459
+ if (xi > totalLength && yi > totalLength)
460
+ break;
461
+
462
+ // get coordinates of next tile to check
463
+ if (xi > yi)
464
+ pos.y += sign(delta.y), yi += unit.y;
465
+ else
466
+ pos.x += sign(delta.x), xi += unit.x;
467
+ }
468
+
469
+ debugRaycast && debugLine(posStart, posEnd, '#00f', .02);
470
+ }
388
471
  }
@@ -189,6 +189,16 @@ function wave(frequency=1, amplitude=1, t=time)
189
189
  * @memberof Utilities */
190
190
  function formatTime(t) { return (t/60|0) + ':' + (t%60<10?'0':'') + (t%60|0); }
191
191
 
192
+ /** Fetches a JSON file from a URL and returns the parsed JSON object. Must be used with await!
193
+ * @param {string} url - URL of JSON file
194
+ * @return {Promise<object>}
195
+ * @memberof Utilities */
196
+ async function fetchJSON(url)
197
+ {
198
+ const response = await fetch(url);
199
+ return response.json();
200
+ }
201
+
192
202
  ///////////////////////////////////////////////////////////////////////////////
193
203
 
194
204
  /** Random global functions
@@ -284,6 +294,12 @@ class RandomGenerator
284
294
  /** Randomly returns either -1 or 1 deterministically
285
295
  * @return {number} */
286
296
  sign() { return this.float() > .5 ? 1 : -1; }
297
+
298
+ /** Returns a seeded random value between the two values passed in with a random sign
299
+ * @param {number} [valueA]
300
+ * @param {number} [valueB]
301
+ * @return {number} */
302
+ floatSign(valueA=1, valueB=0) { return this.float(valueA, valueB) * this.sign(); }
287
303
  }
288
304
 
289
305
  ///////////////////////////////////////////////////////////////////////////////
@@ -210,7 +210,19 @@ function glCreateTexture(image)
210
210
  const texture = glContext.createTexture();
211
211
  glContext.bindTexture(glContext.TEXTURE_2D, texture);
212
212
  if (image && image.width)
213
+ {
213
214
  glSetTextureData(texture, image);
215
+
216
+ const isPowerOfTwo = (value)=> !(value & (value - 1));
217
+ if (!tilesPixelated && isPowerOfTwo(image.width) && isPowerOfTwo(image.height))
218
+ {
219
+ // use mipmap filtering
220
+ glContext.generateMipmap(glContext.TEXTURE_2D);
221
+ glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, glContext.LINEAR_MIPMAP_LINEAR);
222
+ glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MAG_FILTER, glContext.LINEAR);
223
+ return texture;
224
+ }
225
+ }
214
226
  else
215
227
  {
216
228
  // create a white texture
@@ -218,7 +230,7 @@ function glCreateTexture(image)
218
230
  glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, 1, 1, 0, glContext.RGBA, glContext.UNSIGNED_BYTE, whitePixel);
219
231
  }
220
232
 
221
- // use point filtering for pixelated rendering
233
+ // set texture filtering
222
234
  const filter = tilesPixelated ? glContext.NEAREST : glContext.LINEAR;
223
235
  glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, filter);
224
236
  glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MAG_FILTER, filter);