littlejsengine 1.12.6 → 1.13.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 (83) hide show
  1. package/dist/littlejs.d.ts +387 -177
  2. package/dist/littlejs.esm.js +5934 -5294
  3. package/dist/littlejs.esm.min.js +4 -1
  4. package/dist/littlejs.js +5887 -5263
  5. package/dist/littlejs.min.js +4 -1
  6. package/dist/littlejs.release.js +5426 -4829
  7. package/examples/box2d/game.js +1 -1
  8. package/examples/box2d/gameObjects.js +7 -6
  9. package/examples/box2d/index.html +1 -1
  10. package/examples/box2d/scenes.js +7 -7
  11. package/examples/breakout/game.js +1 -1
  12. package/examples/breakout/gameObjects.js +2 -2
  13. package/examples/breakout/index.html +1 -1
  14. package/examples/breakoutTutorial/README.md +2 -2
  15. package/examples/breakoutTutorial/game.js +1 -1
  16. package/examples/breakoutTutorial/index.html +1 -1
  17. package/examples/empty/index.html +1 -1
  18. package/examples/htmlMenu/index.html +1 -1
  19. package/examples/index.html +396 -153
  20. package/examples/module/game.js +1 -1
  21. package/examples/module/index.html +1 -1
  22. package/examples/platformer/game.js +4 -4
  23. package/examples/platformer/gameCharacter.js +6 -6
  24. package/examples/platformer/gameEffects.js +74 -57
  25. package/examples/platformer/gameLevel.js +61 -70
  26. package/examples/platformer/gameObjects.js +4 -4
  27. package/examples/platformer/index.html +1 -1
  28. package/examples/puzzle/index.html +1 -1
  29. package/examples/shorts/base.html +23 -3
  30. package/examples/shorts/box2dCar.js +8 -12
  31. package/examples/shorts/flappyGame.js +52 -0
  32. package/examples/shorts/helloWorld.js +6 -3
  33. package/examples/shorts/hillGlideGame.js +56 -0
  34. package/examples/shorts/landerGame.js +32 -0
  35. package/examples/shorts/maze.js +47 -0
  36. package/examples/shorts/medals.js +42 -0
  37. package/examples/shorts/nineSlice.js +21 -0
  38. package/examples/shorts/piano.js +52 -0
  39. package/examples/shorts/platformer.js +24 -20
  40. package/examples/shorts/{pong.js → pongGame.js} +1 -1
  41. package/examples/shorts/raycasting.js +71 -0
  42. package/examples/shorts/slidingPuzzle.js +42 -0
  43. package/examples/shorts/spaceGame.js +56 -0
  44. package/examples/shorts/starfield.js +17 -0
  45. package/examples/shorts/tileLayer.js +3 -5
  46. package/examples/shorts/tiles.png +0 -0
  47. package/examples/shorts/tiltedView.js +8 -7
  48. package/examples/shorts/topDown.js +18 -26
  49. package/examples/shorts/uiSystem.js +4 -7
  50. package/examples/starter/build.bat +6 -1
  51. package/examples/starter/game.js +2 -2
  52. package/examples/starter/index.html +23 -13
  53. package/examples/stress/index.html +2 -3
  54. package/examples/style.css +136 -0
  55. package/examples/typescript/build.js +1 -2
  56. package/examples/typescript/game.js +2 -2
  57. package/examples/typescript/game.ts +1 -1
  58. package/examples/typescript/index.html +1 -1
  59. package/examples/uiSystem/game.js +8 -24
  60. package/examples/uiSystem/index.html +1 -1
  61. package/package.json +1 -1
  62. package/plugins/box2d.js +8 -8
  63. package/plugins/drawUtilities.js +126 -0
  64. package/plugins/newgrounds.js +1 -1
  65. package/plugins/pluginExport.js +7 -1
  66. package/plugins/postProcess.js +2 -2
  67. package/plugins/uiSystem.js +153 -65
  68. package/plugins/zzfxm.js +1 -1
  69. package/reference.md +10 -10
  70. package/src/engine.js +50 -29
  71. package/src/engineAudio.js +14 -5
  72. package/src/engineBuild.bat +6 -1
  73. package/src/engineBuild.js +20 -5
  74. package/src/engineDebug.js +53 -26
  75. package/src/engineDraw.js +108 -57
  76. package/src/engineExport.js +21 -9
  77. package/src/engineInput.js +109 -37
  78. package/src/engineObject.js +68 -67
  79. package/src/engineParticles.js +26 -9
  80. package/src/engineSettings.js +15 -16
  81. package/src/engineTileLayer.js +312 -153
  82. package/src/engineUtilities.js +182 -130
  83. package/src/engineWebGL.js +47 -36
