littlejsengine 1.10.4 → 1.10.7

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 (63) hide show
  1. package/README.md +8 -78
  2. package/dist/littlejs.d.ts +31 -17
  3. package/dist/littlejs.esm.js +121 -55
  4. package/dist/littlejs.esm.min.js +1 -1
  5. package/dist/littlejs.js +121 -55
  6. package/dist/littlejs.min.js +1 -1
  7. package/dist/littlejs.release.js +121 -55
  8. package/examples/box2d/game.js +15 -4
  9. package/examples/box2d/gameObjects.js +10 -9
  10. package/examples/box2d/index.html +5 -5
  11. package/examples/box2d/scenes.js +4 -4
  12. package/examples/box2d/tiles.png +0 -0
  13. package/examples/breakout/index.html +3 -3
  14. package/examples/breakoutTutorial/index.html +2 -2
  15. package/examples/electron/game.js +3 -0
  16. package/examples/electron/index.html +2 -2
  17. package/examples/htmlMenu/index.html +3 -3
  18. package/examples/js13k/game.js +3 -0
  19. package/examples/js13k/index.html +13 -13
  20. package/examples/module/game.js +3 -0
  21. package/examples/module/index.html +1 -1
  22. package/examples/particles/index.html +4 -1
  23. package/examples/platformer/game.js +19 -15
  24. package/examples/platformer/gameCharacter.js +3 -3
  25. package/examples/platformer/gameLevel.js +1 -0
  26. package/examples/platformer/index.html +8 -8
  27. package/examples/platformer/tiles.png +0 -0
  28. package/examples/puzzle/index.html +2 -2
  29. package/examples/starter/game.js +3 -0
  30. package/examples/starter/index.html +13 -13
  31. package/examples/stress/index.html +1 -1
  32. package/examples/typescript/game.js +2 -0
  33. package/examples/typescript/game.ts +3 -0
  34. package/examples/typescript/index.html +1 -1
  35. package/examples/uiSystem/game.js +23 -16
  36. package/examples/uiSystem/index.html +3 -14
  37. package/package.json +1 -1
  38. package/plugins/postProcess.js +2 -9
  39. package/plugins/uiSystem.js +84 -24
  40. package/src/engine.js +11 -11
  41. package/src/engineAudio.js +7 -13
  42. package/src/engineDraw.js +1 -1
  43. package/src/engineInput.js +7 -3
  44. package/src/engineMedals.js +19 -5
  45. package/src/engineObject.js +8 -5
  46. package/src/engineTileLayer.js +8 -7
  47. package/src/engineUtilities.js +59 -10
  48. package/examples/js13k/build/index.html +0 -1
  49. package/examples/js13k/build/index.js +0 -1
  50. package/examples/js13k/build/tiles.png +0 -0
  51. package/examples/js13k/game - Copy.zip +0 -0
  52. package/examples/js13k/game.zip +0 -0
  53. package/examples/starter/build/index.html +0 -2
  54. package/examples/starter/build/index.js +0 -1
  55. package/examples/starter/build/tiles.png +0 -0
  56. package/examples/starter/game.zip +0 -0
  57. package/examples/typescript/build/build/littlejs.esm.js +0 -4327
  58. package/examples/typescript/build/dist/littlejs.esm.js +0 -4425
  59. package/examples/typescript/build/examples/typescript/build.js +0 -24
  60. package/examples/typescript/build/examples/typescript/game.js +0 -100
  61. package/examples/typescript/build/examples/typescript/test/build/littlejs.esm.js +0 -3934
  62. package/examples/typescript/build/examples/typescript/test/examples/typescript/build.js +0 -86
  63. 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
  ///////////////////////////////////////////////////////////////////////////////
@@ -1436,7 +1485,7 @@ class EngineObject
1436
1485
  * @param {Color} [color=(1,1,1,1)] - Color to apply to tile when rendered
1437
1486
  * @param {Number} [renderOrder] - Objects sorted by renderOrder before being rendered
1438
1487
  */
