littlejsengine 1.9.11 → 1.10.4

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 (76) hide show
  1. package/README.md +9 -13
  2. package/dist/littlejs.d.ts +129 -10
  3. package/dist/littlejs.esm.js +220 -75
  4. package/dist/littlejs.esm.min.js +1 -1
  5. package/dist/littlejs.js +188 -73
  6. package/dist/littlejs.min.js +1 -1
  7. package/dist/littlejs.release.js +187 -72
  8. package/examples/box2d/game.js +4 -1
  9. package/examples/box2d/index.html +7 -6
  10. package/examples/breakout/game.js +1 -1
  11. package/examples/breakout/index.html +5 -4
  12. package/examples/breakoutTutorial/README.md +9 -7
  13. package/examples/breakoutTutorial/game.js +6 -6
  14. package/examples/breakoutTutorial/index.html +4 -3
  15. package/examples/electron/game.js +1 -1
  16. package/examples/electron/index.html +4 -3
  17. package/examples/empty/game.js +1 -1
  18. package/examples/empty/index.html +2 -1
  19. package/examples/htmlMenu/game.js +67 -0
  20. package/examples/htmlMenu/index.html +56 -0
  21. package/examples/htmlMenu/tiles.png +0 -0
  22. package/examples/index.html +33 -0
  23. package/examples/js13k/build/index.html +1 -0
  24. package/examples/js13k/build/index.js +1 -0
  25. package/examples/js13k/build/tiles.png +0 -0
  26. package/examples/js13k/game - Copy.zip +0 -0
  27. package/examples/js13k/game.js +1 -1
  28. package/examples/js13k/game.zip +0 -0
  29. package/examples/js13k/index.html +17 -14
  30. package/examples/module/game.js +1 -1
  31. package/examples/module/index.html +3 -2
  32. package/examples/particles/index.html +4 -3
  33. package/examples/platformer/game.js +7 -5
  34. package/examples/platformer/gameCharacter.js +12 -11
  35. package/examples/platformer/gameEffects.js +3 -4
  36. package/examples/platformer/gameLevel.js +18 -34
  37. package/examples/platformer/gameObjects.js +9 -15
  38. package/examples/platformer/index.html +10 -9
  39. package/examples/puzzle/game.js +1 -1
  40. package/examples/puzzle/index.html +4 -3
  41. package/examples/starter/build/index.html +2 -0
  42. package/examples/starter/build/index.js +1 -0
  43. package/examples/starter/build/tiles.png +0 -0
  44. package/examples/starter/game.js +4 -4
  45. package/examples/starter/game.zip +0 -0
  46. package/examples/starter/index.html +15 -14
  47. package/examples/stress/index.html +3 -2
  48. package/examples/typescript/build/build/littlejs.esm.js +4327 -0
  49. package/examples/typescript/build/dist/littlejs.esm.js +4425 -0
  50. package/examples/typescript/build/examples/typescript/build.js +24 -0
  51. package/examples/typescript/build/examples/typescript/game.js +100 -0
  52. package/examples/typescript/build/examples/typescript/test/build/littlejs.esm.js +3934 -0
  53. package/examples/typescript/build/examples/typescript/test/examples/typescript/build.js +86 -0
  54. package/examples/typescript/build/examples/typescript/test/examples/typescript/game.js +92 -0
  55. package/examples/typescript/game.js +1 -1
  56. package/examples/typescript/game.ts +1 -1
  57. package/examples/typescript/index.html +3 -2
  58. package/examples/uiSystem/game.js +134 -0
  59. package/examples/uiSystem/index.html +25 -0
  60. package/examples/uiSystem/tiles.png +0 -0
  61. package/jsconfig.json +11 -0
  62. package/package.json +1 -1
  63. package/plugins/box2d.js +2 -1
  64. package/plugins/postProcess.js +14 -8
  65. package/plugins/uiSystem.js +243 -0
  66. package/src/engine.js +42 -22
  67. package/src/engineAudio.js +10 -13
  68. package/src/engineDebug.js +1 -1
  69. package/src/engineDraw.js +102 -27
  70. package/src/engineExport.js +32 -2
  71. package/src/engineInput.js +1 -3
  72. package/src/engineObject.js +1 -0
  73. package/src/engineSettings.js +1 -1
  74. package/src/engineTileLayer.js +2 -1
  75. package/src/engineUtilities.js +2 -1
  76. package/src/engineWebGL.js +26 -4
@@ -521,6 +521,7 @@ class Vector2
521
521
  * @param {Number} [length] */
522
522
  setDirection(direction, length=1)