@@ -43,10 +43,20 @@ class UISystemPlugin
43
43
  this.defaultButtonColor = hsl(0,0,.5);
44
44
  /** @property {Color} - Default hover color for UI elements */
45
45
  this.defaultHoverColor = hsl(0,0,.7);
46
+ /** @property {Color} - Default color for disabled UI elements */
47
+ this.defaultDisabledColor = hsl(0,0,.2);
46
48
  /** @property {number} - Default line width for UI elements */
47
49
  this.defaultLineWidth = 4;
50
+ /** @property {number} - Default rounded rect corner radius for UI elements */
51
+ this.defaultCornerRadius = 0;
48
52
  /** @property {string} - Default font for UI elements */
49
53
  this.defaultFont = 'arial';
54
+ /** @property {Sound} - Default sound when interactive UI element is pressed */
55
+ this.defaultSoundPress = undefined;
56
+ /** @property {Sound} - Default sound when interactive UI element is released */
57
+ this.defaultSoundRelease = undefined;
58
+ /** @property {Sound} - Default sound when interactive UI element is clicked */
59
+ this.defaultSoundClick = undefined;
50
60
  /** @property {Array<UIObject>} - List of all UI elements */
51
61
  this.uiObjects = [];
52
62
  /** @property {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} - Context to render UI elements to */
@@ -54,18 +64,30 @@ class UISystemPlugin
54
64
 
55
65
  engineAddPlugin(uiUpdate, uiRender);
56
66
 
67
+ function updateInvisible(o)
68
+ {
69
+ for(const c of o.children)
70
+ updateInvisible(c);
71
+ o.updateInvisible();
72
+ }
73
+
57
74
  // setup recursive update and render
58
75
  function uiUpdate()
59
76
  {
60
77
  function updateObject(o)
61
78
  {
62
- if (!o.visible)
63
- return;
64
- if (o.parent)
65
- o.pos = o.localPos.add(o.parent.pos);
66
- o.update();
67
- for(const c of o.children)
68
- updateObject(c);
79
+ if (o.visible)
80
+ {
81
+ // set position in parent space
82
+ if (o.parent)
83
+ o.pos = o.localPos.add(o.parent.pos);
84
+ // update in reverse order to detect mouse enter/leave
85
+ for(let i=o.children.length; i--;)
86
+ updateObject(o.children[i]);
87
+ o.update();
88
+ }
89
+ else
90
+ updateInvisible(o);
69
91
  }
70
92
  uiSystem.uiObjects.forEach(o=> o.parent || updateObject(o));
71
93
  }
@@ -90,18 +112,23 @@ class UISystemPlugin
90
112
  * @param {Vector2} size
91
113
  * @param {Color} [color=uiSystem.defaultColor]
92
114
  * @param {number} [lineWidth=uiSystem.defaultLineWidth]
93
- * @param {Color} [lineColor=uiSystem.defaultLineColor] */
94
- drawRect(pos, size, color=uiSystem.defaultColor, lineWidth=uiSystem.defaultLineWidth, lineColor=uiSystem.defaultLineColor)
115
+ * @param {Color} [lineColor=uiSystem.defaultLineColor]
116
+ * @param {number} [lineWidth=uiSystem.defaultCornerRadius] */
117
+ drawRect(pos, size, color=uiSystem.defaultColor, lineWidth=uiSystem.defaultLineWidth, lineColor=uiSystem.defaultLineColor, cornerRadius=uiSystem.defaultCornerRadius)
95
118
  {
96
- uiSystem.uiContext.fillStyle = color.toString();
97
- uiSystem.uiContext.beginPath();
98
- uiSystem.uiContext.rect(pos.x-size.x/2, pos.y-size.y/2, size.x, size.y);
99
- uiSystem.uiContext.fill();
119
+ const context = uiSystem.uiContext;
120
+ context.fillStyle = color.toString();
121
+ context.beginPath();
122
+ if (cornerRadius && context['roundRect'])
123
+ context['roundRect'](pos.x-size.x/2, pos.y-size.y/2, size.x, size.y, cornerRadius);
124
+ else
125
+ context.rect(pos.x-size.x/2, pos.y-size.y/2, size.x, size.y);
126
+ context.fill();
100
127
  if (lineWidth)
101
128
  {
102
- uiSystem.uiContext.strokeStyle = lineColor.toString();
103
- uiSystem.uiContext.lineWidth = lineWidth;
104
- uiSystem.uiContext.stroke();
129
+ context.strokeStyle = lineColor.toString();
130
+ context.lineWidth = lineWidth;
131
+ context.stroke();
105
132
  }
106
133
  }