1439
- constructor(pos=vec2(), size=vec2(1), tileInfo, angle=0, color, renderOrder=0)
1488
+ constructor(pos=vec2(), size=vec2(1), tileInfo, angle=0, color=new Color, renderOrder=0)
1440
1489
  {
1441
1490
  // set passed in params
1442
1491
  ASSERT(isVector2(pos) && isVector2(size), 'ensure pos and size are vec2s');
@@ -1550,15 +1599,18 @@ class EngineObject
1550
1599
 
1551
1600
  // apply physics
1552
1601
  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;
1602
+ this.velocity.x *= this.damping;
1603
+ this.velocity.y *= this.damping;
1604
+ if (this.mass) // dont apply gravity to static objects
1605
+ this.velocity.y += gravity * this.gravityScale;
1606
+ this.pos.x += this.velocity.x;
1607
+ this.pos.y += this.velocity.y;
1556
1608
  this.angle += this.angleVelocity *= this.angleDamping;
1557
1609
 
1558
1610
  // physics sanity checks
1559
1611
  ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
1560
1612
  ASSERT(this.damping >= 0 && this.damping <= 1);
1561
- if (!enablePhysicsSolver || !this.mass) // dont do collision for fixed objects
1613
+ if (!enablePhysicsSolver || !this.mass) // dont do collision for static objects
1562
1614
  return;
1563
1615
 
1564
1616
  const wasMovingDown = this.velocity.y < 0;
@@ -1919,7 +1971,7 @@ let drawCount;
1919
1971
  * tile(2) // a tile at index 2 using the default tile size of 16
1920
1972
  * tile(5, 8) // a tile at index 5 using a tile size of 8
1921
1973
  * 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)
1974
+ * tile(vec2(4,8), vec2(30,10)) // a tile at index (4,8) with a size of (30,10)
1923
1975
  * @memberof Draw
1924
1976
  */
1925
1977
  function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0, padding=0)
@@ -2468,7 +2520,7 @@ function keyWasReleased(key, device=0)
2468
2520
 
2469
2521
  /** Clears all input
2470
2522
  * @memberof Input */
2471
- function clearInput() { inputData = [[]]; }
2523
+ function clearInput() { inputData = [[]]; touchGamepadButtons = []; }
2472
2524
 
2473
2525
  /** Returns true if mouse button is down
2474
2526
  * @function
@@ -2633,6 +2685,7 @@ function inputInit()
2633
2685
  onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
2634
2686
  onwheel = (e)=> mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY);
2635
2687
  oncontextmenu = (e)=> false; // prevent right click menu
2688
+ onblur = (e) => clearInput(); // reset input when focus is lost
2636
2689
 
2637
2690
  // init touch input
2638
2691
  if (isTouchDevice && touchInputEnable)
@@ -2831,10 +2884,13 @@ function touchInputInit()
2831
2884
  if (touching)
2832
2885
  {
2833
2886
  touchGamepadTimer.set();
2834
- if (paused)
2887
+ if (paused && !wasTouching)
2835
2888
  {
2836
2889
  // touch anywhere to press start when paused
2837
2890
  touchGamepadButtons[9] = 1;
2891
+
2892
+ // call default touch handler so normal touch events still work
2893
+ handleTouchDefault(e);
2838
2894
  return;
2839
2895
  }
2840
2896
  }
@@ -2859,7 +2915,7 @@ function touchInputInit()
2859
2915
  const button = touchPos.subtract(buttonCenter).direction();
2860
2916
  touchGamepadButtons[button] = 1;
2861
2917
  }
2862
- else if (touchPos.distance(startCenter) < touchGamepadSize)
2918
+ else if (touchPos.distance(startCenter) < touchGamepadSize && !wasTouching)
2863
2919
  {
2864
2920
  // virtual start button in center
2865
2921
  touchGamepadButtons[9] = 1;
@@ -2947,7 +3003,7 @@ function touchGamepadRender()
2947
3003
  /** Audio context used by the engine
2948
3004
  * @type {AudioContext}
2949
3005
  * @memberof Audio */
2950
- let audioContext;
3006
+ let audioContext = new AudioContext;
2951
3007
 
