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
@@ -152,6 +152,12 @@ class ParticleEmitter extends EngineObject
152
152
  this.particleCreateCallback = undefined;
153
153
  /** @property {number} - Track particle emit time */
154
154
  this.emitTimeBuffer = 0;
155
+ /** @property {number} - Percentage of velocity to pass to particles (0-1) */
156
+ this.velocityInheritance = 0;
157
+
158
+ // track previous position and angle
159
+ this.previousAngle = this.angle;
160
+ this.previousPos = this.pos.copy();
155
161
  }
156
162
 
157
163
  /** Update the emitter to spawn particles, called automatically by engine once each frame */
@@ -160,6 +166,18 @@ class ParticleEmitter extends EngineObject
160
166
  // only do default update to apply parent transforms
161
167
  this.parent && super.update();
162
168
 
169
+ if (this.velocityInheritance)
170
+ {
171
+ // pass emitter velocity to particles
172
+ const p = this.velocityInheritance;
173
+ this.velocity.x = p * (this.pos.x - this.previousPos.x);
174
+ this.velocity.y = p * (this.pos.y - this.previousPos.y);
175
+ this.angleVelocity = p * (this.angle - this.previousAngle);
176
+ this.previousAngle = this.angle;
177
+ this.previousPos.x = this.pos.x;
178
+ this.previousPos.y = this.pos.y;
179
+ }
180
+
163
181
  // update emitter
164
182
  if (!this.emitTime || this.getAliveTime() <= this.emitTime)
165
183
  {
@@ -217,7 +235,7 @@ class ParticleEmitter extends EngineObject
217
235
  const particle = new Particle(pos, this.tileInfo, angle, colorStart, colorEnd, particleTime, sizeStart, sizeEnd, this.fadeRate, this.additive, this.trailScale, this.localSpace && this, this.particleDestroyCallback);
218
236
  particle.velocity = vec2().setAngle(velocityAngle, speed);
219
237
  particle.angleVelocity = angleSpeed;
220
- if (!this.localSpace)
238
+ if (!this.localSpace && this.velocityInheritance > 0)
221
239
  {
222
240
  // apply emitter velocity to particle
223
241
  particle.velocity.x += this.velocity.x;
@@ -315,6 +333,16 @@ class Particle extends EngineObject
315
333
  this.velocity.y *= s;
316
334
  }
317
335
  }
336
+
337
+ if (this.lifeTime > 0 && time - this.spawnTime > this.lifeTime)
338
+ {
339
+ // destroy particle when its time runs out
340
+ const c = this.colorEnd;
341
+ this.color.set(c.r, c.g, c.b, c.a);
342
+ this.size.set(this.sizeEnd, this.sizeEnd);
343
+ this.destroyCallback && this.destroyCallback(this);
344
+ this.destroyed = 1;
345
+ }
318
346
  }
319
347
 
320
348
  /** Render the particle, automatically called each frame, sorted by renderOrder */
@@ -323,7 +351,7 @@ class Particle extends EngineObject
323
351
  // lerp color and size
324
352
  const p1 = this.lifeTime > 0 ? min((time - this.spawnTime) / this.lifeTime, 1) : 1, p2 = 1-p1;
325
353
  const radius = p2 * this.sizeStart + p1 * this.sizeEnd;
326
- this.size.x = this.size.y = radius;
354
+ const size = vec2(radius);
327
355
  this.color.r = p2 * this.colorStart.r + p1 * this.colorEnd.r;
328
356
  this.color.g = p2 * this.colorStart.g + p1 * this.colorEnd.g;
329
357
  this.color.b = p2 * this.colorStart.b + p1 * this.colorEnd.b;
@@ -343,7 +371,7 @@ class Particle extends EngineObject
343
371
  {
344
372
  // in local space of emitter
345
373
  const a = this.localSpaceEmitter.angle;
346
- const c = Math.cos(a), s = Math.sin(a);
374
+ const c = cos(a), s = sin(a);
347
375
  pos = this.localSpaceEmitter.pos.add(
348
376
  new Vector2(pos.x*c - pos.y*s, pos.x*s + pos.y*c));
349
377
  angle += this.localSpaceEmitter.angle;
@@ -359,21 +387,14 @@ class Particle extends EngineObject
359
387
  {
360
388
  // stretch in direction of motion
361
389
  const trailLength = speed * this.trailScale;
362
- this.size.y = max(this.size.x, trailLength);
363
- angle = Math.atan2(direction.x, direction.y);
364
- drawTile(pos, this.size, this.tileInfo, this.color, angle, this.mirror);
390
+ size.y = max(size.x, trailLength);
391
+ angle = atan2(direction.x, direction.y);
392
+ drawTile(pos, size, this.tileInfo, this.color, angle, this.mirror);
365
393
  }
366
394
  }