523
523
  {
524
+ direction = mod(direction, 4);
524
525
  ASSERT(direction==0 || direction==1 || direction==2 || direction==3);
525
526
  return vec2(direction%2 ? direction-1 ? -length : length : 0,
526
527
  direction%2 ? 0 : direction ? -length : length);
@@ -773,7 +774,7 @@ class Color
773
774
  * @return {String} */
774
775
  toString(useAlpha = true)
775
776
  {
776
- const toHex = (c)=> ((c=c*255|0)<16 ? '0' : '') + c.toString(16);
777
+ const toHex = (c)=> ((c=clamp(c)*255|0)<16 ? '0' : '') + c.toString(16);
777
778
  return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (useAlpha ? toHex(this.a) : '');
778
779
  }
779
780
 
@@ -1003,7 +1004,7 @@ let tileSizeDefault = vec2(16);
1003
1004
  * @type {Number}
1004
1005
  * @default
1005
1006
  * @memberof Settings */
1006
- let tileFixBleedScale = .5;
1007
+ let tileFixBleedScale = 0;
1007
1008
 
1008
1009
  ///////////////////////////////////////////////////////////////////////////////
1009
1010
  // Object settings
@@ -1703,6 +1704,7 @@ class EngineObject
1703
1704
  this.pos.x = oldPos.x;
1704
1705
  this.velocity.x *= -this.elasticity;
1705
1706
  }
1707
+ debugOverlay && debugPhysics && debugRect(this.pos, this.size, '#f00');
1706
1708
  }
1707
1709
  }
1708
1710
  }
@@ -1905,12 +1907,13 @@ let drawCount;
1905
1907
  ///////////////////////////////////////////////////////////////////////////////
1906
1908
 
1907
1909
  /**
1908
- * Create a tile info object
1910
+ * Create a tile info object using a grid based system
1909
1911
  * - This can take vecs or floats for easier use and conversion
1910
1912
  * - If an index is passed in, the tile size and index will determine the position
1911
- * @param {(Number|Vector2)} [pos=(0,0)] - Top left corner of tile in pixels or index
1913
+ * @param {(Number|Vector2)} [pos=0] - Index of tile in sheet
1912
1914
  * @param {(Number|Vector2)} [size=tileSizeDefault] - Size of tile in pixels
1913
1915
  * @param {Number} [textureIndex] - Texture index to use
1916
+ * @param {Number} [padding] - How many pixels padding around tiles
1914
1917
  * @return {TileInfo}
1915
1918
  * @example
1916
1919
  * tile(2) // a tile at index 2 using the default tile size of 16
@@ -1919,7 +1922,7 @@ let drawCount;
1919
1922
  * tile(vec2(4,8), vec2(30,10)) // a tile at pixel location (4,8) with a size of (30,10)
1920
1923
  * @memberof Draw
1921
1924
  */
1922
- function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0)
1925
+ function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0, padding=0)
1923
1926
  {
1924
1927
  if (headlessMode)
1925
1928
  return new TileInfo;
@@ -1931,17 +1934,17 @@ function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0)
1931
1934
  size = vec2(size);
1932
1935
  }
1933
1936
 
1934
- // if pos is a number, use it as a tile index
1937
+ // use pos as a tile index
1938
+ const textureInfo = textureInfos[textureIndex];
1939
+ ASSERT(textureInfo, 'Texture not loaded');
1940
+ const sizePadded = size.add(vec2(padding*2));
1941
+ const cols = textureInfo.size.x / sizePadded.x |0;
1935
1942
  if (typeof pos === 'number')
1936
- {
1937
- const textureInfo = textureInfos[textureIndex];
1938
- ASSERT(textureInfo, 'Texture not loaded');
1939
- const cols = textureInfo.size.x / size.x |0;
1940
- pos = vec2((pos%cols)*size.x, (pos/cols|0)*size.y);
1941
- }
1943
+ pos = vec2(pos%cols, pos/cols|0);
1944
+ pos = vec2(pos.x*sizePadded.x+padding, pos.y*sizePadded.y+padding);
1942
1945
 
1943
1946
  // return a tile info object
1944
- return new TileInfo(pos, size, textureIndex);
1947
+ return new TileInfo(pos, size, textureIndex, padding);
1945
1948
  }
1946
1949
 
1947
1950
  /**
@@ -1953,8 +1956,9 @@ class TileInfo
1953
1956
  * @param {Vector2} [pos=(0,0)] - Top left corner of tile in pixels
1954
1957
  * @param {Vector2} [size=tileSizeDefault] - Size of tile in pixels
1955
1958
  * @param {Number} [textureIndex] - Texture index to use
1959
+ * @param {Number} [padding] - How many pixels padding around tiles
1956
1960
  */