2952
3008
  /** Master gain node for all audio to pass through
2953
3009
  * @type {GainNode}
@@ -2958,14 +3014,10 @@ function audioInit()
2958
3014
  {
2959
3015
  if (!soundEnable || headlessMode) return;
2960
3016
 
2961
- // create audio context
2962
- audioContext = new AudioContext;
2963
-
2964
- // create and connect gain node
2965
3017
  // (createGain is more widely spported then GainNode construtor)
2966
3018
  audioGainNode = audioContext.createGain();
2967
3019
  audioGainNode.connect(audioContext.destination);
2968
- setSoundVolume(soundVolume); // update gain volume
3020
+ audioGainNode.gain.value = soundVolume; // set starting value
2969
3021
  }
2970
3022
 
2971
3023
  ///////////////////////////////////////////////////////////////////////////////
@@ -3000,12 +3052,15 @@ class Sound
3000
3052
 
3001
3053
  /** @property {Number} - How much to randomize frequency each time sound plays */
3002
3054
  this.randomness = 0;
3055
+
3056
+ /** @property {GainNode} - Gain node for this sound */
3057
+ this.gainNode = audioContext.createGain();
3003
3058
 
3004
3059
  if (zzfxSound)
3005
3060
  {
3006
3061
  // generate zzfx sound now for fast playback
3007
3062
  const defaultRandomness = .05;
3008
- this.randomness = zzfxSound[1] || defaultRandomness;
3063
+ this.randomness = zzfxSound[1] != undefined ? zzfxSound[1] : defaultRandomness;
3009
3064
  zzfxSound[1] = 0; // generate without randomness
3010
3065
  this.sampleChannels = [zzfxG(...zzfxSound)];
3011
3066
  this.sampleRate = zzfxR;
@@ -3046,18 +3101,13 @@ class Sound
3046
3101
 
3047
3102
  // play the sound
3048
3103
  const playbackRate = pitch + pitch * this.randomness*randomnessScale*rand(-1,1);
3049
- this.gainNode = audioContext.createGain();
3050
3104
  return this.source = playSamples(this.sampleChannels, volume, playbackRate, pan, loop, this.sampleRate, this.gainNode);
3051
3105
  }
3052
3106
 
3053
3107
  /** Set the sound volume
3054
3108
  * @param {Number} [volume] - How much to scale volume by
3055
3109
  */
3056
- setVolume(volume=1)
3057
- {
3058
- if (this.gainNode)
3059
- this.gainNode.gain.value = volume;
3060
- }
3110
+ setVolume(volume=1) { this.gainNode.gain.value = volume; }
3061
3111
 
3062
3112
  /** Stop the last instance of this sound that was played */
3063
3113
  stop()
@@ -3541,18 +3591,18 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
3541
3591
 
3542
3592
 
3543
3593
 
3544
- /** The tile collision layer array, use setTileCollisionData and getTileCollisionData to access
3594
+ /** The tile collision layer grid, use setTileCollisionData and getTileCollisionData to access
3545
3595
  * @type {Array}
3546
3596
  * @memberof TileCollision */
3547
3597
  let tileCollision = [];
3548
3598
 
3549
- /** Size of the tile collision layer
3599
+ /** Size of the tile collision layer 2d grid
3550
3600
  * @type {Vector2}
3551
3601
  * @memberof TileCollision */
3552
3602
  let tileCollisionSize = vec2();
3553
3603
 
3554
3604
  /** Clear and initialize tile collision
3555
- * @param {Vector2} size
3605
+ * @param {Vector2} size - width and height of tile collision 2d grid
3556
3606
  * @memberof TileCollision */
3557
3607
  function initTileCollision(size)
3558
3608
  {
@@ -3562,7 +3612,7 @@ function initTileCollision(size)
3562
3612
  tileCollision[i] = 0;
3563
3613
  }
3564
3614
 
3565
- /** Set tile collision data
3615
+ /** Set tile collision data for a given cell in the grid
3566
3616
  * @param {Vector2} pos
3567
3617
  * @param {Number} [data]
3568
3618
  * @memberof TileCollision */
@@ -3571,7 +3621,7 @@ function setTileCollisionData(pos, data=0)
3571
3621
  pos.arrayCheck(tileCollisionSize) && (tileCollision[(pos.y|0)*tileCollisionSize.x+pos.x|0] = data);
