littlejsengine 1.10.4 → 1.11.1

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 (75) hide show
  1. package/LittleJS.zip +0 -0
  2. package/README.md +8 -78
  3. package/dist/littlejs.d.ts +60 -25
  4. package/dist/littlejs.esm.js +164 -63
  5. package/dist/littlejs.esm.min.js +1 -1
  6. package/dist/littlejs.js +160 -62
  7. package/dist/littlejs.min.js +1 -1
  8. package/dist/littlejs.release.js +160 -62
  9. package/examples/box2d/game.js +15 -4
  10. package/examples/box2d/gameObjects.js +10 -9
  11. package/examples/box2d/index.html +5 -5
  12. package/examples/box2d/scenes.js +4 -4
  13. package/examples/box2d/tiles.png +0 -0
  14. package/examples/breakout/index.html +3 -3
  15. package/examples/breakoutTutorial/index.html +2 -2
  16. package/examples/electron/game.js +3 -0
  17. package/examples/electron/index.html +2 -2
  18. package/examples/htmlMenu/index.html +3 -3
  19. package/examples/index.html +10 -3
  20. package/examples/js13k/game.js +3 -0
  21. package/examples/js13k/index.html +13 -13
  22. package/examples/module/game.js +4 -1
  23. package/examples/module/index.html +1 -1
  24. package/examples/particles/index.html +4 -1
  25. package/examples/platformer/game.js +19 -15
  26. package/examples/platformer/gameCharacter.js +3 -3
  27. package/examples/platformer/gameLevel.js +1 -0
  28. package/examples/platformer/index.html +8 -8
  29. package/examples/platformer/tiles.png +0 -0
  30. package/examples/puzzle/game.js +3 -2
  31. package/examples/puzzle/index.html +2 -2
  32. package/examples/starter/game.js +47 -10
  33. package/examples/starter/index.html +13 -13
  34. package/examples/starter/tiles.png +0 -0
  35. package/examples/stress/index.html +1 -1
  36. package/examples/typescript/game.js +2 -0
  37. package/examples/typescript/game.ts +3 -0
  38. package/examples/typescript/index.html +1 -1
  39. package/examples/uiSystem/game.js +23 -16
  40. package/examples/uiSystem/index.html +3 -14
  41. package/package.json +7 -2
  42. package/plugins/postProcess.js +2 -9
  43. package/plugins/uiSystem.js +85 -25
  44. package/reference.md +1 -1
  45. package/shortExamples/engine.html +71 -0
  46. package/shortExamples/index.html +47 -0
  47. package/shortExamples/index.js +0 -0
  48. package/shortExamples/tiles.png +0 -0
  49. package/src/engine.js +14 -12
  50. package/src/engineAudio.js +6 -9
  51. package/src/engineDraw.js +23 -7
  52. package/src/engineExport.js +4 -1
  53. package/src/engineInput.js +7 -3
  54. package/src/engineMedals.js +19 -5
  55. package/src/engineObject.js +8 -5
  56. package/src/engineSettings.js +13 -2
  57. package/src/engineTileLayer.js +9 -8
  58. package/src/engineUtilities.js +59 -10
  59. package/src/engineWebGL.js +1 -1
  60. package/examples/js13k/build/index.html +0 -1
  61. package/examples/js13k/build/index.js +0 -1
  62. package/examples/js13k/build/tiles.png +0 -0
  63. package/examples/js13k/game - Copy.zip +0 -0
  64. package/examples/js13k/game.zip +0 -0
  65. package/examples/starter/build/index.html +0 -2
  66. package/examples/starter/build/index.js +0 -1
  67. package/examples/starter/build/tiles.png +0 -0
  68. package/examples/starter/game.zip +0 -0
  69. package/examples/typescript/build/build/littlejs.esm.js +0 -4327
  70. package/examples/typescript/build/dist/littlejs.esm.js +0 -4425
  71. package/examples/typescript/build/examples/typescript/build.js +0 -24
  72. package/examples/typescript/build/examples/typescript/game.js +0 -100
  73. package/examples/typescript/build/examples/typescript/test/build/littlejs.esm.js +0 -3934
  74. package/examples/typescript/build/examples/typescript/test/examples/typescript/build.js +0 -86
  75. package/examples/typescript/build/examples/typescript/test/examples/typescript/game.js +0 -92