367
395
  else
368
- drawTile(pos, this.size, this.tileInfo, this.color, angle, this.mirror);
396
+ drawTile(pos, size, this.tileInfo, this.color, angle, this.mirror);
369
397
  this.additive && setBlendMode();
370
- debugParticles && debugRect(pos, this.size, '#f005', 0, angle);
371
-
372
- if (p1 === 1)
373
- {
374
- // destroy particle when its time runs out
375
- this.destroyCallback && this.destroyCallback(this);
376
- this.destroyed = 1;
377
- }
398
+ debugParticles && debugRect(pos, size, '#f005', 0, angle);
378
399
  }
379
400
  }
@@ -38,4 +38,5 @@ function debugShowErrors(){}
38
38
  function debugVideoCaptureIsActive(){ return false; }
39
39
  function debugVideoCaptureStart (){}
40
40
  function debugVideoCaptureStop (){}
41
- function debugVideoCaptureUpdate(){}
41
+ function debugVideoCaptureUpdate(){}
42
+ function debugProtectConstant(o){ return o; }
@@ -216,6 +216,14 @@ let touchInputEnable = true;
216
216
  * @memberof Settings */
217
217
  let touchGamepadEnable = false;
218
218
 
219
+ /** True if touch gamepad should have start button in the center
220
+ * - When the game is paused, any touch will press the button
221
+ * - This can function as a way to pause/unpause the game
222
+ * @type {boolean}
223
+ * @default
224
+ * @memberof Settings */
225
+ let touchGamepadCenterButton = true;
226
+
219
227
  /** True if touch gamepad should be analog stick or false to use if 8 way dpad
220
228
  * @type {boolean}
221
229
  * @default
@@ -391,9 +399,14 @@ function setHeadlessMode(headless) { headlessMode = headless; }
391
399
  * @memberof Settings */
392
400
  function setGLEnable(enable)
393
401
  {
402
+ if (enable && !glCanBeEnabled)
403
+ {
404
+ console.warn('Can not enable WebGL if it was disabled on start.');
405
+ return;
406
+ }
394
407
  glEnable = enable;
395
408
  if (glCanvas) // hide glCanvas if WebGL is disabled
396
- glCanvas.style.visibility = enable ? 'visible' : 'hidden';
409
+ glCanvas.style.display = enable ? '' : 'none';
397
410
  }
398
411
 
399
412
  /** Set how many sided polygons to use when drawing circles and ellipses with WebGL
@@ -481,6 +494,12 @@ function setTouchInputEnable(enable) { touchInputEnable = enable; }
481
494
  * @memberof Settings */
482
495
  function setTouchGamepadEnable(enable) { touchGamepadEnable = enable; }
483
496
 
497
+ /** True if touch gamepad should have start button in the center
498
+ * - This can function as a way to pause/unpause the game
499
+ * @param {boolean} enable
500
+ * @memberof Settings */
501
+ function setTouchGamepadCenterButton(enable) { touchGamepadCenterButton = enable; }
502
+
484
503
  /** Set if touch gamepad should be analog stick or 8 way dpad
485
504
  * @param {boolean} analog
486
505
  * @memberof Settings */
@@ -47,23 +47,22 @@ function tileCollisionTest(pos, size=vec2(), object, solidOnly=true)
47
47
  }
48
48
  }
49
49
 
50
- /** Return the center of first tile hit, undefined if nothing was hit.
51
- * This does not return the exact intersection, but the center of the tile hit.
50
+ /** Return the exact position of the boudnary of first tile hit, undefined if nothing was hit.
52
51
  * @param {Vector2} posStart
53
52
  * @param {Vector2} posEnd
54
53
  * @param {EngineObject} [object] - An object or undefined for generic test
54
+ * @param {Vector2} [normal] - Optional normal of the surface hit
55
55
  * @param {boolean} [solidOnly=true] - Only check solid layers if true
56
- * @return {Vector2}
56
+ * @return {Vector2|undefined} - position of the center of the tile hit or undefined if no hit
57
57
  * @memberof TileLayers */