1957
- constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0)
1961
+ constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0, padding=0)
1958
1962
  {
1959
1963
  /** @property {Vector2} - Top left corner of tile in pixels */
1960
1964
  this.pos = pos.copy();
@@ -1962,6 +1966,8 @@ class TileInfo
1962
1966
  this.size = size.copy();
1963
1967
  /** @property {Number} - Texture index to use */
1964
1968
  this.textureIndex = textureIndex;
1969
+ /** @property {Number} - How many pixels padding around tiles */
1970
+ this.padding = padding;
1965
1971
  }
1966
1972
 
1967
1973
  /** Returns a copy of this tile offset by a vector
@@ -1978,7 +1984,7 @@ class TileInfo
1978
1984
  frame(frame)
1979
1985
  {
1980
1986
  ASSERT(typeof frame == 'number');
1981
- return this.offset(vec2(frame*this.size.x, 0));
1987
+ return this.offset(vec2(frame*(this.size.x+this.padding*2), 0));
1982
1988
  }
1983
1989
 
1984
1990
  /** Returns the texture info for this tile
@@ -2003,8 +2009,6 @@ class TextureInfo
2003
2009
  this.size = vec2(image.width, image.height);
2004
2010
  /** @property {WebGLTexture} - webgl texture */
2005
2011
  this.glTexture = glEnable && glCreateTexture(image);
2006
- /** @property {Vector2} - size to adjust tile to fix bleeding */
2007
- this.fixBleedSize = vec2(tileFixBleedScale).divide(this.size);
2008
2012
  }
2009
2013
  }
2010
2014
 
@@ -2073,11 +2077,12 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
2073
2077
  if (textureInfo)
2074
2078
  {
2075
2079
  // calculate uvs and render
2076
- const x = tileInfo.pos.x / textureInfo.size.x;
2077
- const y = tileInfo.pos.y / textureInfo.size.y;
2078
- const w = tileInfo.size.x / textureInfo.size.x;
2079
- const h = tileInfo.size.y / textureInfo.size.y;
2080
- const tileImageFixBleed = textureInfo.fixBleedSize;
2080
+ const sizeInverse = vec2(1).divide(textureInfo.size);
2081
+ const x = tileInfo.pos.x * sizeInverse.x;
2082
+ const y = tileInfo.pos.y * sizeInverse.y;
2083
+ const w = tileInfo.size.x * sizeInverse.x;
2084
+ const h = tileInfo.size.y * sizeInverse.y;
2085
+ const tileImageFixBleed = sizeInverse.scale(tileFixBleedScale);
2081
2086
  glSetTexture(textureInfo.glTexture);
2082
2087
  glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
2083
2088
  x + tileImageFixBleed.x, y + tileImageFixBleed.y,
@@ -2094,6 +2099,7 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
2094
2099
  {
2095
2100
  // normal canvas 2D rendering method (slower)
2096
2101
  showWatermark && ++drawCount;
2102
+ size = vec2(size.x, -size.y); // fix upside down sprites
2097
2103
  drawCanvas2D(pos, size, angle, mirror, (context)=>
2098
2104
  {
2099
2105
  if (textureInfo)
@@ -2147,6 +2153,74 @@ function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, contex
2147
2153
  drawRect(posA.add(halfDelta), size, color, halfDelta.angle(), useWebGL, screenSpace, context);
2148
2154
  }
2149
2155
 
2156
+ /** Draw colored polygon using passed in points
2157
+ * @param {Array} points - Array of Vector2 points
2158
+ * @param {Color} [color=(1,1,1,1)]
2159
+ * @param {Number} [lineWidth=0]
2160
+ * @param {Color} [lineColor=(0,0,0,1)]
2161
+ * @param {Boolean} [screenSpace=false]
2162
+ * @param {CanvasRenderingContext2D} [context=mainContext]
2163
+ * @memberof Draw */
2164
+ function drawPoly(points, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=mainContext)
2165
+ {
2166
+ context.fillStyle = color.toString();
2167
+ context.beginPath();
2168
+ for (const point of screenSpace ? points : points.map(worldToScreen))
2169
+ context.lineTo(point.x, point.y);
2170
+ context.closePath();
2171
+ context.fill();
2172
+ if (lineWidth)
2173
+ {
2174
+ context.strokeStyle = lineColor.toString();
2175
+ context.lineWidth = screenSpace ? lineWidth : lineWidth*cameraScale;
2176
+ context.stroke();
2177
+ }
2178
+ }
2179
+
2180
+ /** Draw colored ellipse using passed in point
2181
+ * @param {Vector2} pos
2182
+ * @param {Number} [width=1]
2183
+ * @param {Number} [height=1]
2184
+ * @param {Number} [angle=0]
2185
+ * @param {Color} [color=(1,1,1,1)]
2186
+ * @param {Number} [lineWidth=0]
2187
+ * @param {Color} [lineColor=(0,0,0,1)]
2188
+ * @param {Boolean} [screenSpace=false]
2189
+ * @param {CanvasRenderingContext2D} [context=mainContext]
2190
+ * @memberof Draw */
2191
+ function drawEllipse(pos, width=1, height=1, angle=0, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=mainContext)
2192
+ {
2193
+ if (!screenSpace)
2194
+ {
2195
+ pos = worldToScreen(pos);
2196
+ width *= cameraScale;
2197
+ height *= cameraScale;
2198
+ lineWidth *= cameraScale;
2199
+ }
2200
+ context.fillStyle = color.toString();
2201
+ context.beginPath();
2202
+ context.ellipse(pos.x, pos.y, width, height, angle, 0, 9);
2203
+ context.fill();
2204
+ if (lineWidth)
2205
+ {
2206
+ context.strokeStyle = lineColor.toString();
2207
+ context.lineWidth = lineWidth;
2208
+ context.stroke();
2209
+ }
2210
+ }
2211
+
2212
+ /** Draw colored circle using passed in point
2213
+ * @param {Vector2} pos
2214
+ * @param {Number} [radius=1]
2215
+ * @param {Color} [color=(1,1,1,1)]
2216
+ * @param {Number} [lineWidth=0]
2217
+ * @param {Color} [lineColor=(0,0,0,1)]
2218
+ * @param {Boolean} [screenSpace=false]
2219
+ * @param {CanvasRenderingContext2D} [context=mainContext]
2220
+ * @memberof Draw */
2221
+ function drawCircle(pos, radius=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=mainContext)
2222
+ { drawEllipse(pos, radius, radius, 0, color, lineWidth, lineColor, screenSpace, context); }
2223
+
2150
2224
  /** Draw directly to a 2d canvas context in world space
2151
2225
  * @param {Vector2} pos
2152
2226
  * @param {Vector2} size
@@ -2201,10 +2275,11 @@ function setBlendMode(additive, useWebGL=glEnable, context)
2201
2275
  * @param {CanvasTextAlign} [textAlign='center']
2202
2276
  * @param {String} [font=fontDefault]
2203
2277
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
2278
+ * @param {Number} [maxWidth]
2204
2279
  * @memberof Draw */
2205
- function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, context)
2280
+ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, context, maxWidth)
2206
2281
  {
2207
- drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, context);
2282
+ drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, context, maxWidth);
2208
2283
  }