@@ -239,7 +239,8 @@ function formatTime(t) { return (t/60|0) + ':' + (t%60<10?'0':'') + (t%60|0); }
239
239
  * @memberof Random */
240
240
  function rand(valueA=1, valueB=0) { return valueB + Math.random() * (valueA-valueB); }
241
241
 
242
- /** Returns a floored random value the two values passed in
242
+ /** Returns a floored random value between the two values passed in
243
+ * The upper bound is exclusive. (If 2 is passed in, result will be 0 or 1)
243
244
  * @param {Number} valueA
244
245
  * @param {Number} [valueB]
245
246
  * @return {Number}
@@ -368,18 +369,24 @@ class Vector2
368
369
  * @param {Number} [y] - Y axis location */
369
370
  constructor(x=0, y=0)
370
371
  {
371
- ASSERT(typeof x == 'number' && typeof y == 'number');
372
372
  /** @property {Number} - X axis location */
373
373
  this.x = x;
374
374
  /** @property {Number} - Y axis location */
375
375
  this.y = y;
376
+ ASSERT(this.isValid());
376
377
  }
377
378
 
378
379
  /** Sets values of this vector and returns self
379
380
  * @param {Number} [x] - X axis location
380
381
  * @param {Number} [y] - Y axis location
381
382
  * @return {Vector2} */
382
- set(x=0, y=0) { this.x=x; this.y=y; return this; }
383
+ set(x=0, y=0)
384
+ {
385
+ this.x = x;
386
+ this.y = y;
387
+ ASSERT(this.isValid());
388
+ return this;
389
+ }
383
390
 
384
391
  /** Returns a new vector that is a copy of this
385
392
  * @return {Vector2} */
@@ -571,6 +578,14 @@ class Vector2
571
578
  if (debug)
572
579
  return `(${(this.x<0?'':' ') + this.x.toFixed(digits)},${(this.y<0?'':' ') + this.y.toFixed(digits)} )`;
573
580
  }
581
+
582
+ /** Checks if this is a valid vector
583
+ * @return {Boolean} */
584
+ isValid()
585
+ {
586
+ return typeof this.x == 'number' && !isNaN(this.x)
587
+ && typeof this.y == 'number' && !isNaN(this.y);
588
+ }
574
589
  }
575
590
 
576
591
  ///////////////////////////////////////////////////////////////////////////////
@@ -631,6 +646,7 @@ class Color
631
646
  this.b = b;
632
647
  /** @property {Number} - Alpha */
633
648
  this.a = a;
649
+ ASSERT(this.isValid());
634
650
  }
635
651
 
636
652
  /** Sets values of this color and returns self
@@ -640,7 +656,14 @@ class Color
640
656
  * @param {Number} [a] - alpha
641
657
  * @return {Color} */
642
658
  set(r=1, g=1, b=1, a=1)
643
- { this.r=r; this.g=g; this.b=b; this.a=a; return this; }
659
+ {
660
+ this.r = r;
661
+ this.g = g;
662
+ this.b = b;
663
+ this.a = a;
664
+ ASSERT(this.isValid());
665
+ return this;
666
+ }
644
667
 
645
668
  /** Returns a new color that is a copy of this
646
669
  * @return {Color} */
@@ -723,6 +746,7 @@ class Color
723
746
  this.g = f(p, q, h);
724
747
  this.b = f(p, q, h - 1/3);
725
748
  this.a = a;
749
+ ASSERT(this.isValid());
726
750
  return this;
727
751
  }
728
752
 
@@ -750,7 +774,6 @@ class Color
750
774
  else if (b == max)
751
775
  h = (r - g) / d + 4;
752
776
  }
753
-
754
777
  return [h / 6, s, l, a];
755
778
  }
756
779
 
@@ -783,11 +806,27 @@ class Color
783
806
  * @return {Color} */
784
807
  setHex(hex)