107
134
 
@@ -112,12 +139,13 @@ class UISystemPlugin
112
139
  * @param {Color} [lineColor=uiSystem.defaultLineColor] */
113
140
  drawLine(posA, posB, lineWidth=uiSystem.defaultLineWidth, lineColor=uiSystem.defaultLineColor)
114
141
  {
115
- uiSystem.uiContext.strokeStyle = lineColor.toString();
116
- uiSystem.uiContext.lineWidth = lineWidth;
117
- uiSystem.uiContext.beginPath();
118
- uiSystem.uiContext.lineTo(posA.x, posA.y);
119
- uiSystem.uiContext.lineTo(posB.x, posB.y);
120
- uiSystem.uiContext.stroke();
142
+ const context = uiSystem.uiContext;
143
+ context.strokeStyle = lineColor.toString();
144
+ context.lineWidth = lineWidth;
145
+ context.beginPath();
146
+ context.lineTo(posA.x, posA.y);
147
+ context.lineTo(posB.x, posB.y);
148
+ context.stroke();
121
149
  }
122
150
 
123
151
  /** Draw a tile to the UI context
@@ -165,26 +193,40 @@ class UIObject
165
193
  this.pos = pos.copy();
166
194
  /** @property {Vector2} - Screen space size of the object */
167
195
  this.size = size.copy();
168
- /** @property {Color} */
196
+ /** @property {Color} - color of the object */
169
197
  this.color = uiSystem.defaultColor;
170
- /** @property {Color} */
171
- this.lineColor = uiSystem.defaultLineColor;
172
- /** @property {Color} */
198
+ /** @property {Color} - color for text */
173
199
  this.textColor = uiSystem.defaultTextColor;
174
- /** @property {Color} */
200
+ /** @property {Color} - color used when hovering over the object */
175
201
  this.hoverColor = uiSystem.defaultHoverColor;
176
- /** @property {number} */
202
+ /** @property {Color} - color for line drawing */
203
+ this.lineColor = uiSystem.defaultLineColor;
204
+ /** @property {number} - width for line drawing */
177
205
  this.lineWidth = uiSystem.defaultLineWidth;
178
- /** @property {string} */
206
+ /** @property {string} - font for this objecct */
179
207
  this.font = uiSystem.defaultFont;
180
208
  /** @property {number} - override for text height */
181
209
  this.textHeight = undefined;
182
- /** @property {boolean} */
210
+ /** @property {boolean} - should this object be drawn */
183
211
  this.visible = true;
184
- /** @property {Array<UIObject>} */
212
+ /** @property {Array<UIObject>} - a list of this object's children */
185
213
  this.children = [];
186
- /** @property {UIObject} */
214
+ /** @property {UIObject} - this object's parent, position is in parent space */
187
215
  this.parent = undefined;
216
+ /** @property {number} - Extra size added when checking if element is touched */
217
+ this.extraTouchSize = 0;
218
+ /** @property {Sound} - Sound when interactive element is pressed */
219
+ this.soundPress = uiSystem.defaultSoundPress;
220
+ /** @property {Sound} - Sound when interactive element is released */
221
+ this.soundRelease = uiSystem.defaultSoundRelease;
222
+ /** @property {Sound} - Sound when interactive element is clicked */
223
+ this.soundClick = uiSystem.defaultSoundClick;
224
+ /** @property {boolean} - Is the mouse over this element */
225
+ this.mouseIsOver = false;
226
+ /** @property {boolean} - Is this element interactive */
227
+ this.interactive = false;
228
+ /** @property {boolean} - Is the mouse held on this element (was pressed while over and hasn't been released) */
229
+ this.mouseIsHeld = false;
188
230
  uiSystem.uiObjects.push(this);
189
231
  }
190
232
 
@@ -211,55 +253,93 @@ class UIObject
211
253
  /** Update the object, called automatically by plugin once each frame */
212
254
  update()