58
- function tileCollisionRaycast(posStart, posEnd, object, solidOnly=true)
58
+ function tileCollisionRaycast(posStart, posEnd, object, normal, solidOnly=true)
59
59
  {
60
60
  for (const layer of tileCollisionLayers)
61
61
  {
62
62
  if (!solidOnly || layer.isSolid)
63
63
  {
64
- const hitPos = layer.collisionRaycast(posStart, posEnd, object)
65
- if (hitPos)
66
- return hitPos;
64
+ const hitPos = layer.collisionRaycast(posStart, posEnd, object, normal)
65
+ if (hitPos) return hitPos;
67
66
  }
68
67
  }
69
68
  }
@@ -201,6 +200,8 @@ class CanvasLayer extends EngineObject
201
200
  /** @property {TextureInfo} - Texture info to use for this object rendering */
202
201
  const useWebGL = false; // do not use webgl by default
203
202
  this.textureInfo = new TextureInfo(this.canvas, useWebGL);
203
+ /** @property {boolean} - True if WebGL texture needs to be refreshed */
204
+ this.refreshWebGL = false;
204
205
 
205
206
  // disable physics by default
206
207
  this.mass = this.gravityScale = this.friction = this.restitution = 0;
@@ -234,8 +235,15 @@ class CanvasLayer extends EngineObject
234
235
  * @memberof Draw */
235
236
  draw(pos, size, angle=0, color=WHITE, mirror=false, additiveColor, screenSpace=false, context)
236
237
  {
237
- // draw the canvas layer as a single tile that uses the whole texture
238
238
  const useWebGL = glEnable && this.textureInfo.hasWebGL();
239
+ if (useWebGL && this.refreshWebGL)
240
+ {
241
+ // update the WebGL texture
242
+ this.textureInfo.createWebGLTexture();
243
+ this.refreshWebGL = false;
244
+ }
245
+
246
+ // draw the canvas layer as a single tile that uses the whole texture
239
247
  const tileInfo = new TileInfo().setFullImage(this.textureInfo);
240
248
  drawTile(pos, size, tileInfo, color, angle, mirror, additiveColor, useWebGL, screenSpace, context);
241
249
  }
@@ -305,9 +313,18 @@ class CanvasLayer extends EngineObject
305
313
  { this.drawTile(pos, size, undefined, color, angle); }
306
314
 
