littlejsengine 1.12.4 → 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 (88) hide show
  1. package/dist/box2d.wasm.js +630 -0
  2. package/dist/box2d.wasm.wasm +0 -0
  3. package/dist/littlejs.d.ts +437 -219
  4. package/dist/littlejs.esm.js +5950 -5307
  5. package/dist/littlejs.esm.min.js +4 -1
  6. package/dist/littlejs.js +5921 -5295
  7. package/dist/littlejs.min.js +4 -1
  8. package/dist/littlejs.release.js +5485 -4886
  9. package/examples/box2d/game.js +37 -42
  10. package/examples/box2d/gameObjects.js +41 -37
  11. package/examples/box2d/index.html +2 -2
  12. package/examples/box2d/scenes.js +24 -20
  13. package/examples/box2d/tiles.png +0 -0
  14. package/examples/breakout/game.js +1 -1
  15. package/examples/breakout/gameObjects.js +2 -2
  16. package/examples/breakout/index.html +1 -1
  17. package/examples/breakoutTutorial/README.md +2 -2
  18. package/examples/breakoutTutorial/game.js +1 -1
  19. package/examples/breakoutTutorial/index.html +1 -1
  20. package/examples/empty/index.html +1 -1
  21. package/examples/htmlMenu/index.html +1 -1
  22. package/examples/index.html +410 -159
  23. package/examples/module/game.js +1 -1
  24. package/examples/module/index.html +1 -1
  25. package/examples/platformer/game.js +6 -15
  26. package/examples/platformer/gameCharacter.js +6 -6
  27. package/examples/platformer/gameEffects.js +74 -57
  28. package/examples/platformer/gameLevel.js +61 -70
  29. package/examples/platformer/gameObjects.js +4 -4
  30. package/examples/platformer/index.html +1 -1
  31. package/examples/puzzle/index.html +1 -1
  32. package/examples/shorts/base.html +24 -3
  33. package/examples/shorts/box2d.js +46 -0
  34. package/examples/shorts/box2dCar.js +45 -0
  35. package/examples/shorts/flappyGame.js +52 -0
  36. package/examples/shorts/helloWorld.js +6 -3
  37. package/examples/shorts/hillGlideGame.js +56 -0
  38. package/examples/shorts/landerGame.js +32 -0
  39. package/examples/shorts/maze.js +47 -0
  40. package/examples/shorts/medals.js +42 -0
  41. package/examples/shorts/nineSlice.js +21 -0
  42. package/examples/shorts/piano.js +52 -0
  43. package/examples/shorts/platformer.js +24 -20
  44. package/examples/shorts/{pong.js → pongGame.js} +1 -1
  45. package/examples/shorts/postProcess.js +45 -0
  46. package/examples/shorts/raycasting.js +71 -0
  47. package/examples/shorts/slidingPuzzle.js +42 -0
  48. package/examples/shorts/spaceGame.js +56 -0
  49. package/examples/shorts/starfield.js +17 -0
  50. package/examples/shorts/tileLayer.js +3 -5
  51. package/examples/shorts/tiles.png +0 -0
  52. package/examples/shorts/tiltedView.js +8 -7
  53. package/examples/shorts/topDown.js +18 -26
  54. package/examples/shorts/uiSystem.js +36 -0
  55. package/examples/starter/build.bat +6 -1
  56. package/examples/starter/game.js +4 -2
  57. package/examples/starter/index.html +23 -13
  58. package/examples/stress/index.html +2 -3
  59. package/examples/style.css +136 -0
  60. package/examples/typescript/build.js +1 -2
  61. package/examples/typescript/game.js +2 -2
  62. package/examples/typescript/game.ts +1 -1
  63. package/examples/typescript/index.html +1 -1
  64. package/examples/uiSystem/game.js +9 -25
  65. package/examples/uiSystem/index.html +1 -1
  66. package/package.json +1 -1
  67. package/plugins/box2d.js +29 -35
  68. package/plugins/drawUtilities.js +126 -0
  69. package/plugins/newgrounds.js +1 -1
  70. package/plugins/pluginExport.js +8 -2
  71. package/plugins/postProcess.js +2 -2
  72. package/plugins/uiSystem.js +162 -68
  73. package/plugins/zzfxm.js +1 -1
  74. package/reference.md +10 -10
  75. package/src/engine.js +59 -39
  76. package/src/engineAudio.js +38 -20
  77. package/src/engineBuild.bat +6 -1
  78. package/src/engineBuild.js +32 -7
  79. package/src/engineDebug.js +53 -26
  80. package/src/engineDraw.js +108 -57
  81. package/src/engineExport.js +22 -9
  82. package/src/engineInput.js +112 -40
  83. package/src/engineObject.js +68 -67
  84. package/src/engineParticles.js +26 -9
  85. package/src/engineSettings.js +15 -16
  86. package/src/engineTileLayer.js +312 -153
  87. package/src/engineUtilities.js +198 -130
  88. package/src/engineWebGL.js +58 -35