2209
2284
 
2210
2285
  /** Draw text on overlay canvas in screen space
@@ -2218,8 +2293,9 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
2218
2293
  * @param {CanvasTextAlign} [textAlign]
2219
2294
  * @param {String} [font=fontDefault]
2220
2295
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
2296
+ * @param {Number} [maxWidth]
2221
2297
  * @memberof Draw */
2222
- function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, context=overlayContext)
2298
+ function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, context=overlayContext, maxWidth=undefined)
2223
2299
  {
2224
2300
  context.fillStyle = color.toString();
2225
2301
  context.lineWidth = lineWidth;
@@ -2232,8 +2308,8 @@ function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineCol
2232
2308
  pos = pos.copy();
2233
2309
  (text+'').split('\n').forEach(line=>
2234
2310
  {
2235
- lineWidth && context.strokeText(line, pos.x, pos.y);
2236
- context.fillText(line, pos.x, pos.y);
2311
+ lineWidth && context.strokeText(line, pos.x, pos.y, maxWidth);
2312
+ context.fillText(line, pos.x, pos.y, maxWidth);
2237
2313
  pos.y += size;
2238
2314
  });
2239
2315
  }
@@ -2336,13 +2412,14 @@ function isFullscreen() { return !!document.fullscreenElement; }
2336
2412
  * @memberof Draw */
2337
2413
  function toggleFullscreen()
2338
2414
  {
2415
+ const rootElement = mainCanvas.parentElement;
2339
2416
  if (isFullscreen())
2340
2417
  {
2341
2418
  if (document.exitFullscreen)
2342
2419
  document.exitFullscreen();
2343
2420
  }
2344
- else if (document.body.requestFullscreen)
2345
- document.body.requestFullscreen();
2421
+ else if (rootElement.requestFullscreen)
2422
+ rootElement.requestFullscreen();
2346
2423
  }