3572
3622
  }
3573
3623
 
3574
- /** Get tile collision data
3624
+ /** Get tile collision data for a given cell in the grid
3575
3625
  * @param {Vector2} pos
3576
3626
  * @return {Number}
3577
3627
  * @memberof TileCollision */
@@ -3602,7 +3652,8 @@ function tileCollisionTest(pos, size=vec2(), object)
3602
3652
  return false;
3603
3653
  }
3604
3654
 
3605
- /** Return the center of first tile hit (does not return the exact intersection)
3655
+ /** Return the center of first tile hit, undefined if nothing was hit.
3656
+ * This does not return the exact intersection, but the center of the tile hit.
3606
3657
  * @param {Vector2} posStart
3607
3658
  * @param {Vector2} posEnd
3608
3659
  * @param {EngineObject} [object]
@@ -3847,8 +3898,8 @@ class TileLayer extends EngineObject
3847
3898
  const d = this.getData(layerPos);
3848
3899
  if (d.tile != undefined)
3849
3900
  {
3850
- const pos = this.pos.add(layerPos).add(vec2(.5));
3851
3901
  ASSERT(mainContext == this.context, 'must call redrawStart() before drawing tiles');
3902
+ const pos = layerPos.add(vec2(.5));
3852
3903
  const tileInfo = tile(d.tile, s, this.tileInfo.textureIndex);
3853
3904
  drawTile(pos, vec2(1), tileInfo, d.color, d.direction*PI/2, d.mirror);
3854
3905
  }
@@ -4277,7 +4328,7 @@ function medalsInit(saveName)
4277
4328
  // check if medals are unlocked
4278
4329
  medalsSaveName = saveName;
4279
4330
  if (!debugMedals)
4280
- medalsForEach(medal=> medal.unlocked = (localStorage[medal.storageKey()] | 0));
4331
+ medalsForEach(medal=> medal.unlocked = !!localStorage[medal.storageKey()]);
4281
4332
 
4282
4333
  // engine automatically renders medals
4283
4334
  engineAddPlugin(undefined, medalsRender);
@@ -4340,14 +4391,28 @@ class Medal
4340
4391
  constructor(id, name, description='', icon='🏆', src)
4341
4392
  {
4342
4393
  ASSERT(id >= 0 && !medals[id]);
4343
-
4344
- // save attributes and add to list of medals
4345
- medals[this.id = id] = this;
4394
+
4395
+ /** @property {Number} - The unique identifier of the medal */
4396
+ this.id = id;
4397
+
4398
+ /** @property {String} - Name of the medal */
4346
4399
  this.name = name;
4400
+
4401
+ /** @property {String} - Description of the medal */
4347
4402
  this.description = description;
4403
+
4404
+ /** @property {String} - Icon for the medal */
4348
4405
  this.icon = icon;
4406
+
4407
+ /** @property {Boolean} - Is the medal unlocked? */
4408
+ this.unlocked = false;
4409
+
4410
+ // load the source image if provided
4349
4411
  if (src)
4350
4412
  (this.image = new Image).src = src;
4413
+
4414
+ // add this to list of medals
4415
+ medals[id] = this;
4351
4416
  }
4352
4417
 
4353
4418
  /** Unlocks a medal if not already unlocked */
@@ -4358,7 +4423,7 @@ class Medal
4358
4423
 
4359
4424
  // save the medal
4360
4425
  ASSERT(medalsSaveName, 'save name must be set');
4361
- localStorage[this.storageKey()] = this.unlocked = 1;
4426
+ localStorage[this.storageKey()] = this.unlocked = true;
4362
4427
  medalsDisplayQueue.push(this);
4363
4428
  }
4364
4429
 
@@ -4766,7 +4831,7 @@ const engineName = 'LittleJS';
4766
4831
  * @type {String}
4767
4832
  * @default
4768
4833
  * @memberof Engine */
4769
- const engineVersion = '1.10.4';
4834
+ const engineVersion = '1.10.7';
4770
4835
 