785
808
  {
786
- const fromHex = (c)=> clamp(parseInt(hex.slice(c,c+2),16)/255);
787
- this.r = fromHex(1);
788
- this.g = fromHex(3),
789
- this.b = fromHex(5);
790
- this.a = hex.length > 7 ? fromHex(7) : 1;
809
+ ASSERT(typeof hex == 'string' && hex[0] == '#');
810
+ ASSERT([4,5,7,9].includes(hex.length), 'Invalid hex');
811
+
812
+ if (hex.length < 6)
813
+ {
814
+ const fromHex = (c)=> clamp(parseInt(hex[c],16)/15);
815
+ this.r = fromHex(1);
816
+ this.g = fromHex(2),
817
+ this.b = fromHex(3);
818
+ this.a = hex.length == 5 ? fromHex(4) : 1;
819
+ }
820
+ else
821
+ {
822
+ const fromHex = (c)=> clamp(parseInt(hex.slice(c,c+2),16)/255);
823
+ this.r = fromHex(1);
824
+ this.g = fromHex(3),
825
+ this.b = fromHex(5);
826
+ this.a = hex.length == 9 ? fromHex(7) : 1;
827
+ }
828
+
829
+ ASSERT(this.isValid());
791
830
  return this;
792
831
  }
793
832
 
@@ -801,6 +840,16 @@ class Color
801
840
  const a = clamp(this.a)*255<<24;
802
841
  return r + g + b + a;
803
842
  }
843
+
844
+ /** Checks if this is a valid color
845
+ * @return {Boolean} */
846
+ isValid()
847
+ {
848
+ return typeof this.r == 'number' && !isNaN(this.r)
849
+ && typeof this.g == 'number' && !isNaN(this.g)
850
+ && typeof this.b == 'number' && !isNaN(this.b)
851
+ && typeof this.a == 'number' && !isNaN(this.a);
852
+ }
804
853
  }
805
854
 
806
855
  ///////////////////////////////////////////////////////////////////////////////
@@ -952,12 +1001,18 @@ let canvasMaxSize = vec2(1920, 1080);
952
1001
  * @memberof Settings */
953
1002
  let canvasFixedSize = vec2();
954
1003
 
955
- /** Disables filtering for crisper pixel art if true
1004
+ /** Use nearest neighbor scaling algorithm for canvas for more pixelated look
956
1005
  * @type {Boolean}
957
1006
  * @default
958
1007
  * @memberof Settings */
959
1008
  let canvasPixelated = true;
960
1009
 
1010
+ /** Disables texture filtering for crisper pixel art
1011
+ * @type {Boolean}
1012
+ * @default
1013
+ * @memberof Settings */
1014
+ let tilesPixelated = true;
1015
+
961
1016
  /** Default font used for text rendering
962
1017
  * @type {String}
963
1018
  * @default
@@ -1206,11 +1261,16 @@ function setCanvasMaxSize(size) { canvasMaxSize = size; }
1206
1261
  * @memberof Settings */
1207
1262
  function setCanvasFixedSize(size) { canvasFixedSize = size; }
1208
1263
 
1209
- /** Disables anti aliasing for pixel art if true
1264
+ /** Use nearest neighbor scaling algorithm for canvas for more pixelated look
1210
1265
  * @param {Boolean} pixelated
1211
1266
  * @memberof Settings */
1212
1267
  function setCanvasPixelated(pixelated) { canvasPixelated = pixelated; }
1213
1268
 
1269
+ /** Disables texture filtering for crisper pixel art
1270
+ * @param {Boolean} pixelated
1271
+ * @memberof Settings */
1272
+ function setTilesPixelated(pixelated) { tilesPixelated = pixelated; }
1273
+
1214
1274
  /** Set default font used for text rendering
1215
1275
  * @param {String} font
1216
1276
  * @memberof Settings */
@@ -1436,7 +1496,7 @@ class EngineObject
1436
1496
  * @param {Color} [color=(1,1,1,1)] - Color to apply to tile when rendered
1437
1497
  * @param {Number} [renderOrder] - Objects sorted by renderOrder before being rendered
1438
1498
  */
