littlejsengine 1.14.5 → 1.14.8

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 (45) hide show
  1. package/dist/littlejs.d.ts +53 -19
  2. package/dist/littlejs.esm.js +183 -106
  3. package/dist/littlejs.esm.min.js +1 -1
  4. package/dist/littlejs.js +179 -104
  5. package/dist/littlejs.min.js +1 -1
  6. package/dist/littlejs.release.js +178 -103
  7. package/examples/box2d/game.js +1 -3
  8. package/examples/box2d/gameObjects.js +2 -2
  9. package/examples/htmlMenu/game.js +1 -1
  10. package/examples/index.html +92 -51
  11. package/examples/puzzle/game.js +1 -2
  12. package/examples/shorts/animation.js +2 -2
  13. package/examples/shorts/box2d.js +1 -0
  14. package/examples/shorts/box2dCar.js +1 -0
  15. package/examples/shorts/flappyGame.js +2 -1
  16. package/examples/shorts/helloWorld.js +2 -2
  17. package/examples/shorts/hillGlideGame.js +6 -3
  18. package/examples/shorts/maze.js +1 -0
  19. package/examples/shorts/medals.js +3 -0
  20. package/examples/shorts/music.js +3 -5
  21. package/examples/shorts/piano.js +24 -25
  22. package/examples/shorts/platformer.js +1 -0
  23. package/examples/shorts/shapes.js +6 -6
  24. package/examples/shorts/sound.js +21 -32
  25. package/examples/shorts/spriteAtlas.js +1 -2
  26. package/examples/shorts/tileLayer.js +1 -0
  27. package/examples/shorts/tiltedView.js +1 -1
  28. package/examples/shorts/timers.js +34 -31
  29. package/examples/shorts/topDown.js +2 -1
  30. package/examples/shorts/uiSystem.js +15 -10
  31. package/examples/stress/index.html +1 -1
  32. package/examples/uiSystem/game.js +1 -1
  33. package/package.json +1 -1
  34. package/plugins/uiSystem.js +83 -58
  35. package/reference.md +1 -0
  36. package/src/engine.js +23 -6
  37. package/src/engineDebug.js +1 -1
  38. package/src/engineDraw.js +19 -12
  39. package/src/engineExport.js +4 -2
  40. package/src/engineInput.js +7 -0
  41. package/src/engineObject.js +4 -4
  42. package/src/engineSettings.js +11 -1
  43. package/src/engineTileLayer.js +2 -2
  44. package/src/engineUtilities.js +9 -2
  45. package/src/engineWebGL.js +20 -18
package/src/engineDraw.js CHANGED
@@ -136,8 +136,9 @@ class TileInfo
136
136
  * @param {Vector2} [size=tileSizeDefault] - Size of tile in pixels
137
137
  * @param {number} [textureIndex] - Texture index to use
138
138
  * @param {number} [padding] - How many pixels padding around tiles
139
+ * @param {number} [bleedScale] - How many pixels smaller to draw tiles
139
140
  */
140
- constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0, padding=0)
141
+ constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0, padding=0, bleedScale=tileFixBleedScale)
141
142
  {
142
143
  /** @property {Vector2} - Top left corner of tile in pixels */
143
144
  this.pos = pos.copy();
@@ -149,6 +150,8 @@ class TileInfo
149
150
  this.padding = padding;
150
151
  /** @property {TextureInfo} - The texture info for this tile */
151
152
  this.textureInfo = textureInfos[this.textureIndex];
153
+ /** @property {float} - Shrinks tile by this many pixels to prevent neighbors bleeding */
154
+ this.bleedScale = bleedScale;
152
155
  }
153
156
 
154
157
  /** Returns a copy of this tile offset by a vector
@@ -156,7 +159,7 @@ class TileInfo
156
159
  * @return {TileInfo}
157
160
  */
158
161
  offset(offset)
159
- { return new TileInfo(this.pos.add(offset), this.size, this.textureIndex); }
162
+ { return new TileInfo(this.pos.add(offset), this.size, this.textureIndex, this.padding, this.bleedScale); }
160
163
 