213
255
  {
214
- // track mouse input
215
256
  const mouseWasOver = this.mouseIsOver;
257
+ const mousePress = mouseWasPressed(0);
216
258
  const mouseDown = mouseIsDown(0);
217
- if (!mouseDown || isTouchDevice)
259
+ if (mousePress || !mouseDown || this.mouseIsHeld)
218
260
  {
219
- this.mouseIsOver = isOverlapping(this.pos, this.size, mousePosScreen);
220
- if (!mouseDown && isTouchDevice)
221
- this.mouseIsOver = false;
222
- if (this.mouseIsOver && !mouseWasOver)
223
- this.onEnter();
224
- if (!this.mouseIsOver && mouseWasOver)
225
- this.onLeave();
261
+ const size = this.size.add(vec2(isTouchDevice && this.extraTouchSize || 0));
262
+ this.mouseIsOver = isOverlapping(this.pos, size, mousePosScreen);
226
263
  }
227
- if (mouseWasPressed(0) && this.mouseIsOver)
264
+ if (this.mouseIsOver)
228
265
  {
229
- this.mouseIsHeld = true;
230
- this.onPress();
231
- if (isTouchDevice)
232
- this.mouseIsOver = false;
266
+ if (mousePress)
267
+ inputClearKey(0,0,0,1,0); // clear mouse was pressed state
268
+ if (!this.disabled)
269
+ {
270
+ if (mousePress)
271
+ {
272
+ if (this.interactive)
273
+ {
274
+ this.onPress();
275
+ if (this.soundPress)
276
+ this.soundPress.play();
277
+ }
278
+ this.mouseIsHeld = true;
279
+ }
280
+ if (!mouseDown && this.mouseIsHeld && this.interactive)
281
+ {
282
+ this.onClick();
283
+ if (this.soundClick)
284
+ this.soundClick.play();
285
+ }
286
+ }
233
287
  }
234
- else if (this.mouseIsHeld && !mouseDown)
288
+ if (!mouseDown)
235
289
  {
290
+ if (this.mouseIsHeld && this.interactive)
291
+ {
292
+ this.onRelease();
293
+ if (this.soundRelease)
294
+ this.soundRelease.play();
295
+ }
296
+ if (isTouchDevice)
297
+ this.mouseIsOver = false;
236
298
  this.mouseIsHeld = false;
237
- this.onRelease();
238
299
  }
300
+
301
+ if (this.mouseIsOver != mouseWasOver)
302
+ this.mouseIsOver ? this.onEnter() : this.onLeave();
239
303
  }
240
304
 
241
305
  /** Render the object, called automatically by plugin once each frame */
242
306
  render()
243
307
  {
244
308
  if (this.size.x && this.size.y)
245
- uiSystem.drawRect(this.pos, this.size, this.color, this.lineWidth, this.lineColor);
309
+ uiSystem.drawRect(this.pos, this.size, this.color, this.lineWidth, this.lineColor, this.cornerRadius);
310
+ }
311
+
312
+ /** Special update for when object is invisible */
313
+ updateInvisible()
314
+ {
315
+ // reset input state when not visible
316
+ this.mouseIsOver = this.mouseIsHeld = false;
246
317
  }
247
318
 
248
319
  /** Called when the mouse enters the object */
249
- onEnter() {}
320
+ onEnter()
321
+ {}
250
322
 
251
323
  /** Called when the mouse leaves the object */
252
- onLeave() {}
324
+ onLeave()
325
+ {}
253
326
 
254
327
  /** Called when the mouse is pressed while over the object */
255
- onPress() {}
328
+ onPress()
329
+ {}
256
330
 
257
331
  /** Called when the mouse is released while over the object */
258
- onRelease() {}
332
+ onRelease()
333
+ {}
334
+
335
+ /** Called when user clicks on this object */
336
+ onClick()
337
+ {}
259
338
 
260
339
  /** Called when the state of this object changes */
261
- onChange() {}
262
- }
340
+ onChange()
341
+ {}
342
+ };
263
343
 
264
344
  ///////////////////////////////////////////////////////////////////////////////
265
345
  /**
@@ -346,13 +426,18 @@ class UIButton extends UIObject
346
426
 
347
427
  /** @property {string} */
348
428
  this.text = text;
429
+ /** @property {Color} */
430
+ this.disabledColor = uiSystem.defaultDisabledColor;
431
+ /** @property {boolean} */
432
+ this.disabled = false;
433
+ this.interactive = true;
349
434
  this.color = color;
350
435
  }
351
436
  render()
352
437
  {
353
- const lineColor = this.mouseIsHeld ? this.color : this.lineColor;
354
- const color = this.mouseIsOver? this.hoverColor : this.color;
355
- uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor);
438
+ const lineColor = this.mouseIsHeld && !this.disabled ? this.color : this.lineColor;
439
+ const color = this.disabled ? this.disabledColor : this.mouseIsOver ? this.hoverColor : this.color;
440
+ uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor, this.cornerRadius);
356
441
 
357
442
  const textScale = .8; // scale text to fit in button
358
443
  const textSize = vec2(this.size.x, this.textHeight || this.size.y*textScale);
@@ -379,8 +464,9 @@ class UICheckbox extends UIObject
379
464
 
380
465
  /** @property {boolean} */
381
466
  this.checked = checked;
467
+ this.interactive = true;
382
468
  }