@@ -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,24 +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
- /** @property {boolean} */
208
+ /** @property {number} - override for text height */
209
+ this.textHeight = undefined;
210
+ /** @property {boolean} - should this object be drawn */
181
211
  this.visible = true;
182
- /** @property {Array<UIObject>} */
212
+ /** @property {Array<UIObject>} - a list of this object's children */
183
213
  this.children = [];
184
- /** @property {UIObject} */
214
+ /** @property {UIObject} - this object's parent, position is in parent space */
185
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;
186
230
  uiSystem.uiObjects.push(this);
187
231
  }
188
232
 
@@ -209,55 +253,93 @@ class UIObject
209
253
  /** Update the object, called automatically by plugin once each frame */
210
254
  update()
211
255
  {
212
- // track mouse input
213
256
  const mouseWasOver = this.mouseIsOver;
257
+ const mousePress = mouseWasPressed(0);
214
258
  const mouseDown = mouseIsDown(0);
215
- if (!mouseDown || isTouchDevice)
259
+ if (mousePress || !mouseDown || this.mouseIsHeld)
216
260
  {
217
- this.mouseIsOver = isOverlapping(this.pos, this.size, mousePosScreen);
218
- if (!mouseDown && isTouchDevice)
219
- this.mouseIsOver = false;
220
- if (this.mouseIsOver && !mouseWasOver)
221
- this.onEnter();
222
- if (!this.mouseIsOver && mouseWasOver)
223
- this.onLeave();
261
+ const size = this.size.add(vec2(isTouchDevice && this.extraTouchSize || 0));
262
+ this.mouseIsOver = isOverlapping(this.pos, size, mousePosScreen);
224
263
  }
225
- if (mouseWasPressed(0) && this.mouseIsOver)
264
+ if (this.mouseIsOver)
226
265
  {
227
- this.mouseIsHeld = true;
228
- this.onPress();
229
- if (isTouchDevice)
230
- 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
+ }
231
287
  }
232
- else if (this.mouseIsHeld && !mouseDown)
288
+ if (!mouseDown)
233
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;
234
298
  this.mouseIsHeld = false;
235
- this.onRelease();
236
299
  }
300
+
301
+ if (this.mouseIsOver != mouseWasOver)
302
+ this.mouseIsOver ? this.onEnter() : this.onLeave();
237
303
  }
238
304
 
239
305
  /** Render the object, called automatically by plugin once each frame */
240
306
  render()
241
307
  {
242
308
  if (this.size.x && this.size.y)
243
- 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;
244
317
  }
245
318
 
246
319
  /** Called when the mouse enters the object */
247
- onEnter() {}
320
+ onEnter()
321
+ {}
248
322
 
249
323
  /** Called when the mouse leaves the object */
250
- onLeave() {}
324
+ onLeave()
325
+ {}
251
326
 
252
327
  /** Called when the mouse is pressed while over the object */
253
- onPress() {}
328
+ onPress()
329
+ {}
254
330
 
255
331
  /** Called when the mouse is released while over the object */
256
- onRelease() {}
332
+ onRelease()
333
+ {}
334
+
335
+ /** Called when user clicks on this object */
336
+ onClick()
337
+ {}
257
338
 
258
339
  /** Called when the state of this object changes */
259
- onChange() {}
260
- }
340
+ onChange()
341
+ {}
342
+ };
261
343
 
262
344
  ///////////////////////////////////////////////////////////////////////////////