1439
- constructor(pos=vec2(), size=vec2(1), tileInfo, angle=0, color, renderOrder=0)
1499
+ constructor(pos=vec2(), size=vec2(1), tileInfo, angle=0, color=new Color, renderOrder=0)
1440
1500
  {
1441
1501
  // set passed in params
1442
1502
  ASSERT(isVector2(pos) && isVector2(size), 'ensure pos and size are vec2s');
@@ -1550,15 +1610,18 @@ class EngineObject
1550
1610
 
1551
1611
  // apply physics
1552
1612
  const oldPos = this.pos.copy();
1553
- this.pos.x += this.velocity.x *= this.damping;
1554
- this.pos.y += this.velocity.y = this.damping * this.velocity.y
1555
- + gravity * this.gravityScale;
1613
+ this.velocity.x *= this.damping;
1614
+ this.velocity.y *= this.damping;
1615
+ if (this.mass) // dont apply gravity to static objects
1616
+ this.velocity.y += gravity * this.gravityScale;
1617
+ this.pos.x += this.velocity.x;
1618
+ this.pos.y += this.velocity.y;
1556
1619
  this.angle += this.angleVelocity *= this.angleDamping;
1557
1620
 
1558
1621
  // physics sanity checks
1559
1622
  ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
1560
1623
  ASSERT(this.damping >= 0 && this.damping <= 1);
1561
- if (!enablePhysicsSolver || !this.mass) // dont do collision for fixed objects
1624
+ if (!enablePhysicsSolver || !this.mass) // dont do collision for static objects
1562
1625
  return;
1563
1626
 
1564
1627
  const wasMovingDown = this.velocity.y < 0;
@@ -1919,7 +1982,7 @@ let drawCount;
1919
1982
  * tile(2) // a tile at index 2 using the default tile size of 16
1920
1983
  * tile(5, 8) // a tile at index 5 using a tile size of 8
1921
1984
  * tile(1, 16, 3) // a tile at index 1 of size 16 on texture 3
1922
- * tile(vec2(4,8), vec2(30,10)) // a tile at pixel location (4,8) with a size of (30,10)
1985
+ * tile(vec2(4,8), vec2(30,10)) // a tile at index (4,8) with a size of (30,10)
1923
1986
  * @memberof Draw
1924
1987
  */
1925
1988
  function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0, padding=0)
@@ -2264,6 +2327,24 @@ function setBlendMode(additive, useWebGL=glEnable, context)
2264
2327
  }
2265
2328
  }
2266
2329
 
2330
+ /** Draw text on main canvas in world space
2331
+ * Automatically splits new lines into rows
2332
+ * @param {String} text
2333
+ * @param {Vector2} pos
2334
+ * @param {Number} [size]
2335
+ * @param {Color} [color=(1,1,1,1)]
2336
+ * @param {Number} [lineWidth]
2337
+ * @param {Color} [lineColor=(0,0,0,1)]
2338
+ * @param {CanvasTextAlign} [textAlign='center']
2339
+ * @param {String} [font=fontDefault]
2340
+ * @param {Number} [maxWidth]
2341
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=mainContext]
2342
+ * @memberof Draw */
2343
+ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, maxWidth, context=mainContext)
2344
+ {
2345
+ drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, maxWidth, context);
2346
+ }
2347
+
2267
2348
  /** Draw text on overlay canvas in world space
2268
2349
  * Automatically splits new lines into rows
2269
2350
  * @param {String} text
@@ -2274,12 +2355,11 @@ function setBlendMode(additive, useWebGL=glEnable, context)
2274
2355
  * @param {Color} [lineColor=(0,0,0,1)]
2275
2356
  * @param {CanvasTextAlign} [textAlign='center']
2276
2357
  * @param {String} [font=fontDefault]
2277
- * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
2278
2358
  * @param {Number} [maxWidth]
2279
2359
  * @memberof Draw */
2280
- function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, context, maxWidth)
2360
+ function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, maxWidth)
2281
2361
  {
2282
- drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, context, maxWidth);
2362
+ drawText(text, pos, size, color, lineWidth, lineColor, textAlign, font, maxWidth, overlayContext);
2283
2363
  }
2284
2364
 
2285
2365
  /** Draw text on overlay canvas in screen space
@@ -2292,10 +2372,10 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
2292
2372
  * @param {Color} [lineColor=(0,0,0,1)]
2293
2373
  * @param {CanvasTextAlign} [textAlign]
2294
2374
  * @param {String} [font=fontDefault]
2295
- * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
2296
2375
  * @param {Number} [maxWidth]
2376
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
2297
2377
  * @memberof Draw */