383
- onPress()
469
+ onClick()
384
470
  {
385
471
  this.checked = !this.checked;
386
472
  this.onChange();
@@ -388,7 +474,7 @@ class UICheckbox extends UIObject
388
474
  render()
389
475
  {
390
476
  const color = this.mouseIsOver? this.hoverColor : this.color;
391
- uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, this.lineColor);
477
+ uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, this.lineColor, this.cornerRadius);
392
478
  if (this.checked)
393
479
  {
394
480
  // draw an X if checked
@@ -421,8 +507,10 @@ class UIScrollbar extends UIObject
421
507
  this.value = value;
422
508
  /** @property {string} */
423
509
  this.text = text;
424
- this.color = color;
510
+ /** @property {Color} */
425
511
  this.handleColor = handleColor;
512
+ this.color = color;
513
+ this.interactive = true;
426
514
  }
427
515
  update()
428
516
  {
@@ -442,19 +530,19 @@ class UIScrollbar extends UIObject
442
530
  {
443
531
  const lineColor = this.mouseIsHeld ? this.color : this.lineColor;
444
532
  const color = this.mouseIsOver? this.hoverColor : this.color;
445
- uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor);
533
+ uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor, this.cornerRadius);
446
534
 
447
535
  const handleSize = vec2(this.size.y);
448
536
  const handleWidth = this.size.x - handleSize.x;
449
537
  const p1 = this.pos.x - handleWidth/2;
450
538
  const p2 = this.pos.x + handleWidth/2;
451
- const handlePos = vec2(lerp(this.value, p1, p2), this.pos.y);
539
+ const handlePos = vec2(lerp(p1, p2, this.value), this.pos.y);
452
540
  const barColor = this.mouseIsHeld ? this.color : this.handleColor;
453
- uiSystem.drawRect(handlePos, handleSize, barColor, this.lineWidth, this.lineColor);
541
+ uiSystem.drawRect(handlePos, handleSize, barColor, this.lineWidth, this.lineColor, this.cornerRadius);
454
542
 
455
543
  const textScale = .8; // scale text to fit in scrollbar
456
544
  const textSize = vec2(this.size.x, this.textHeight || this.size.y*textScale);
457
545
  uiSystem.drawText(this.text, this.pos, textSize,
458
546
  this.textColor, 0, undefined, this.align, this.font);
459
547
  }
460
- }
548
+ }
package/plugins/zzfxm.js CHANGED
@@ -160,4 +160,4 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
160
160
  }
161
161
 
162
162
  return [leftChannelBuffer, rightChannelBuffer];
163
- }
163
+ }
package/reference.md CHANGED
@@ -39,11 +39,11 @@ sign(value) // Get the sign of value
39
39
  mod(dividend, divisor=1) // Get remainder of division
40
40
  clamp(value, min=0, max=1) // Clamps between values
41
41
  percent(value, valueA, valueB) // Get percentage between values
42
- lerp(percent, valueA, valueB) // Linearly interpolates between values
42
+ lerp(valueA, valueB, percent) // Linearly interpolates between values
43
43
  distanceWrap(valueA, valueB, wrapSize=1) // Signed wrapped distance between values
44
- lerpWrap(percent, valueA, valueB, wrapSize=1) // Linearly interpolates with wrapping
44
+ lerpWrap(valueA, valueB, percent, wrapSize=1) // Linearly interpolates with wrapping
45
45
  distanceAngle(angleA, angleB) // Signed wrapped distance between angles
46
- lerpAngle(percent, angleA, angleB) // Linearly interpolates with wrapping
46
+ lerpAngle(angleA, angleB, percent) // Linearly interpolates with wrapping
47
47
  smoothStep(percent) // Applies smoothstep function
48
48
  nearestPowerOfTwo(value) // Returns the nearest power of two
49
49
  isOverlapping(pointA, sizeA, pointB, sizeB) // Checks if bounding boxes overlap
@@ -55,7 +55,7 @@ formatTime(t) // Formats seconds for display
55
55
  rand(valueA=1, valueB=0) // Random float between values
56
56
  randInt(valueA, valueB=0) // Random integer between values
57
57
  randSign() // Randomly bool either -1 or 1
58
- randVector(length=1) // Random Vector2 with the passed in length
58
+ randVec2(length=1) // Random Vector2 with the passed in length
59
59
  randInCircle(radius=1, minRadius=0) // Random Vector2 within a circle
60
60
  randColor(colorA, colorB, linear) // Random color between values
61
61
 
@@ -148,7 +148,7 @@ TileInfo.size // Size of tile in pixels
148
148
  TileInfo.textureIndex // Texture index to use
149
149
  TileInfo.offset(offset) // Offset this tile by a certain amount in pixels