263
345
  /**
@@ -287,7 +369,8 @@ class UIText extends UIObject
287
369
  }
288
370
  render()
289
371
  {
290
- uiSystem.drawText(this.text, this.pos, this.size, this.textColor, this.lineWidth, this.lineColor, this.align, this.font);
372
+ const textSize = vec2(this.size.x, this.textHeight || this.size.y);
373
+ uiSystem.drawText(this.text, this.pos, textSize, this.textColor, this.lineWidth, this.lineColor, this.align, this.font);
291
374
  }
292
375
  }
293
376
 
@@ -343,14 +426,21 @@ class UIButton extends UIObject
343
426
 
344
427
  /** @property {string} */
345
428
  this.text = text;
429
+ /** @property {Color} */
430
+ this.disabledColor = uiSystem.defaultDisabledColor;
431
+ /** @property {boolean} */
432
+ this.disabled = false;
433
+ this.interactive = true;
346
434
  this.color = color;
347
435
  }
348
436
  render()
349
437
  {
350
- const lineColor = this.mouseIsHeld ? this.color : this.lineColor;
351
- const color = this.mouseIsOver? this.hoverColor : this.color;
352
- uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor);
353
- const textSize = vec2(this.size.x, this.size.y*.8);
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);
441
+
442
+ const textScale = .8; // scale text to fit in button
443
+ const textSize = vec2(this.size.x, this.textHeight || this.size.y*textScale);
354
444
  uiSystem.drawText(this.text, this.pos, textSize,
355
445
  this.textColor, 0, undefined, this.align, this.font);
356
446
  }
@@ -374,8 +464,9 @@ class UICheckbox extends UIObject
374
464
 
375
465
  /** @property {boolean} */
376
466
  this.checked = checked;
467
+ this.interactive = true;
377
468
  }
378
- onPress()
469
+ onClick()
379
470
  {
380
471
  this.checked = !this.checked;
381
472
  this.onChange();
@@ -383,7 +474,7 @@ class UICheckbox extends UIObject
383
474
  render()
384
475
  {
385
476
  const color = this.mouseIsOver? this.hoverColor : this.color;
386
- 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);
387
478
  if (this.checked)
388
479
  {
389
480
  // draw an X if checked
@@ -416,8 +507,10 @@ class UIScrollbar extends UIObject
416
507
  this.value = value;
417
508
  /** @property {string} */
418
509
  this.text = text;
419
- this.color = color;
510
+ /** @property {Color} */
420
511
  this.handleColor = handleColor;
512
+ this.color = color;
513
+ this.interactive = true;
421
514
  }
422
515
  update()
423
516
  {
@@ -437,18 +530,19 @@ class UIScrollbar extends UIObject
437
530
  {
438
531
  const lineColor = this.mouseIsHeld ? this.color : this.lineColor;
439
532
  const color = this.mouseIsOver? this.hoverColor : this.color;
440
- uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor);
533
+ uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor, this.cornerRadius);
441
534
 
442
535
  const handleSize = vec2(this.size.y);
443
536
  const handleWidth = this.size.x - handleSize.x;
444
537
  const p1 = this.pos.x - handleWidth/2;
445
538
  const p2 = this.pos.x + handleWidth/2;
446
- const handlePos = vec2(lerp(this.value, p1, p2), this.pos.y);
539
+ const handlePos = vec2(lerp(p1, p2, this.value), this.pos.y);
447
540
  const barColor = this.mouseIsHeld ? this.color : this.handleColor;
448
- uiSystem.drawRect(handlePos, handleSize, barColor, this.lineWidth, this.lineColor);
541
+ uiSystem.drawRect(handlePos, handleSize, barColor, this.lineWidth, this.lineColor, this.cornerRadius);
449
542
 
450
- const textSize = vec2(this.size.x, this.size.y*.8);
543
+ const textScale = .8; // scale text to fit in scrollbar
544
+ const textSize = vec2(this.size.x, this.textHeight || this.size.y*textScale);
451
545
  uiSystem.drawText(this.text, this.pos, textSize,
452
546
  this.textColor, 0, undefined, this.align, this.font);
453
547
  }
454
- }
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.4';
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,18 +106,28 @@ 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
- function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
130
+ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
116
131
  {
117
132
  ASSERT(!mainContext, 'engine already initialized');
118
133
  ASSERT(Array.isArray(imageSources), 'pass in images as array');
@@ -145,7 +160,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
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 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
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 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
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 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
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
 
@@ -269,20 +283,19 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
269
283
  mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
270
284
  }