307
315
  /** Create or update the WebGL texture for this layer
308
- * @param {boolean} [enable] - enable WebGL rendering and update the texture */
309
- useWebGL(enable=true)
316
+ * @param {boolean} [enable] - enable WebGL rendering and update the texture
317
+ * @param {boolean} [immediate] - shoulkd the texture be updated immediately
318
+ */
319
+ useWebGL(enable=true, immediate=false)
310
320
  {
321
+ if (!immediate && enable && this.textureInfo.hasWebGL())
322
+ {
323
+ // refresh the texture when needed
324
+ this.refreshWebGL = true;
325
+ return;
326
+ }
327
+
311
328
  if (enable)
312
329
  this.textureInfo.createWebGLTexture();
313
330
  else
@@ -338,7 +355,7 @@ class TileLayer extends CanvasLayer
338
355
  */
339
356
  constructor(position, size, tileInfo=tile(), renderOrder=0)
340
357
  {
341
- const canvasSize = size.multiply(tileInfo.size);
358
+ const canvasSize = tileInfo ? size.multiply(tileInfo.size) : size;
342
359
  super(position, size, 0, renderOrder, canvasSize);
343
360
 
344
361
  // set tile info
@@ -391,6 +408,13 @@ class TileLayer extends CanvasLayer
391
408
  {
392
409
  ASSERT(drawContext !== this.context, 'must call redrawEnd() after drawing tiles!');
393
410
 
411
+ if (this.refreshWebGL)
412
+ {
413
+ // update the WebGL texture
414
+ this.textureInfo.createWebGLTexture();
415
+ this.refreshWebGL = false;
416
+ }
417
+
394
418
  // draw the tile layer as a single tile
395
419
  const tileInfo = new TileInfo().setFullImage(this.textureInfo);
396
420
  const size = this.drawSize || this.size;
@@ -427,8 +451,9 @@ class TileLayer extends CanvasLayer
427
451
  drawCanvas = this.canvas;
428
452
  drawContext = this.context;
429
453
  cameraPos = this.size.scale(.5);
430
- cameraScale = this.tileInfo.size.x;
431
- mainCanvasSize = this.size.multiply(this.tileInfo.size);
454
+ const tileSize = this.tileInfo ? this.tileInfo.size : vec2(1);
455
+ cameraScale = tileSize.x;
456
+ mainCanvasSize = this.size.multiply(tileSize);
432
457
  if (clear)
433
458
  {
434
459
  // clear and set size
@@ -588,65 +613,43 @@ class TileCollisionLayer extends TileLayer
588
613
  // check if the object should collide with this tile
589
614
  const tileData = this.collisionData[y*this.size.x+x];
590
615
  if (tileData)
591
- if (!object || object.collideWithTile(tileData, hitPos.set(x, y)))
616
+ if (!object || object.collideWithTile(tileData,
617
+ hitPos.set(x + this.pos.x, y + this.pos.y)))
592
618
  return true;
593
619
  }
594
620
  return false;
595
621
  }
596
622
 
597
- /** Return the center of first tile hit, undefined if nothing was hit.
598
- * This does not return the exact intersection, but the center of the tile hit.
623
+ /** Return the exact position of the boudnary of first tile hit, undefined if nothing was hit.
599
624
  * @param {Vector2} posStart
600
625
  * @param {Vector2} posEnd
601
- * @param {EngineObject} [object]
602
- * @return {Vector2} */
603
- collisionRaycast(posStart, posEnd, object)
626
+ * @param {EngineObject} [object] - An object or undefined for generic test
627
+ * @param {Vector2} [normal] - Optional normal of the surface hit
628
+ * @return {Vector2|undefined} */
629
+ collisionRaycast(posStart, posEnd, object, normal)
604
630
  {
605
631
  ASSERT(isVector2(posStart) && isVector2(posEnd), 'positions must be Vector2s');
606
632
  ASSERT(!object || object instanceof EngineObject, 'object must be an EngineObject');
607
-
608
- // transform to local layer space
609
- const posStartX = posStart.x - this.pos.x;
610
- const posStartY = posStart.y - this.pos.y;
611
- const posEndX = posEnd.x - this.pos.x;
612
- const posEndY = posEnd.y - this.pos.y;
613
-
614
- // test if a ray collides with tiles from start to end
615
- const deltaX = posEndX - posStartX;
616
- const deltaY = posEndY - posStartY;
617
- const totalLength = (deltaX**2 + deltaY**2)**.5;
618
- const unitX = abs(totalLength/deltaX);
619
- const unitY = abs(totalLength/deltaY);
620
-
621
- // setup iteration variables
622
- const pos = posStart.floor(), signDeltaX = sign(deltaX), signDeltaY = sign(deltaY);
623
- let xi = unitX * (deltaX < 0 ? posStart.x - pos.x : pos.x - posStart.x + 1) || 0;
624
- let yi = unitY * (deltaY < 0 ? posStart.y - pos.y : pos.y - posStart.y + 1) || 0;
625
-
626
- // use line drawing algorithm to test for collisions
627
- while (true)
633
+
634
+ const localPos = new Vector2;
635
+ const collisionTest = (pos)=>
628
636
  {
629
637
  // check for tile collision
630
- const tileData = this.getCollisionData(pos);
631
- if (tileData && (!object || object.collideWithTile(tileData, pos)))
632
- {
633
- pos.x += .5; pos.y += .5;
634
- debugRaycast && debugLine(posStart, posEnd, '#f00', .02);
635
- debugRaycast && debugPoint(pos, '#ff0');
636
- return pos;
637
- }
638
-
639
- // check if past the end
640
- if (xi >= totalLength && yi >= totalLength)
641
- break;
642
-
643
- // get coordinates of next tile to check
644
- if (xi > yi)
645
- pos.y += signDeltaY, yi += unitY;
646
- else
647
- pos.x += signDeltaX, xi += unitX;
638
+ localPos.set(pos.x - this.pos.x, pos.y - this.pos.y);
639
+ const tileData = this.getCollisionData(localPos);
640
+ return tileData && (!object || object.collideWithTile(tileData, pos));
648
641
  }
649
-
650
642
  debugRaycast && debugLine(posStart, posEnd, '#00f', .02);
643
+ const hitPos = lineTest(posStart, posEnd, collisionTest, normal);
644
+ if (hitPos)
645
+ {
646
+ const tilePos = hitPos.floor().add(vec2(.5));
647
+ debugRaycast && debugRect(tilePos, vec2(1), '#f008');
648
+ debugRaycast && debugLine(posStart, hitPos, '#f00', .02);
649
+ debugRaycast && debugPoint(hitPos, '#0f0');
650
+ debugRaycast && normal &&
651
+ debugLine(hitPos, hitPos.add(normal), '#ff0', .02);
652
+ return hitPos;
653
+ }
651
654
  }
652
655
  }