150
150
  TileInfo.frame(frame) // Offset this tile by a number of animation frames
151
- TileInfo.getTextureInfo() // Returns texture info for this tile
151
+ TileInfo.textureInfo // The texture info for this tile
152
152
 
153
153
  // Texture Info Object
154
154
  TextureInfo(image) // Created automatically for each image
@@ -176,7 +176,6 @@ canvasPixelated = true // Use nearest neighbor canvas scaling for more pi
176
176
  tilesPixelated = true // Disable filtering for crisper pixel art
177
177
  showSplashScreen = false // Show the LittleJS splash screen on startup
178
178
  glEnable = true // Enable fast WebGL rendering
179
- glOverlay = true // Prevent compositing the WebGL canvas
180
179
 
181
180
  // Tile sheet settings
182
181
  tileSizeDefault = (16,16) // Default size of tiles in pixels
@@ -307,7 +306,7 @@ EngineObject.mirror // Should it flip along y axis when rendered
307
306
  EngineObject.mass // Weight of object, static if 0
308
307
  EngineObject.damping // How much to slow velocity each frame (0-1)
309
308
  EngineObject.angleDamping // How much to slow rotation each frame (0-1)
310
- EngineObject.elasticity // How bouncy is it when colliding (0-1)
309
+ EngineObject.restitution // How bouncy is it when colliding (0-1)
311
310
  EngineObject.friction // How much friction when sliding (0-1)
312
311
  EngineObject.gravityScale // How much to scale gravity by
313
312
  EngineObject.renderOrder // Objects are sorted by render order
@@ -319,7 +318,7 @@ enablePhysicsSolver = true // Enable collisions between objects?
319
318
  objectDefaultMass = 1 // Default object mass for collisions
320
319
  objectDefaultDamping = 1 // How much to slow velocity by each frame (0-1)
321
320
  objectDefaultAngleDamping = 1 // How much to slow angular velocity each frame (0-1)
322
- objectDefaultElasticity = 0 // How much to bounce when a collision occurs (0-1)
321
+ objectDefaultRestitution = 0 // How much to bounce when a collision occurs (0-1)
323
322
  objectDefaultFriction = .8 // How much to slow when touching (0-1)
324
323
  objectMaxSpeed = 1 // Clamp max speed to avoid fast objects missing collisions
325
324
  gravity = (0,0) // How much gravity to apply to objects
@@ -358,11 +357,12 @@ TileLayerData.clear() // Clear this tile data
358
357
 
359
358
  // Tile Collision Layer
360
359
  TileCollisionLayer(position, size, tileInfo=tile()) // Create a tile collision layer object
361
- TileCollisionLayer.size // Size of the tile collision layer
362
360
  TileCollisionLayer.setCollisionData(pos, data=1) // Set tile collision data at pos
363
- getTileCollisionData(pos) // Get tile collision data at pos
361
+ tileCollisionGetData(pos) // Get tile collision data at pos
364
362
  tileCollisionTest(pos, size=(0,0), object) // Check if collision should occur
365
363
  tileCollisionRaycast(posStart, posEnd, object) // Return the center of tile if hit