161
164
  /** Returns a copy of this tile offset by a number of animation frames
162
165
  * @param {number} frame - Offset to apply in animation frames
@@ -179,6 +182,8 @@ class TileInfo
179
182
  this.pos = new Vector2;
180
183
  this.size = new Vector2(image.width, image.height);
181
184
  this.textureInfo = new TextureInfo(image, glTexture);
185
+ // do not use padding or bleed
186
+ this.bleedScale = this.padding = 0;
182
187
  return this;
183
188
  }
184
189
  }
@@ -236,6 +241,7 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
236
241
  ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
237
242
 
238
243
  const textureInfo = tileInfo && tileInfo.textureInfo;
244
+ const bleedScale = tileInfo ? tileInfo.bleedScale : 0;
239
245
  if (useWebGL)
240
246
  {
241
247
  if (screenSpace)
@@ -253,10 +259,10 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
253
259
  const w = tileInfo.size.x * sizeInverse.x;
254
260
  const h = tileInfo.size.y * sizeInverse.y;
255
261
  glSetTexture(textureInfo.glTexture);
256
- if (tileFixBleedScale)
262
+ if (bleedScale)
257
263
  {
258
- const tileImageFixBleedX = sizeInverse.x*tileFixBleedScale;
259
- const tileImageFixBleedY = sizeInverse.y*tileFixBleedScale;
264
+ const tileImageFixBleedX = sizeInverse.x*bleedScale;
265
+ const tileImageFixBleedY = sizeInverse.y*bleedScale;
260
266
  glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
261
267
  x + tileImageFixBleedX, y + tileImageFixBleedY,
262
268
  x - tileImageFixBleedX + w, y - tileImageFixBleedY + h,
@@ -287,7 +293,7 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
287
293
  // calculate uvs and render
288
294
  const x = tileInfo.pos.x, y = tileInfo.pos.y;
289
295
  const w = tileInfo.size.x, h = tileInfo.size.y;
290
- drawImageColor(context, textureInfo.image, x, y, w, h, -.5, -.5, 1, 1, color, additiveColor);
296
+ drawImageColor(context, textureInfo.image, x, y, w, h, -.5, -.5, 1, 1, color, additiveColor, bleedScale);
291
297
  }
292
298
  else
293
299
  {
@@ -775,15 +781,16 @@ function combineCanvases()
775
781
  * @param {number} dHeight
776
782
  * @param {Color} color
777
783
  * @param {Color} [additiveColor]
784
+ * @param {number} [bleedScale] - How much to shrink the source, used to fix bleeding
778
785
  * @memberof Draw */
779
- function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight, color, additiveColor)
786
+ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight, color, additiveColor, bleedScale=0)
780
787
  {
781
788
  function isWhite(c) { return c.r >= 1 && c.g >= 1 && c.b >= 1; }
782
789
  function isBlack(c) { return c.r <= 0 && c.g <= 0 && c.b <= 0 && c.a <= 0; }
783
- const sx2 = tileFixBleedScale;
784
- const sy2 = tileFixBleedScale;
785
- const sWidth2 = sWidth - 2*tileFixBleedScale;
786
- const sHeight2 = sHeight - 2*tileFixBleedScale;
790
+ const sx2 = bleedScale;
791
+ const sy2 = bleedScale;
792
+ const sWidth2 = sWidth - 2*bleedScale;
793
+ const sHeight2 = sHeight - 2*bleedScale;
787
794
  if (!canvasColorTiles || (additiveColor ? isWhite(color.add(additiveColor)) && additiveColor.a <= 0 : isWhite(color)))
788
795
  {
789
796
  // white texture with no additive alpha, no need to tint
@@ -899,7 +906,7 @@ class FontImage
899
906
  * @param {Vector2} pos
900
907
  * @param {number} [scale=.25]
901
908
  * @param {boolean} [center]
902
- * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D}[context=drawContext]
909
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
903
910
  */
904
911
  drawText(text, pos, scale=1, center, context=drawContext)
905
912
  {
@@ -53,6 +53,7 @@ export
53
53
  cameraAngle,
54
54
  cameraScale,
55
55
  canvasColorTiles,
56
+ canvasClearColor,
56
57
  canvasMaxSize,
57
58
  canvasFixedSize,
58
59
  canvasPixelated,
@@ -94,6 +95,7 @@ export
94
95
  setCameraAngle,
95
96
  setCameraScale,
96
97
  setCanvasColorTiles,
98
+ setCanvasClearColor,
97
99
  setCanvasMaxSize,
98
100
  setCanvasFixedSize,
99
101
  setCanvasPixelated,
@@ -192,8 +194,6 @@ export
192
194
  MAGENTA,
193
195
 
194
196
  // Draw
195
- textureInfos,
196
- drawCount,
197
197
  tile,
198
198
  TileInfo,
199
199
  TextureInfo,
@@ -204,6 +204,8 @@ export
204
204
  overlayCanvas,
205
205
  overlayContext,
206
206
  mainCanvasSize,
207
+ textureInfos,
208
+ drawCount,
207
209
  screenToWorld,
208
210
  worldToScreen,
209
211
  drawTile,
@@ -216,6 +216,7 @@ function inputInit()
216
216
  document.addEventListener('wheel', onMouseWheel);
217
217
  document.addEventListener('contextmenu', onContextMenu);
218
218
  document.addEventListener('blur', onBlur);
219
+ document.addEventListener('mouseleave', onMouseLeave);
219
220
 
220
221
  // init touch input
221
222
  if (isTouchDevice && touchInputEnable)
@@ -274,6 +275,12 @@ function inputInit()
274
275
  function onMouseWheel(e) { mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY); }
275
276
  function onContextMenu(e) { e.preventDefault(); } // prevent right click menu
276
277
  function onBlur() { inputClear(); } // reset input when focus is lost
278
+ function onMouseLeave()
279
+ {
280
+ // set mouse position and delta when leaving canvas
281
+ mousePosScreen = vec2(Infinity);
282
+ mouseDeltaScreen = vec2(0);
283
+ }
277
284
  }