4771
4836
  /** Frames per second to update
4772
4837
  * @type {Number}
@@ -4829,6 +4894,8 @@ const pluginUpdateList = [], pluginRenderList = [];
4829
4894
  * @memberof Engine */
4830
4895
  function engineAddPlugin(updateFunction, renderFunction)
4831
4896
  {
4897
+ ASSERT(!pluginUpdateList.includes(updateFunction));
4898
+ ASSERT(!pluginRenderList.includes(renderFunction));
4832
4899
  updateFunction && pluginUpdateList.push(updateFunction);
4833
4900
  renderFunction && pluginRenderList.push(renderFunction);
4834
4901
  }
@@ -4837,12 +4904,12 @@ function engineAddPlugin(updateFunction, renderFunction)
4837
4904
  // Main engine functions
4838
4905
 
4839
4906
  /** 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
4907
+ * @param {Function|function():Promise} gameInit - Called once after the engine starts up
4908
+ * @param {Function} gameUpdate - Called every frame before objects are updated
4909
+ * @param {Function} gameUpdatePost - Called after physics and objects are updated, even when paused
4910
+ * @param {Function} gameRender - Called before objects are rendered, for drawing the background
4911
+ * @param {Function} gameRenderPost - Called after objects are rendered, useful for drawing UI
4912
+ * @param {Array} [imageSources=[]] - List of images to load
4846
4913
  * @param {HTMLElement} [rootElement] - Root element to attach to, the document body by default
4847
4914
  * @memberof Engine */
4848
4915
  function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
@@ -4992,8 +5059,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4992
5059
 
4993
5060
  function startEngine()
4994
5061
  {
4995
- gameInit();
4996
- engineUpdate();
5062
+ new Promise((resolve) => resolve(gameInit())).then(engineUpdate);
4997
5063
  }
4998
5064
 
4999
5065
  if (headlessMode)
@@ -5008,7 +5074,6 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
5008
5074
  'width:100vw;height:100vh;' + // fill the window
5009
5075
  'display:flex;' + // use flexbox
5010
5076
  'align-items:center;' + // horizontal center
5011
- (canvasPixelated ? 'image-rendering:pixelated;' : '') + // pixel art
5012
5077
  'justify-content:center;' + // vertical center
5013
5078
  'background:#000;' + // set background color
5014
5079
  'user-select:none;' + // prevent hold to select
@@ -5346,4 +5411,5 @@ function drawEngineSplashScreen(t)
5346
5411
  }
5347
5412
 
5348
5413
  x.restore();
5349
- }
5414
+ }
5415
+
@@ -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
 
@@ -12,7 +12,8 @@
12
12
  function spawnBox(pos, size=1, color=WHITE, type=box2dBodyTypeDynamic, applyTexture=true, angle=0)
13
13
  {
14
14
  size = typeof size == 'number' ? vec2(size) : size; // square
15
- const o = new Box2dObject(pos, size, applyTexture && tile(3), angle, color, type);
15
+ const o = new Box2dObject(pos, size, applyTexture && spriteAtlas.squareOutline, angle, color, type);
16
+ o.drawSize = size.scale(1.02); // slightly enarge to cover gaps
16
17
  o.addBox(size);
17
18
  return o;
18
19
  }