2347
2424
  /**
2348
2425
  * LittleJS Input System
@@ -2513,7 +2590,6 @@ function inputInit()
2513
2590
 
2514
2591
  onkeydown = (e)=>
2515
2592
  {
2516
- if (debug && e.target != document.body) return;
2517
2593
  if (!e.repeat)
2518
2594
  {
2519
2595
  isUsingGamepad = false;
@@ -2526,7 +2602,6 @@ function inputInit()
2526
2602
 
2527
2603
  onkeyup = (e)=>
2528
2604
  {
2529
- if (debug && e.target != document.body) return;
2530
2605
  inputData[0][e.code] = 4;
2531
2606
  if (inputWASDEmulateDirection)
2532
2607
  inputData[0][remapKey(e.code)] = 4;
@@ -2728,7 +2803,7 @@ function touchInputInit()
2728
2803
  // set event pos and pass it along
2729
2804
  const p = vec2(e.touches[0].clientX, e.touches[0].clientY);
2730
2805
  mousePosScreen = mouseToScreen(p);
2731
- wasTouching ? isUsingGamepad = false : inputData[0][button] = 3;
2806
+ wasTouching ? isUsingGamepad = touchGamepadEnable : inputData[0][button] = 3;
2732
2807
  }
2733
2808
  else if (wasTouching)
2734
2809
  inputData[0][button] = inputData[0][button] & 2 | 4;
@@ -3178,17 +3253,6 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
3178
3253
  {
3179
3254
  if (!soundEnable || headlessMode) return;
3180
3255
 
3181
- // prevent sounds from building up if they can't be played
3182
- if (audioContext.state != 'running')
3183
- {
3184
- // fix stalled audio
3185
- audioContext.resume().then(()=>
3186
- playSamples(sampleChannels, volume, rate, pan, loop, sampleRate, gainNode));
3187
-
3188
- // prevent suspended sounds from building up
3189
- return;
3190
- }
3191
-
3192
3256
  // create buffer and source
3193
3257
  const channelCount = sampleChannels.length;
3194
3258
  const sampleLength = sampleChannels[0].length;
@@ -3210,8 +3274,16 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
3210
3274
  const pannerNode = new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)});
3211
3275
  source.connect(pannerNode).connect(gainNode);
3212
3276
 
3213
- // play and return sound
3214
- source.start();
3277
+ // play the sound
3278
+ if (audioContext.state != 'running')
3279
+ {
3280
+ // fix stalled audio and play
3281
+ audioContext.resume().then(()=>source.start());
3282
+ }
3283
+ else
3284
+ source.start();
3285
+
3286
+ // return sound
3215
3287
  return source;
3216
3288
  }
3217
3289
 
@@ -3527,6 +3599,7 @@ function tileCollisionTest(pos, size=vec2(), object)
3527
3599
  if (tileData && (!object || object.collideWithTile(tileData, vec2(x, y))))
3528
3600
  return true;
3529
3601
  }
3602
+ return false;
3530
3603
  }
3531
3604
 
3532
3605
  /** Return the center of first tile hit (does not return the exact intersection)
@@ -3550,7 +3623,7 @@ function tileCollisionRaycast(posStart, posEnd, object)
3550
3623
  let xi = unit.x * (delta.x < 0 ? posStart.x - pos.x : pos.x - posStart.x + 1);
3551
3624
  let yi = unit.y * (delta.y < 0 ? posStart.y - pos.y : pos.y - posStart.y + 1);
3552
3625
 
3553
- while (1)
3626
+ while (true)
3554
3627
  {
3555
3628
  // check for tile collision
3556
3629
  const tileData = getTileCollisionData(pos);
@@ -4359,6 +4432,11 @@ let glCanvas;
4359
4432
  * @memberof WebGL */
4360
4433
  let glContext;
4361
4434
 
4435
+ /** Shoule webgl be setup with antialiasing, must be set before calling engineInit
4436
+ * @type {Boolean}
4437
+ * @memberof WebGL */
4438
+ let glAntialias = true;
4439
+
4362
4440
  // WebGL internal variables not exposed to documentation
4363
4441
  let glShader, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glInstanceCount, glAdditive, glBatchAdditive;
4364
4442
 
@@ -4371,10 +4449,11 @@ function glInit()
4371
4449
 
4372
4450
  // create the canvas and textures
4373
4451
  glCanvas = document.createElement('canvas');
4374
- glContext = glCanvas.getContext('webgl2');
4452
+ glContext = glCanvas.getContext('webgl2', {antialias:glAntialias});
4375
4453
 
4376
4454
  // some browsers are much faster without copying the gl buffer so we just overlay it instead