278
285
 
279
286
  // convert a mouse or touch event position to screen space
@@ -52,7 +52,7 @@ class EngineObject
52
52
  /** @property {Vector2} - World space position of the object */
53
53
  this.pos = pos.copy();
54
54
  /** @property {Vector2} - World space width and height of the object */
55
- this.size = size;
55
+ this.size = size.copy();
56
56
  /** @property {Vector2} - Size of object used for drawing, uses size if not set */
57
57
  this.drawSize = undefined;
58
58
  /** @property {TileInfo} - Tile info to render object (undefined is untextured) */
@@ -60,7 +60,7 @@ class EngineObject
60
60
  /** @property {number} - Angle to rotate the object */
61
61
  this.angle = angle;
62
62
  /** @property {Color} - Color to apply when rendered */
63
- this.color = color;
63
+ this.color = color.copy();
64
64
  /** @property {Color} - Additive color to apply when rendered */
65
65
  this.additiveColor = undefined;
66
66
  /** @property {boolean} - Should it flip along y axis when rendered */
@@ -184,7 +184,7 @@ class EngineObject
184
184
  for (const o of engineObjectsCollide)
185
185
  {
186
186
  // non solid objects don't collide with each other
187
- if (!this.isSolid && !o.isSolid || o.destroyed || o.parent || o === this)
187
+ if ((!this.isSolid && !o.isSolid) || o.destroyed || o.parent || o === this)
188
188
  continue;
189
189
 
190
190
  // check collision
@@ -223,7 +223,7 @@ class EngineObject
223
223
  {
224
224
  // push outside object collision
225
225
  this.pos.y = o.pos.y + (sizeBoth.y/2 + epsilon) * sign(oldPos.y - o.pos.y);
226
- if (o.groundObject && wasMovingDown || !o.mass)
226
+ if ((o.groundObject && wasMovingDown) || !o.mass)
227
227
  {
228
228
  // set ground object if landed on something
229
229
  if (wasMovingDown)
@@ -37,6 +37,11 @@ let cameraScale = 32;
37
37
  * @memberof Settings */
38
38
  let canvasColorTiles = true;
39
39
 
40
+ /** Color to clear the canvas to before render
41
+ * @type {Color}
42
+ * @memberof Draw */
43
+ let canvasClearColor = CLEAR_BLACK;
44
+
40
45
  /** The max size of the canvas, centered if window is larger
41
46
  * @type {Vector2}
42
47
  * @default Vector2(1920,1080)
@@ -315,6 +320,11 @@ function setCameraScale(scale) { cameraScale = scale; }
315
320
  * @memberof Settings */
316
321
  function setCanvasColorTiles(colorTiles) { canvasColorTiles = colorTiles; }
317
322
 
323
+ /** Set color to clear the canvas to before render
324
+ * @param {Color} color
325
+ * @memberof Settings */
326
+ function setCanvasClearColor(color) { canvasClearColor = color; }
327
+
318
328
  /** Set max size of the canvas
319
329
  * @param {Vector2} size
320
330
  * @memberof Settings */
@@ -380,7 +390,7 @@ function setGLEnable(enable)
380
390
  glCanvas.style.visibility = enable ? 'visible' : 'hidden';
381
391
  }
382
392
 
383
- /** Set how many sided polygons to use when drawing circles and elipses with WebGL
393
+ /** Set how many sided polygons to use when drawing circles and ellipses with WebGL
384
394
  * @param {number} sides
385
395
  * @memberof Settings */
386
396
  function setGLCircleSides(sides) { glCircleSides = sides; }
@@ -156,7 +156,7 @@ class TileLayerData
156
156
  /** @property {boolean} - If the tile should be mirrored along the x axis */
157
157
  this.mirror = mirror;
158
158
  /** @property {Color} - Color of the tile */
159
- this.color = color;
159
+ this.color = color.copy();
160
160
  }
161
161
 
162
162
  /** Set this tile to clear, it will not be rendered */
@@ -255,7 +255,7 @@ class CanvasLayer extends EngineObject
255
255
  * @param {TileInfo} [tileInfo]
256
256
  * @param {Color} [color=(1,1,1,1)]
257
257
  * @param {number} [angle=0]
258
- * @param {boolean} [mirror=0] */
258
+ * @param {boolean} [mirror=false] */
259
259
  drawTile(pos, size=vec2(1), tileInfo, color=new Color, angle, mirror)
260
260
  {
261
261
  this.drawCanvas2D(pos, size, angle, mirror, (context)=>
@@ -155,8 +155,11 @@ function nearestPowerOfTwo(value) { return 2**Math.ceil(Math.log2(value)); }
155
155
  * @memberof Utilities */
156
156
  function isOverlapping(posA, sizeA, posB, sizeB=vec2())
157
157
  {
158
- return abs(posA.x - posB.x)*2 < sizeA.x + sizeB.x
159
- && abs(posA.y - posB.y)*2 < sizeA.y + sizeB.y;
158
+ const dx = (posA.x - posB.x)*2;
159
+ const dy = (posA.y - posB.y)*2;
160
+ const sx = sizeA.x + sizeB.x;
161
+ const sy = sizeA.y + sizeB.y;
162
+ return dx >= -sx && dx < sx && dy >= -sy && dy < sy;
160
163
  }
161
164
 
162
165
  /** Returns true if a line segment is intersecting an axis aligned box
@@ -985,6 +988,10 @@ class Timer
985
988
  * @return {number} */
986
989
  getPercent() { return this.isSet()? 1-percent(this.time - time, 0, this.setTime) : 0; }
987
990
 
991
+ /** Get the time this timer was set to, returns 0 if not set
992
+ * @return {number} */
993
+ getSetTime() { return this.isSet() ? this.setTime : 0; }
994
+
988
995
  /** Returns this timer expressed as a string
989
996
  * @return {string} */
990
997
  toString() { if (debug) { return this.isSet() ? Math.abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ) : 'unset'; }}
@@ -260,7 +260,8 @@ function glSetTexture(texture, wrap=false)
260
260
  return;
261
261
 
262
262
  glFlush();
263
- glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture = texture);
263
+ glActiveTexture = texture;
264
+ glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture);
264
265
 