@@ -20,7 +21,7 @@ function spawnBox(pos, size=1, color=WHITE, type=box2dBodyTypeDynamic, applyText
20
21
  function spawnCircle(pos, diameter=1, color=WHITE, type=box2dBodyTypeDynamic, applyTexture=true, angle=0)
21
22
  {
22
23
  const size = vec2(diameter);
23
- const o = new Box2dObject(pos, size, applyTexture && tile(2), angle, color, type);
24
+ const o = new Box2dObject(pos, size, applyTexture && spriteAtlas.circleOutline, angle, color, type);
24
25
  o.addCircle(diameter);
25
26
  return o;
26
27
  }
@@ -115,7 +116,7 @@ class CarObject extends Box2dObject
115
116
  const damping = .7;
116
117
  const frequencyHz = 4;
117
118
  const maxTorque = 50;
118
- const sprite = tile(4);
119
+ const sprite = spriteAtlas.wheel;
119
120
 
120
121
  // create wheels
121
122
  this.wheels = [];
@@ -208,7 +209,7 @@ class PulleyJointObjects extends Box2dObject
208
209
  {
209
210
  constructor(pos, size, color, connectionPos)
210
211
  {
211
- super(pos, size, tile(3), 0, color);
212
+ super(pos, size, spriteAtlas.squareOutline, 0, color);
212
213
  this.addBox(size);
213
214
  this.connectionPos = connectionPos;
214
215
  }
@@ -228,7 +229,7 @@ class MotorJointObject extends Box2dObject
228
229
  {
229
230
  constructor(pos, size, color, otherObject)
230
231
  {
231
- super(pos, size, tile(4), 0, color);
232
+ super(pos, size, spriteAtlas.wheel, 0, color);
232
233
  this.addCircle(size.x);
233
234
  this.connectionPos = pos;
234
235
  const joint = box2dCreateMotorJoint(otherObject, this);
@@ -265,7 +266,7 @@ class SoftBodyObject extends Box2dObject
265
266
  const mass = .1;
266
267
  const center = vec2(x-nodeSize.x/2, y-nodeSize.y/2);
267
268
  const p = pos.add(center.multiply(spacing));
268
- const o = new Box2dObject(p, vec2(objectDiameter*1.1), tile(0), 0, color);
269
+ const o = new Box2dObject(p, vec2(objectDiameter*1.1), spriteAtlas.circle, 0, color);
269
270
  o.addCircle(objectDiameter);
270
271
  o.setMass(mass);
271
272
  o.setAngularDamping(10);
@@ -336,7 +337,7 @@ class ClothObject extends Box2dObject
336
337
  const mass = .5;
337
338
  const center = vec2(x-nodeSize.x/2, y-nodeSize.y/2);
338
339
  const p = pos.add(center.multiply(spacing));
339
- const o = new Box2dObject(p, vec2(.4), tile(0), 0, color);
340
+ const o = new Box2dObject(p, vec2(.4), spriteAtlas.circle, 0, color);
340
341
  o.addCircle(objectDiameter);
341
342
  o.setFilterData(2, 2);
342
343
  o.setLinearDamping(1);
@@ -463,7 +464,7 @@ function explosion(pos, radius=3, strength=300)
463
464
  new ParticleEmitter(
464
465
  pos, 0, // pos, angle
465
466
  radius/2, .2, 50*radius, PI,// emitSize, emitTime, emitRate, emiteCone
466
- tile(1), // tileInfo
467
+ spriteAtlas.dot, // tileInfo
467
468
  hsl(0,0,0), hsl(0,0,0), // colorStartA, colorStartB
468
469
  hsl(0,0,0,0), hsl(0,0,0,0), // colorEndA, colorEndB
469
470
  1, 1, 2, .1, .1, // time, sizeStart, sizeEnd, speed, angleSpeed
@@ -475,7 +476,7 @@ function explosion(pos, radius=3, strength=300)
475
476
  new ParticleEmitter(
476
477
  pos, 0, // pos, angle
477
478
  radius, .1, 100*radius, PI, // emitSize, emitTime, emitRate, emiteCone
478
- tile(1), // tileInfo
479
+ spriteAtlas.dot, // tileInfo
479
480
  hsl(0,1,.5), hsl(.15,1,.5), // colorStartA, colorStartB
480
481
  hsl(0,1,.5,0), hsl(.1, 1,.5,0), // colorEndA, colorEndB
481
482
  .7, 1, .5, .1, .1, // time, sizeStart, sizeEnd, speed, angleSpeed
@@ -7,8 +7,8 @@
7
7
  </head><body>
8
8
 
9
9
  <script src=../../dist/littlejs.js></script>
10
- <script src=../../plugins/Box2D_v2.3.1_min.wasm.js?1100></script>
11
- <script src=../../plugins/box2d.js?1100></script>
12
- <script src=scenes.js?1100></script>
13
- <script src=gameObjects.js?1100></script>
14
- <script src=game.js?1100></script>
10
+ <script src=../../plugins/Box2D_v2.3.1_min.wasm.js?1105></script>
11
+ <script src=../../plugins/box2d.js?1105></script>
12
+ <script src=scenes.js?1105></script>
13
+ <script src=gameObjects.js?1105></script>
14
+ <script src=game.js?1105></script>
@@ -132,7 +132,7 @@ function loadScene(_scene)
132
132
  const o1 = spawnCircle(vec2(23.5,3), 3, randColor());
133
133
  const j1 = box2dCreateRevoluteJoint(groundObject, o1, o1.pos);
134
134
  const o2 = spawnCircle(vec2(28,3), 6, randColor());
135
- o1.tileInfo = o2.tileInfo = tile(5);
135
+ o1.tileInfo = o2.tileInfo = spriteAtlas.gear;
136
136
  const j2 = box2dCreateRevoluteJoint(groundObject, o2, o2.pos);
137
137
  box2dCreateGearJoint(o1, o2, j1, j2, 2);
138
138
  }
@@ -144,21 +144,21 @@ function loadScene(_scene)
144
144
  }
145
145
  {
146
146
  // distance joint
147
- const o1 = new Box2dObject(vec2(30,11), vec2(4), tile(2), 0, randColor(), box2dBodyTypeStatic);
147
+ const o1 = new Box2dObject(vec2(30,11), vec2(4), spriteAtlas.circleOutline, 0, randColor(), box2dBodyTypeStatic);
148
148
  o1.renderOrder = -2;
149
149
  const o2 = spawnCircle(vec2(30,8), 2, randColor());
150
150
  box2dCreateDistanceJoint(o1, o2);
151
151
  }
152
152
  {
153
153
  // motor joint
154
- const o = new Box2dObject(vec2(10,8), vec2(4), tile(2), 0, randColor(), box2dBodyTypeStatic);
154
+ const o = new Box2dObject(vec2(10,8), vec2(4), spriteAtlas.circleOutline, 0, randColor(), box2dBodyTypeStatic);
155
155
  o.renderOrder = -2;
156
156
  new MotorJointObject(vec2(10,8), vec2(2), randColor(), o);
157
157
  }
158
158
  {
159
159
  // friction joint
160
160
  const o = spawnBox(vec2(10,15), 3, randColor());
161
- o.tileInfo = tile(6);
161
+ o.tileInfo = spriteAtlas.squareOutline2;
162
162
  const joint = box2dCreateFrictionJoint(groundObject, o);
163
163
  joint.SetMaxForce(200);
164
164
  joint.SetMaxTorque(200);
Binary file
@@ -6,7 +6,7 @@
6
6
  <link rel=icon type=image/png href=../favicon.png>
7
7
  </head><body>
8
8
 
9
- <script src=../../dist/littlejs.js?1100></script>
9
+ <script src=../../dist/littlejs.js?1105></script>
10
10
  <script src=../../plugins/postProcess.js></script>
11
- <script src=gameObjects.js?1100></script>
12
- <script src=game.js?1100></script>
11
+ <script src=gameObjects.js?1105></script>
12
+ <script src=game.js?1105></script>
@@ -6,5 +6,5 @@
6
6
  <link rel=icon type=image/png href=../favicon.png>
7
7
  </head><body>
8
8
 
9
- <script src=../../dist/littlejs.js?1100></script>
10
- <script src=game.js?1100></script>
9
+ <script src=../../dist/littlejs.js?1105></script>
10
+ <script src=game.js?1105></script>
@@ -10,6 +10,9 @@
10
10
  // show the LittleJS splash screen
11
11
  setShowSplashScreen(true);
12
12
 
13
+ // fix texture bleeding by shrinking tile slightly
14
+ tileFixBleedScale = .5;
15
+
13
16
  // sound effects
14
17
  const sound_click = new Sound([1,.5]);
15
18
 
@@ -7,7 +7,7 @@
7
7
  </head><body>
8
8
 
9
9
  <!-- LittleJS Engine -->
10
- <script src=../../dist/littlejs.js?1100></script>
10
+ <script src=../../dist/littlejs.js?1105></script>
11
11
 
12
12
  <!-- Add your game scripts here -->
13
- <script src=game.js?1100></script>
13
+ <script src=game.js?1105></script>