4377
- glOverlay && document.body.appendChild(glCanvas);
4455
+ const rootElement = mainCanvas.parentElement;
4456
+ glOverlay && rootElement.appendChild(glCanvas);
4378
4457
 
4379
4458
  // setup vertex and fragment shaders
4380
4459
  glShader = glCreateProgram(
@@ -4429,7 +4508,8 @@ function glPreRender()
4429
4508
  // set up the shader
4430
4509
  glContext.useProgram(glShader);
4431
4510
  glContext.activeTexture(gl_TEXTURE0);
4432
- glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = textureInfos[0].glTexture);
4511
+ if (textureInfos[0])
4512
+ glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = textureInfos[0].glTexture);
4433
4513
 
4434
4514
  // set vertex attributes
4435
4515
  let offset = glAdditive = glBatchAdditive = 0;
@@ -4527,8 +4607,14 @@ function glCreateTexture(image)
4527
4607
  // build the texture
4528
4608
  const texture = glContext.createTexture();
4529
4609
  glContext.bindTexture(gl_TEXTURE_2D, texture);
4530
- if (image)
4610
+ if (image && image.width)
4531
4611
  glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
4612
+ else
4613
+ {
4614
+ // create a white texture
4615
+ const whitePixel = new Uint8Array([255, 255, 255, 255]);
4616
+ glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, 1, 1, 0, gl_RGBA, gl_UNSIGNED_BYTE, whitePixel);
4617
+ }
4532
4618
 
4533
4619
  // use point filtering for pixelated rendering
4534
4620
  const filter = canvasPixelated ? gl_NEAREST : gl_LINEAR;
@@ -4571,6 +4657,15 @@ function glCopyToContext(context, forceDraw=false)
4571
4657
  context.drawImage(glCanvas, 0, 0);
4572
4658
  }
4573
4659
 
4660
+ /** Set antialiasing for webgl canvas
4661
+ * @param {Boolean} [antialias]
4662
+ * @memberof WebGL */
4663
+ function glSetAntialias(antialias=true)
4664
+ {
4665
+ ASSERT(!glCanvas, 'must be called before engineInit');
4666
+ glAntialias = antialias;
4667
+ }
4668
+
4574
4669
  /** Add a sprite to the gl draw list, used by all gl draw functions
4575
4670
  * @param {Number} x
4576
4671
  * @param {Number} y
@@ -4671,7 +4766,7 @@ const engineName = 'LittleJS';
4671
4766
  * @type {String}
4672
4767
  * @default
4673
4768
  * @memberof Engine */
4674
- const engineVersion = '1.9.11';
4769
+ const engineVersion = '1.10.4';
4675
4770
 
4676
4771
  /** Frames per second to update
4677
4772
  * @type {Number}
@@ -4715,7 +4810,6 @@ let timeReal = 0;
4715
4810
  * @default false
4716
4811
  * @memberof Engine */
4717
4812
  let paused = false;
4718
-
4719
4813
  /** Set if game is paused
4720
4814
  * @param {Boolean} isPaused
4721
4815
  * @memberof Engine */
@@ -4748,9 +4842,10 @@ function engineAddPlugin(updateFunction, renderFunction)
4748
4842
  * @param {Function} gameUpdatePost - Called after physics and objects are updated, setup camera and prepare for render
4749
4843
  * @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects
4750
4844
  * @param {Function} gameRenderPost - Called after objects are rendered, draw effects or hud that appear above all objects
4751
- * @param {Array} [imageSources=['tiles.png']] - Image to load
4845
+ * @param {Array} [imageSources=[]] - Image to load
4846
+ * @param {HTMLElement} [rootElement] - Root element to attach to, the document body by default
4752
4847
  * @memberof Engine */
4753
- function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=['tiles.png'])
4848
+ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
4754
4849
  {
4755
4850
  ASSERT(Array.isArray(imageSources), 'pass in images as array');
4756
4851
 
@@ -4792,6 +4887,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4792
4887
  for (const o of engineObjects)
4793
4888
  o.parent || o.updateTransforms();
4794
4889
  inputUpdate();
4890
+ pluginUpdateList.forEach(f=>f());
4795
4891
  debugUpdate();
4796
4892
  gameUpdatePost();
4797
4893
  inputUpdatePost();
@@ -4907,16 +5003,21 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4907
5003
  }
4908
5004
 
4909
5005
  // setup html
4910
- const styleBody =
5006
+ const styleRoot =
4911
5007
  'margin:0;overflow:hidden;' + // fill the window