2298
- function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, context=overlayContext, maxWidth=undefined)
2378
+ function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, maxWidth=undefined, context=overlayContext)
2299
2379
  {
2300
2380
  context.fillStyle = color.toString();
2301
2381
  context.lineWidth = lineWidth;
@@ -2371,7 +2451,6 @@ class FontImage
2371
2451
  {
2372
2452
  const context = this.context;
2373
2453
  context.save();
2374
- context.imageSmoothingEnabled = !canvasPixelated;
2375
2454
 
2376
2455
  const size = this.tileSize;
2377
2456
  const drawSize = size.add(this.paddingSize).scale(scale);
@@ -2468,7 +2547,7 @@ function keyWasReleased(key, device=0)
2468
2547
 
2469
2548
  /** Clears all input
2470
2549
  * @memberof Input */
2471
- function clearInput() { inputData = [[]]; }
2550
+ function clearInput() { inputData = [[]]; touchGamepadButtons = []; }
2472
2551
 
2473
2552
  /** Returns true if mouse button is down
2474
2553
  * @function
@@ -2633,6 +2712,7 @@ function inputInit()
2633
2712
  onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
2634
2713
  onwheel = (e)=> mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY);
2635
2714
  oncontextmenu = (e)=> false; // prevent right click menu
2715
+ onblur = (e) => clearInput(); // reset input when focus is lost
2636
2716
 
2637
2717
  // init touch input
2638
2718
  if (isTouchDevice && touchInputEnable)
@@ -2831,10 +2911,13 @@ function touchInputInit()
2831
2911
  if (touching)
2832
2912
  {
2833
2913
  touchGamepadTimer.set();
2834
- if (paused)
2914
+ if (paused && !wasTouching)
2835
2915
  {
2836
2916
  // touch anywhere to press start when paused
2837
2917
  touchGamepadButtons[9] = 1;
2918
+
2919
+ // call default touch handler so normal touch events still work
2920
+ handleTouchDefault(e);
2838
2921
  return;
2839
2922
  }
2840
2923
  }
@@ -2859,7 +2942,7 @@ function touchInputInit()
2859
2942
  const button = touchPos.subtract(buttonCenter).direction();
2860
2943
  touchGamepadButtons[button] = 1;
2861
2944
  }
2862
- else if (touchPos.distance(startCenter) < touchGamepadSize)
2945
+ else if (touchPos.distance(startCenter) < touchGamepadSize && !wasTouching)
2863
2946
  {
2864
2947
  // virtual start button in center
2865
2948
  touchGamepadButtons[9] = 1;
@@ -2947,7 +3030,7 @@ function touchGamepadRender()
2947
3030
  /** Audio context used by the engine
2948
3031
  * @type {AudioContext}
2949
3032
  * @memberof Audio */
2950
- let audioContext;
3033
+ let audioContext = new AudioContext;
2951
3034
 
2952
3035
  /** Master gain node for all audio to pass through
2953
3036
  * @type {GainNode}
@@ -2958,14 +3041,10 @@ function audioInit()
2958
3041
  {
2959
3042
  if (!soundEnable || headlessMode) return;
2960
3043
 
2961
- // create audio context
2962
- audioContext = new AudioContext;
2963
-
2964
- // create and connect gain node
2965
3044
  // (createGain is more widely spported then GainNode construtor)
2966
3045
  audioGainNode = audioContext.createGain();
2967
3046
  audioGainNode.connect(audioContext.destination);
2968
- setSoundVolume(soundVolume); // update gain volume
3047
+ audioGainNode.gain.value = soundVolume; // set starting value
2969
3048
  }
2970
3049
 
2971
3050
  ///////////////////////////////////////////////////////////////////////////////
@@ -3005,7 +3084,7 @@ class Sound
3005
3084
  {
3006
3085
  // generate zzfx sound now for fast playback
3007
3086
  const defaultRandomness = .05;
3008
- this.randomness = zzfxSound[1] || defaultRandomness;
3087
+ this.randomness = zzfxSound[1] != undefined ? zzfxSound[1] : defaultRandomness;
3009
3088
  zzfxSound[1] = 0; // generate without randomness
3010
3089
  this.sampleChannels = [zzfxG(...zzfxSound)];
3011
3090
  this.sampleRate = zzfxR;
@@ -3047,10 +3126,11 @@ class Sound
3047
3126
  // play the sound
3048
3127
  const playbackRate = pitch + pitch * this.randomness*randomnessScale*rand(-1,1);
3049
3128
  this.gainNode = audioContext.createGain();
3050
- return this.source = playSamples(this.sampleChannels, volume, playbackRate, pan, loop, this.sampleRate, this.gainNode);
3129
+ this.source = playSamples(this.sampleChannels, volume, playbackRate, pan, loop, this.sampleRate, this.gainNode);
3130
+ return this.source;
3051
3131
  }
3052
3132
 
3053
- /** Set the sound volume
3133
+ /** Set the sound volume of the most recently played instance of this sound
3054
3134
  * @param {Number} [volume] - How much to scale volume by
3055
3135
  */
3056
3136
  setVolume(volume=1)
@@ -3541,18 +3621,18 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
3541
3621
 
3542
3622
 
3543
3623
 
3544
- /** The tile collision layer array, use setTileCollisionData and getTileCollisionData to access
3624
+ /** The tile collision layer grid, use setTileCollisionData and getTileCollisionData to access
3545
3625
  * @type {Array}
3546
3626
  * @memberof TileCollision */
3547
3627
  let tileCollision = [];
3548
3628
 
3549
- /** Size of the tile collision layer
3629
+ /** Size of the tile collision layer 2d grid
3550
3630
  * @type {Vector2}
3551
3631
  * @memberof TileCollision */
3552
3632
  let tileCollisionSize = vec2();
3553
3633
 
3554
3634
  /** Clear and initialize tile collision
3555
- * @param {Vector2} size
3635
+ * @param {Vector2} size - width and height of tile collision 2d grid
3556
3636
  * @memberof TileCollision */
3557
3637
  function initTileCollision(size)
3558
3638
  {
@@ -3562,7 +3642,7 @@ function initTileCollision(size)
3562
3642
  tileCollision[i] = 0;
3563
3643
  }
3564
3644
 
3565
- /** Set tile collision data
3645
+ /** Set tile collision data for a given cell in the grid
3566
3646
  * @param {Vector2} pos
3567
3647
  * @param {Number} [data]
3568
3648
  * @memberof TileCollision */
@@ -3571,7 +3651,7 @@ function setTileCollisionData(pos, data=0)
3571
3651
  pos.arrayCheck(tileCollisionSize) && (tileCollision[(pos.y|0)*tileCollisionSize.x+pos.x|0] = data);
3572
3652
  }
3573
3653
 
3574
- /** Get tile collision data
3654
+ /** Get tile collision data for a given cell in the grid
3575
3655
  * @param {Vector2} pos
3576
3656
  * @return {Number}
3577
3657
  * @memberof TileCollision */
@@ -3602,7 +3682,8 @@ function tileCollisionTest(pos, size=vec2(), object)
3602
3682
  return false;
3603
3683
  }