265
266
  // set wrap mode
266
267
  const wrapMode = wrap ? glContext.REPEAT : glContext.CLAMP_TO_EDGE;
@@ -310,6 +311,7 @@ function glCreateProgram(vsSource, fsSource)
310
311
  }
311
312
 
312
313
  /** Create WebGL texture from an image and init the texture settings
314
+ * Restores the active texture when done
313
315
  * @param {HTMLImageElement|HTMLCanvasElement|OffscreenCanvas} [image]
314
316
  * @return {WebGLTexture}
315
317
  * @memberof WebGL */
@@ -319,30 +321,29 @@ function glCreateTexture(image)
319
321
 
320
322
  // build the texture
321
323
  const texture = glContext.createTexture();
322
- glContext.bindTexture(glContext.TEXTURE_2D, texture);
324
+ let mipMap = false;
323
325
  if (image && image.width)
324
326
  {
325
327
  glSetTextureData(texture, image);
326
- if (!tilesPixelated && isPowerOfTwo(image.width) && isPowerOfTwo(image.height))
327
- {
328
- // use mipmap filtering
329
- glContext.generateMipmap(glContext.TEXTURE_2D);
330
- glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, glContext.LINEAR_MIPMAP_LINEAR);
331
- glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MAG_FILTER, glContext.LINEAR);
332
- return texture;
333
- }
328
+ glContext.bindTexture(glContext.TEXTURE_2D, texture);
329
+ mipMap = !tilesPixelated && isPowerOfTwo(image.width) && isPowerOfTwo(image.height);
334
330
  }