5008
+ 'width:100vw;height:100vh;' + // fill the window
5009
+ 'display:flex;' + // use flexbox
5010
+ 'align-items:center;' + // horizontal center
5011
+ (canvasPixelated ? 'image-rendering:pixelated;' : '') + // pixel art
5012
+ 'justify-content:center;' + // vertical center
4912
5013
  'background:#000;' + // set background color
4913
5014
  'user-select:none;' + // prevent hold to select
4914
5015
  '-webkit-user-select:none;' + // compatibility for ios
4915
5016
  (!touchInputEnable ? '' : // no touch css setttings
4916
5017
  'touch-action:none;' + // prevent mobile pinch to resize
4917
5018
  '-webkit-touch-callout:none');// compatibility for ios
4918
- document.body.style.cssText = styleBody;
4919
- document.body.appendChild(mainCanvas = document.createElement('canvas'));
5019
+ rootElement.style.cssText = styleRoot;
5020
+ rootElement.appendChild(mainCanvas = document.createElement('canvas'));
4920
5021
  mainContext = mainCanvas.getContext('2d');
4921
5022
 
4922
5023
  // init stuff and start engine
@@ -4926,13 +5027,14 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4926
5027
  glInit();
4927
5028
 
4928
5029
  // create overlay canvas for hud to appear above gl canvas
4929
- document.body.appendChild(overlayCanvas = document.createElement('canvas'));
5030
+ rootElement.appendChild(overlayCanvas = document.createElement('canvas'));
4930
5031
  overlayContext = overlayCanvas.getContext('2d');
4931
5032
 
4932
5033
  // set canvas style
4933
- const styleCanvas = 'position:absolute;' + // position
4934
- 'top:50%;left:50%;transform:translate(-50%,-50%)'; // center
4935
- (glCanvas||mainCanvas).style.cssText = mainCanvas.style.cssText = overlayCanvas.style.cssText = styleCanvas;
5034
+ const styleCanvas = 'position:absolute'; // allow canvases to overlap
5035
+ mainCanvas.style.cssText = overlayCanvas.style.cssText = styleCanvas;
5036
+ if (glCanvas)
5037
+ glCanvas.style.cssText = styleCanvas;
4936
5038
  updateCanvas();
4937
5039
 
4938
5040
  // create promises for loading images
@@ -4949,19 +5051,32 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4949
5051
  })
4950
5052
  );
4951
5053
 
4952
- // draw splash screen
4953
- showSplashScreen && promises.push(new Promise(resolve =>
5054
+ if (!imageSources.length)
4954
5055
  {
4955
- let t = 0;
4956
- console.log(`${engineName} Engine v${engineVersion}`);
4957
- updateSplash();
4958
- function updateSplash()
5056
+ // no images to load
5057
+ promises.push(new Promise(resolve =>
4959
5058
  {
4960
- clearInput();
4961
- drawEngineSplashScreen(t+=.01);
4962
- t>1 ? resolve() : setTimeout(updateSplash, 16);
4963
- }
4964
- }));
5059
+ textureInfos[0] = new TextureInfo(new Image);
5060
+ resolve();
5061
+ }));
5062
+ }
5063
+
5064
+ if (showSplashScreen)
5065
+ {
5066
+ // draw splash screen
5067
+ promises.push(new Promise(resolve =>
5068
+ {
5069
+ let t = 0;
5070
+ console.log(`${engineName} Engine v${engineVersion}`);
5071
+ updateSplash();
5072
+ function updateSplash()
5073
+ {
5074
+ clearInput();
5075
+ drawEngineSplashScreen(t+=.01);
5076
+ t>1 ? resolve() : setTimeout(updateSplash, 16);
5077
+ }
5078
+ }));
5079
+ }
4965
5080
 
4966
5081
  // load all of the images
4967
5082
  Promise.all(promises).then(startEngine);
@@ -15,6 +15,9 @@
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
+
18
21
  // game variables
19
22
  const maxScenes = 11;
20
23
  let scene = 0;
@@ -153,4 +156,4 @@ function gameRenderPost()
153
156
  ///////////////////////////////////////////////////////////////////////////////
154
157
  // Startup LittleJS Engine with Box2D
155
158
 
156
- box2dEngineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost);
159
+ box2dEngineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, ['tiles.png']);
@@ -1,13 +1,14 @@
1
1
  <!DOCTYPE html><head>
2
- <title>Little JS Starter Project</title>
2
+ <title>LittleJS Box2D Example</title>
3
+ <meta charset=utf-8>
3
4
  <meta name=apple-mobile-web-app-capable content=yes>
4
5
  <meta name=mobile-web-app-capable content=yes>
5
6
  <link rel=icon type=image/png href=../favicon.png>
6
7
  </head><body>
7
8
 
8
9
  <script src=../../dist/littlejs.js></script>