3604
3684
 
3605
- /** Return the center of first tile hit (does not return the exact intersection)
3685
+ /** Return the center of first tile hit, undefined if nothing was hit.
3686
+ * This does not return the exact intersection, but the center of the tile hit.
3606
3687
  * @param {Vector2} posStart
3607
3688
  * @param {Vector2} posEnd
3608
3689
  * @param {EngineObject} [object]
@@ -3810,7 +3891,7 @@ class TileLayer extends EngineObject
3810
3891
  }
3811
3892
 
3812
3893
  // disable smoothing for pixel art
3813
- this.context.imageSmoothingEnabled = !canvasPixelated;
3894
+ this.context.imageSmoothingEnabled = !tilesPixelated;
3814
3895
 
3815
3896
  // setup gl rendering if enabled
3816
3897
  glPreRender();
@@ -3847,8 +3928,8 @@ class TileLayer extends EngineObject
3847
3928
  const d = this.getData(layerPos);
3848
3929
  if (d.tile != undefined)
3849
3930
  {
3850
- const pos = this.pos.add(layerPos).add(vec2(.5));
3851
3931
  ASSERT(mainContext == this.context, 'must call redrawStart() before drawing tiles');
3932
+ const pos = layerPos.add(vec2(.5));
3852
3933
  const tileInfo = tile(d.tile, s, this.tileInfo.textureIndex);
3853
3934
  drawTile(pos, vec2(1), tileInfo, d.color, d.direction*PI/2, d.mirror);
3854
3935
  }
@@ -4277,7 +4358,7 @@ function medalsInit(saveName)
4277
4358
  // check if medals are unlocked
4278
4359
  medalsSaveName = saveName;
4279
4360
  if (!debugMedals)
4280
- medalsForEach(medal=> medal.unlocked = (localStorage[medal.storageKey()] | 0));
4361
+ medalsForEach(medal=> medal.unlocked = !!localStorage[medal.storageKey()]);
4281
4362
 
4282
4363
  // engine automatically renders medals
4283
4364
  engineAddPlugin(undefined, medalsRender);
@@ -4340,14 +4421,28 @@ class Medal
4340
4421
  constructor(id, name, description='', icon='🏆', src)
4341
4422
  {
4342
4423
  ASSERT(id >= 0 && !medals[id]);
4343
-
4344
- // save attributes and add to list of medals
4345
- medals[this.id = id] = this;
4424
+
4425
+ /** @property {Number} - The unique identifier of the medal */
4426
+ this.id = id;
4427
+
4428
+ /** @property {String} - Name of the medal */
4346
4429
  this.name = name;