335
331
  else
336
332
  {
337
333
  // create a white texture
338
334
  const whitePixel = new Uint8Array([255, 255, 255, 255]);
335
+ glContext.bindTexture(glContext.TEXTURE_2D, texture);
339
336
  glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, 1, 1, 0, glContext.RGBA, glContext.UNSIGNED_BYTE, whitePixel);
340
337
  }
341
338
 
342
339
  // set texture filtering
343
- const filter = tilesPixelated ? glContext.NEAREST : glContext.LINEAR;
344
- glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, filter);
345
- glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MAG_FILTER, filter);
340
+ const magFilter = tilesPixelated ? glContext.NEAREST : glContext.LINEAR;
341
+ const minFilter = mipMap ? glContext.LINEAR_MIPMAP_LINEAR : magFilter;
342
+ glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MAG_FILTER, magFilter);
343
+ glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, minFilter);
344
+ if (mipMap)
345
+ glContext.generateMipmap(glContext.TEXTURE_2D);
346
+ glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture); // rebind active texture
346
347
  return texture;
347
348
  }
348
349
 
@@ -355,7 +356,7 @@ function glDeleteTexture(texture)
355
356
  glContext.deleteTexture(texture);
356
357
  }
357
358
 
358
- /** Set WebGL texture data from an image
359
+ /** Set WebGL texture data from an image, restores the active texture when done
359
360
  * @param {WebGLTexture} texture
360
361
  * @param {HTMLImageElement|HTMLCanvasElement|OffscreenCanvas} image
361
362
  * @memberof WebGL */
@@ -367,6 +368,7 @@ function glSetTextureData(texture, image)
367
368
  ASSERT(!!image && image.width > 0, 'Invalid image data.');
368
369
  glContext.bindTexture(glContext.TEXTURE_2D, texture);
369
370
  glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, image);
371
+ glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture); // rebind active texture
370
372
  }
371
373
 
372
374
  /** Draw all sprites and clear out the buffer, called automatically by the system whenever necessary
@@ -512,7 +514,7 @@ function glDrawPoints(points, rgba)
512
514
 
513
515
  // setup triangle strip with degenerate verts at start and end
514
516
  let offset = glBatchCount * gl_INDICES_PER_POLY_VERTEX;
515
- for(let i = vertCount; i--;)
517
+ for (let i = vertCount; i--;)
516
518
  {
517
519
  const j = clamp(i-1, 0, vertCount-3);
518
520
  const point = points[j];
@@ -540,7 +542,7 @@ function glDrawColoredPoints(points, pointColors)
540
542
 
541
543
  // setup triangle strip with degenerate verts at start and end
542
544
  let offset = glBatchCount * gl_INDICES_PER_POLY_VERTEX;
543
- for(let i = vertCount; i--;)
545
+ for (let i = vertCount; i--;)
544
546
  {
545
547
  const j = clamp(i-1, 0, vertCount-3);
546
548
  const point = points[j];
@@ -677,7 +679,7 @@ function glPolyStrip(points)
677
679
  while (indices.length > 3 && attempts++ < maxAttempts)
678
680
  {
679
681
  let foundEar = false;
680
- for (let i = indices.length; --i;)
682
+ for (let i = 0; i < indices.length; i++)
681
683
  {
682
684
  const i0 = indices[(i + indices.length - 1) % indices.length];
683
685
  const i1 = indices[i];
@@ -714,7 +716,7 @@ function glPolyStrip(points)
714
716
  if (!foundEar)
715
717
  {
716
718
  let worstIndex = -1, worstValue = Infinity;
717
- for (let i = indices.length; --i;)
719
+ for (let i = 0; i < indices.length; i++)
718
720
  {
719
721
  const i0 = indices[(i + indices.length - 1) % indices.length];
720
722
  const i1 = indices[i];