9
- <script src=../../plugins/Box2D_v2.3.1_min.wasm.js?198></script>
10
- <script src=../../plugins/box2d.js?198></script>
11
- <script src=scenes.js?198></script>
12
- <script src=gameObjects.js?198></script>
13
- <script src=game.js?198></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>
@@ -135,4 +135,4 @@ function setupPostProcess()
135
135
 
136
136
  ///////////////////////////////////////////////////////////////////////////////
137
137
  // Startup LittleJS Engine
138
- engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost);
138
+ engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, ['tiles.png']);
@@ -1,11 +1,12 @@
1
1
  <!DOCTYPE html><head>
2
- <title>Little JS Breakout Game</title>
2
+ <title>LittleJS Breakout Game</title>
3
+ <meta charset=utf-8>
3
4
  <meta name=apple-mobile-web-app-capable content=yes>
4
5
  <meta name=mobile-web-app-capable content=yes>
5
6
  <link rel=icon type=image/png href=../favicon.png>
6
7
  </head><body>
7
8
 
8
- <script src=../../dist/littlejs.js?198></script>
9
+ <script src=../../dist/littlejs.js?1100></script>
9
10
  <script src=../../plugins/postProcess.js></script>
10
- <script src=gameObjects.js?198></script>
11
- <script src=game.js?198></script>
11
+ <script src=gameObjects.js?1100></script>
12
+ <script src=game.js?1100></script>
@@ -53,6 +53,8 @@ for(let y=0; y<=levelSize.y; y+=1)
53
53
  ## Move the Camera
54
54
 
55
55
  Let’s move the camera to the center of our level by setting the cameraPos variable to half the level size.
56
+ You can also change the camera zoom by using camaraScale which controls the number of pixels for each world space unit.
57
+ For this example the camera will just remain stationary, but in other types of games it is very useful!
56
58
 
57
59
  ```javascript
58
60
  setCameraPos(levelSize.scale(.5)); // center camera in level
@@ -307,7 +309,7 @@ collideWithObject(o)
307
309
  But wait! This will cause the ball to careen through the bricks without bouncing. Maybe fun for a special powerup, but not our goal here. This function should return a Boolean value to indicate if the collision needs to be resolved, if it returns false then the ball will not bounce. So the answer is to just add another line of code to the collideWithObject function that returns a truthy value indicating that the collision should occur.
308
310
 
309
311
  ```javascript
310
- return 1; // allow object to collide
312
+ return true; // allow object to collide
311
313
  ```
312
314
 
313
315
  ![LittleJS Screenshot](images/8.png)
@@ -356,7 +358,7 @@ To play the sound we will override collideWithObject for the Ball class. Also re
356
358
  collideWithObject(o)
357
359
  {
358
360
  sound_bounce.play(); // play bounce sound
359
- return 1; // allow object to collide
361
+ return true; // allow object to collide
360
362
  }
361
363
  ```
362
364
  Let’s also make a brick break sound. You can use the ZzFX sound designer again to create your own sound, for this one try the Hit preset.
@@ -422,12 +424,12 @@ const color = this.color;
422
424
  new ParticleEmitter(
423
425
  this.pos, 0, // pos, angle
424
426
  this.size, .1, 200, PI, // emitSize, emitTime, emitRate, emiteCone
425
- 0, // tileInfo
427
+ undefined, // tileInfo
426
428
  color, color, // colorStartA, colorStartB
427
429
  color.scale(1,0), color.scale(1,0), // colorEndA, colorEndB
428
430
  .2, .5, 1, .1, .1, // time, sizeStart, sizeEnd, speed, angleSpeed
429
431
  .99, .95, .4, PI, // damping, angleDamping, gravityScale, cone
430
- .1, .5, 0, 1 // fadeRate, randomness, collide, additive
432
+ .1, .5, false, true // fadeRate, randomness, collide, additive
431
433
  );
432
434
  ```
433
435
 
@@ -446,7 +448,7 @@ collideWithObject(o)
446
448
  {
447
449
  // prevent colliding with paddle if moving upwards
448
450
  if (o == paddle && this.velocity.y > 0)
449
- return 0;
451
+ return false;
450
452
 
451
453
  sound_bounce.play(); // play bounce sound
452
454
 
@@ -460,10 +462,10 @@ collideWithObject(o)
460
462
  this.velocity.y = max(-this.velocity.y, .2);
461
463
 
462
464
  // prevent default collision code
463
- return 0;
465
+ return false;
464
466
  }
465
467
 
466
- return 1; // allow object to collide
468
+ return true; // allow object to collide
467
469
  }
468
470
  ```
469
471