4430
+
4431
+ /** @property {String} - Description of the medal */
4347
4432
  this.description = description;
4433
+
4434
+ /** @property {String} - Icon for the medal */
4348
4435
  this.icon = icon;
4436
+
4437
+ /** @property {Boolean} - Is the medal unlocked? */
4438
+ this.unlocked = false;
4439
+
4440
+ // load the source image if provided
4349
4441
  if (src)
4350
4442
  (this.image = new Image).src = src;
4443
+
4444
+ // add this to list of medals
4445
+ medals[id] = this;
4351
4446
  }
4352
4447
 
4353
4448
  /** Unlocks a medal if not already unlocked */
@@ -4358,7 +4453,7 @@ class Medal
4358
4453
 
4359
4454
  // save the medal
4360
4455
  ASSERT(medalsSaveName, 'save name must be set');
4361
- localStorage[this.storageKey()] = this.unlocked = 1;
4456
+ localStorage[this.storageKey()] = this.unlocked = true;
4362
4457
  medalsDisplayQueue.push(this);
4363
4458
  }
4364
4459
 
@@ -4617,7 +4712,7 @@ function glCreateTexture(image)
4617
4712
  }
4618
4713
 
4619
4714
  // use point filtering for pixelated rendering
4620
- const filter = canvasPixelated ? gl_NEAREST : gl_LINEAR;
4715
+ const filter = tilesPixelated ? gl_NEAREST : gl_LINEAR;
4621
4716
  glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, filter);
4622
4717
  glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, filter);
4623
4718
  return texture;
@@ -4766,7 +4861,7 @@ const engineName = 'LittleJS';
4766
4861
  * @type {String}
4767
4862
  * @default
4768
4863
  * @memberof Engine */
4769
- const engineVersion = '1.10.4';
4864
+ const engineVersion = '1.11.1';
4770
4865
 
4771
4866
  /** Frames per second to update
4772
4867
  * @type {Number}
@@ -4829,6 +4924,8 @@ const pluginUpdateList = [], pluginRenderList = [];
4829
4924
  * @memberof Engine */
4830
4925
  function engineAddPlugin(updateFunction, renderFunction)
4831
4926
  {
4927
+ ASSERT(!pluginUpdateList.includes(updateFunction));
4928
+ ASSERT(!pluginRenderList.includes(renderFunction));
4832
4929
  updateFunction && pluginUpdateList.push(updateFunction);
4833
4930
  renderFunction && pluginRenderList.push(renderFunction);
4834
4931
  }
@@ -4837,12 +4934,12 @@ function engineAddPlugin(updateFunction, renderFunction)
4837
4934
  // Main engine functions
4838
4935
 