271
285
 
272
- function startEngine()
286
+ // wait for gameInit to load
287
+ async function startEngine()
273
288
  {
274
- new Promise((resolve) => resolve(gameInit())).then(engineUpdate);
289
+ await gameInit();
290
+ engineUpdate();
275
291
  }
276
-
277
292
  if (headlessMode)
278
- {
279
- startEngine();
280
- return;
281
- }
293
+ return startEngine();
282
294
 
283
295
  // setup html
284
296
  const styleRoot =
285
297
  'margin:0;' + // fill the window
298
+ 'overflow:hidden;' + // no scroll bars
286
299
  'background:#000;' + // set background color
287
300
  (canvasPixelated ? 'image-rendering:pixelated;' : '') + // pixel art
288
301
  'user-select:none;' + // prevent hold to select
@@ -291,8 +304,9 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
291
304
  'touch-action:none;' + // prevent mobile pinch to resize
292
305
  '-webkit-touch-callout:none');// compatibility for ios
293
306
  rootElement.style.cssText = styleRoot;
294
- rootElement.appendChild(mainCanvas = document.createElement('canvas'));
295
- mainContext = mainCanvas.getContext('2d');
307
+ drawCanvas = mainCanvas = document.createElement('canvas');
308
+ rootElement.appendChild(mainCanvas);
309
+ drawContext = mainContext = mainCanvas.getContext('2d');
296
310
 
297
311
  // init stuff and start engine
298
312
  inputInit();
@@ -301,7 +315,8 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
301
315
  glInit();
302
316
 
303
317
  // create overlay canvas for hud to appear above gl canvas
304
- rootElement.appendChild(overlayCanvas = document.createElement('canvas'));
318
+ overlayCanvas = document.createElement('canvas')
319
+ rootElement.appendChild(overlayCanvas);
305
320
  overlayContext = overlayCanvas.getContext('2d');
306
321
 
307
322
  // set canvas style
@@ -319,7 +334,9 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
319
334
  const image = new Image;
320
335
  image.onerror = image.onload = ()=>
321
336
  {
322
- textureInfos[textureIndex] = new TextureInfo(image);
337
+ const textureInfo = new TextureInfo(image);
338
+ textureInfo.createWebGLTexture();
339
+ textureInfos[textureIndex] = textureInfo;
323
340
  resolve();
324
341
  }
325
342
  image.crossOrigin = 'anonymous';
@@ -332,7 +349,9 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
332
349
  // no images to load
333
350
  promises.push(new Promise(resolve =>
334
351
  {
335
- textureInfos[0] = new TextureInfo(new Image);
352
+ const textureInfo = new TextureInfo(new Image);
353
+ textureInfos[0] = textureInfo;
354
+ textureInfo.createWebGLTexture();
336
355
  resolve();
337
356
  }));
338
357
  }
@@ -347,15 +366,16 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
347
366
  updateSplash();
348
367
  function updateSplash()
349
368
  {
350
- clearInput();
369
+ inputClear();
351
370
  drawEngineSplashScreen(t+=.01);
352
371
  t>1 ? resolve() : setTimeout(updateSplash, 16);
353
372
  }
354
373
  }));
355
374
  }
356
375
 
357
- // load all of the images
358
- Promise.all(promises).then(startEngine);
376
+ // wait for all the promises to finish
377
+ await Promise.all(promises);
378
+ return startEngine();
359
379
  }
360
380
 
361
381
  /** Update each engine object, remove destroyed objects, and update time
@@ -471,7 +491,7 @@ function drawEngineSplashScreen(t)
471
491
  const p3 = percent(t, 1, .8);
472
492
  const p4 = percent(t, 0, .5);
473
493
  const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,Math.hypot(w,h)*.7);
474
- 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());
475
495
  g.addColorStop(1,hsl(0,0,0,p3).toString());
476
496
  x.save();
477
497
  x.fillStyle = g;
@@ -622,4 +642,4 @@ function drawEngineSplashScreen(t)
622
642
  }
623
643
 
624
644
  x.restore();
625
- }
645
+ }