364
+ tileCollisionLoad(tileMapData, tileInfo) // Load tile layers from exported data
365
+
366
366
  ```
367
367
 
368
368
  ## LittleJS Particle System
package/src/engine.js CHANGED
@@ -30,7 +30,7 @@ const engineName = 'LittleJS';
30
30
  * @type {string}
31
31
  * @default
32
32
  * @memberof Engine */
33
- const engineVersion = '1.12.6';
33
+ const engineVersion = '1.13.1';
34
34
 
35
35
  /** Frames per second to update
36
36
  * @type {number}
@@ -75,10 +75,15 @@ let timeReal = 0;
75
75
  * @memberof Engine */
76
76
  let paused = false;
77
77
 
78
+ /** Get if game is paused
79
+ * @return {boolean}
80
+ * @memberof Engine */
81
+ function getPaused() { return paused; }
82
+
78
83
  /** Set if game is paused
79
- * @param {boolean} isPaused
84
+ * @param {boolean} [isPaused]
80
85
  * @memberof Engine */
81
- function setPaused(isPaused) { paused = isPaused; }
86
+ function setPaused(isPaused=true) { paused = isPaused; }
82
87
 
83
88
  // Frame time tracking
84
89
  let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
@@ -101,16 +106,26 @@ function engineAddPlugin(updateFunction, renderFunction)
101
106
  }
102
107
 
103
108
  ///////////////////////////////////////////////////////////////////////////////
104
- // Main engine functions
109
+ // Main Engine Functions
105
110
 
106
111
  /** Startup LittleJS engine with your callback functions
107
- * @param {Function|function():Promise} gameInit - Called once after the engine starts up
108
- * @param {Function} gameUpdate - Called every frame before objects are updated
109
- * @param {Function} gameUpdatePost - Called after physics and objects are updated, even when paused
110
- * @param {Function} gameRender - Called before objects are rendered, for drawing the background
111
- * @param {Function} gameRenderPost - Called after objects are rendered, useful for drawing UI
112
- * @param {Array<string>} [imageSources=[]] - List of images to load
113
- * @param {HTMLElement} [rootElement] - Root element to attach to, the document body by default
112
+ * @param {Function|function():Promise} gameInit - Called once after the engine starts up, can be async for loading
113
+ * @param {Function} gameUpdate - Called every frame before objects are updated (60fps), use for game logic
114
+ * @param {Function} gameUpdatePost - Called after physics and objects are updated, even when paused, use for UI updates
115
+ * @param {Function} gameRender - Called before objects are rendered, use for drawing backgrounds/world elements
116
+ * @param {Function} gameRenderPost - Called after objects are rendered, use for drawing UI/overlays
117
+ * @param {Array<string>} [imageSources=[]] - List of image file paths to preload (e.g., ['player.png', 'tiles.png'])
118
+ * @param {HTMLElement} [rootElement] - Root DOM element to attach canvas to, defaults to document.body
119
+ * @example
120
+ * // Basic engine startup
121
+ * engineInit(
122
+ * () => { console.log('Game initialized!'); }, // gameInit
123
+ * () => { updatePlayer(); }, // gameUpdate
124
+ * () => { updateUI(); }, // gameUpdatePost
125
+ * () => { drawBackground(); }, // gameRender
126
+ * () => { drawHUD(); }, // gameRenderPost
127
+ * ['tiles.png', 'tilesLevel.png'] // images to load
128
+ * );
114
129
  * @memberof Engine */
115
130
  async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
116
131
  {
@@ -145,7 +160,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
145
160
  let frameTimeDeltaMS = frameTimeMS - frameTimeLastMS;
146
161
  frameTimeLastMS = frameTimeMS;
147
162
  if (debug || showWatermark)
148
- averageFPS = lerp(.05, averageFPS, 1e3/(frameTimeDeltaMS||1));
163
+ averageFPS = lerp(averageFPS, 1e3/(frameTimeDeltaMS||1), .05);
149
164
  const debugSpeedUp = debug && keyIsDown('Equal'); // +
150
165
  const debugSpeedDown = debug && keyIsDown('Minus'); // -
151
166
  if (debug) // +/- to speed/slow time
@@ -154,9 +169,8 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
154
169
  frameTimeBufferMS += paused ? 0 : frameTimeDeltaMS;
155
170
  if (!debugSpeedUp)
156
171
  frameTimeBufferMS = min(frameTimeBufferMS, 50); // clamp min framerate
157
- if (debug && debugVideoCaptureIsActive())
158
- frameTimeBufferMS = 0; // disable time smoothing when capturing video
159
-
172
+ if (debugVideoCaptureIsActive())
173
+ frameTimeBufferMS = 0; // no time smoothing when capturing video
160
174
  updateCanvas();
161
175
 
162
176
  if (paused)
@@ -215,11 +229,12 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
215
229
  pluginRenderList.forEach(f=>f());
216
230
  touchGamepadRender();
217
231
  debugRender();
218
- glCopyToContext(mainContext);
232
+ glFlush();
233
+ debugVideoCaptureUpdate();
219
234
 
220
- if (showWatermark)
235
+ if (showWatermark && !debugVideoCaptureIsActive())
221
236
  {
222
- // update fps
237
+ // update fps display
223
238
  overlayContext.textAlign = 'right';
224
239
  overlayContext.textBaseline = 'top';
225
240
  overlayContext.font = '1em monospace';
@@ -230,11 +245,10 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
230
245
  overlayContext.fillText(text, mainCanvas.width-3, 3);
231
246
  overlayContext.fillStyle = '#fff';
232
247
  overlayContext.fillText(text, mainCanvas.width-2, 2);
233
- drawCount = 0;
234
248
  }
249
+ if (debug || showWatermark)
250
+ drawCount = 0;
235
251
  }
236
-
237
- debugVideoCaptureUpdate();
238
252
  requestAnimationFrame(engineUpdate);
239
253
  }
240
254
 
@@ -281,6 +295,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
281
295
  // setup html
282
296
  const styleRoot =
283
297
  'margin:0;' + // fill the window
298
+ 'overflow:hidden;' + // no scroll bars
284
299
  'background:#000;' + // set background color
285
300
  (canvasPixelated ? 'image-rendering:pixelated;' : '') + // pixel art
286
301
  'user-select:none;' + // prevent hold to select
@@ -289,8 +304,9 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
289
304
  'touch-action:none;' + // prevent mobile pinch to resize
290
305
  '-webkit-touch-callout:none');// compatibility for ios
291
306
  rootElement.style.cssText = styleRoot;
292
- rootElement.appendChild(mainCanvas = document.createElement('canvas'));
293
- mainContext = mainCanvas.getContext('2d');
307
+ drawCanvas = mainCanvas = document.createElement('canvas');
308
+ rootElement.appendChild(mainCanvas);
309
+ drawContext = mainContext = mainCanvas.getContext('2d');
294
310
 
295
311
  // init stuff and start engine
296
312
  inputInit();
@@ -299,7 +315,8 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
299
315
  glInit();
300
316
 
301
317
  // create overlay canvas for hud to appear above gl canvas
302
- rootElement.appendChild(overlayCanvas = document.createElement('canvas'));
318
+ overlayCanvas = document.createElement('canvas')
319
+ rootElement.appendChild(overlayCanvas);
303
320
  overlayContext = overlayCanvas.getContext('2d');
304
321
 
305
322
  // set canvas style
@@ -317,7 +334,9 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
317
334
  const image = new Image;
318
335
  image.onerror = image.onload = ()=>
319
336
  {
320
- textureInfos[textureIndex] = new TextureInfo(image);
337
+ const textureInfo = new TextureInfo(image);
338
+ textureInfo.createWebGLTexture();
339
+ textureInfos[textureIndex] = textureInfo;
321
340
  resolve();
322
341
  }
323
342
  image.crossOrigin = 'anonymous';
@@ -330,7 +349,9 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
330
349
  // no images to load
331
350
  promises.push(new Promise(resolve =>
332
351
  {
333
- textureInfos[0] = new TextureInfo(new Image);
352
+ const textureInfo = new TextureInfo(new Image);
353
+ textureInfos[0] = textureInfo;
354
+ textureInfo.createWebGLTexture();
334
355
  resolve();
335
356
  }));
336
357
  }
@@ -345,7 +366,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
345
366
  updateSplash();
346
367
  function updateSplash()
347
368
  {
348
- clearInput();
369
+ inputClear();
349
370
  drawEngineSplashScreen(t+=.01);
350
371
  t>1 ? resolve() : setTimeout(updateSplash, 16);
351
372
  }
@@ -470,7 +491,7 @@ function drawEngineSplashScreen(t)
470
491
  const p3 = percent(t, 1, .8);
471
492
  const p4 = percent(t, 0, .5);
472
493
  const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,Math.hypot(w,h)*.7);
473
- g.addColorStop(0,hsl(0,0,lerp(p4,0,p3/2),p3).toString());
494
+ g.addColorStop(0,hsl(0,0,lerp(0,p3/2,p4),p3).toString());
474
495
  g.addColorStop(1,hsl(0,0,0,p3).toString());
475
496
  x.save();
476
497
  x.fillStyle = g;
@@ -621,4 +642,4 @@ function drawEngineSplashScreen(t)
621
642
  }
622
643
 
623
644
  x.restore();
624
- }
645
+ }
@@ -122,11 +122,20 @@ class Sound
122
122
  this.gainNode.gain.value = volume;
123
123
  }
124
124
 
125
- /** Stop the last instance of this sound that was played */
126
- stop()
125
+ /** Stop the last instance of this sound that was played
126
+ * @param {number} [fadeTime] - How long to fade out (seconds)
127
+ */
128
+ stop(fadeTime=0)
127
129
  {
128
- if (this.source)
129
- this.source.stop();
130
+ if (!this.source)
131
+ return;
132
+
133
+ // ramp off gain
134
+ const startFade = audioContext.currentTime;
135
+ const endFade = startFade + fadeTime;
136
+ this.gainNode.gain.linearRampToValueAtTime(1, startFade);
137
+ this.gainNode.gain.linearRampToValueAtTime(0, endFade);
138
+ this.source.stop(endFade);
130
139
  this.source = undefined;
131
140
  }
132
141
 
@@ -469,4 +478,4 @@ function zzfxG
469
478
  }
470
479
 
471
480
  return b; // return sample buffer
472
- }
481
+ }
@@ -1,2 +1,7 @@
1
1
  rem LittleJS Build Script
2
- call npm run build
2
+ call npm run build
3
+ if %errorlevel% neq 0 (
4
+ echo Build failed with error level %errorlevel%
5
+ pause
6
+ exit /b %errorlevel%
7
+ )