4839
4936
  /** Startup LittleJS engine with your callback functions
4840
- * @param {Function} gameInit - Called once after the engine starts up, setup the game
4841
- * @param {Function} gameUpdate - Called every frame at 60 frames per second, handle input and update the game state
4842
- * @param {Function} gameUpdatePost - Called after physics and objects are updated, setup camera and prepare for render
4843
- * @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects
4844
- * @param {Function} gameRenderPost - Called after objects are rendered, draw effects or hud that appear above all objects
4845
- * @param {Array} [imageSources=[]] - Image to load
4937
+ * @param {Function|function():Promise} gameInit - Called once after the engine starts up
4938
+ * @param {Function} gameUpdate - Called every frame before objects are updated
4939
+ * @param {Function} gameUpdatePost - Called after physics and objects are updated, even when paused
4940
+ * @param {Function} gameRender - Called before objects are rendered, for drawing the background
4941
+ * @param {Function} gameRenderPost - Called after objects are rendered, useful for drawing UI
4942
+ * @param {Array} [imageSources=[]] - List of images to load
4846
4943
  * @param {HTMLElement} [rootElement] - Root element to attach to, the document body by default
4847
4944
  * @memberof Engine */
4848
4945
  function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
@@ -4856,7 +4953,8 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4856
4953
  mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
4857
4954
 
4858
4955
  // disable smoothing for pixel art
4859
- mainContext.imageSmoothingEnabled = !canvasPixelated;
4956
+ overlayContext.imageSmoothingEnabled =
4957
+ mainContext.imageSmoothingEnabled = !tilesPixelated;
4860
4958
 
4861
4959
  // setup gl rendering if enabled
4862
4960
  glPreRender();
@@ -4992,8 +5090,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4992
5090
 
4993
5091
  function startEngine()
4994
5092
  {
4995
- gameInit();
4996
- engineUpdate();
5093
+ new Promise((resolve) => resolve(gameInit())).then(engineUpdate);
4997
5094
  }
4998
5095
 
4999
5096
  if (headlessMode)
@@ -5008,9 +5105,9 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
5008
5105
  'width:100vw;height:100vh;' + // fill the window
5009
5106
  'display:flex;' + // use flexbox
5010
5107
  'align-items:center;' + // horizontal center
5011
- (canvasPixelated ? 'image-rendering:pixelated;' : '') + // pixel art
5012
5108
  'justify-content:center;' + // vertical center
5013
5109
  'background:#000;' + // set background color
5110
+ (canvasPixelated ? 'image-rendering:pixelated;' : '') + // pixel art
5014
5111
  'user-select:none;' + // prevent hold to select
5015
5112
  '-webkit-user-select:none;' + // compatibility for ios
5016
5113
  (!touchInputEnable ? '' : // no touch css setttings
@@ -5346,4 +5443,5 @@ function drawEngineSplashScreen(t)
5346
5443
  }
5347
5444
 
5348
5445
  x.restore();
5349
- }
5446
+ }
5447
+
@@ -15,13 +15,11 @@
15
15
  setShowSplashScreen(true);
16
16
  //box2dDebug = 1; // enable box2d debug draw
17
17
 
18
- // fix texture bleeding by shrinking tile slightly
19
- tileFixBleedScale = .2;
20
-
21
18
  // game variables
22
19
  const maxScenes = 11;
23
20
  let scene = 0;
24
21
  let sceneName;
22
+ let spriteAtlas;
25
23
  let groundObject;
26
24
  let mouseJoint;
27
25
  let car;
@@ -30,6 +28,19 @@ let repeatSpawnTimer = new Timer;
30
28
  ///////////////////////////////////////////////////////////////////////////////
31
29
  function gameInit()
32
30
  {
31
+ // create a table of all sprites
32
+ const gameTile = (i)=> tile(i, 16, 0, 1);
33
+ spriteAtlas =
34
+ {
35
+ circle: gameTile(0),
36
+ dot: gameTile(1),
37
+ circleOutline: gameTile(2),
38
+ squareOutline: gameTile(3),
39
+ wheel: gameTile(4),
40
+ gear: gameTile(5),
41
+ squareOutline2: gameTile(6),
42
+ };
43
+
33
44
  loadScene(scene);
34
45
  }
35
46
 
@@ -128,7 +139,7 @@ function gameRenderPost()
128
139
  {
129
140
  // draw mouse joint
130
141
  const ab = vec2(mouseJoint.GetAnchorB());
131
- drawTile(ab, vec2(.3), tile(0), BLACK);
142
+ drawTile(ab, vec2(.3), spriteAtlas.circle, BLACK);
132
143
  drawLine(mousePos, ab, .1, BLACK);
133
144
  }
134
145