littlejsengine 1.9.11 → 1.10.2

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 (53) hide show
  1. package/README.md +4 -0
  2. package/dist/littlejs.d.ts +80 -9
  3. package/dist/littlejs.esm.js +193 -71
  4. package/dist/littlejs.esm.min.js +1 -1
  5. package/dist/littlejs.js +175 -69
  6. package/dist/littlejs.min.js +1 -1
  7. package/dist/littlejs.release.js +174 -68
  8. package/examples/box2d/game.js +1 -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/index.html +4 -3
  13. package/examples/electron/game.js +1 -1
  14. package/examples/electron/index.html +4 -3
  15. package/examples/empty/game.js +1 -1
  16. package/examples/empty/index.html +2 -1
  17. package/examples/htmlMenu/game.js +67 -0
  18. package/examples/htmlMenu/index.html +56 -0
  19. package/examples/htmlMenu/tiles.png +0 -0
  20. package/examples/index.html +33 -0
  21. package/examples/js13k/game.js +1 -1
  22. package/examples/js13k/index.html +17 -14
  23. package/examples/module/game.js +1 -1
  24. package/examples/module/index.html +3 -2
  25. package/examples/particles/index.html +4 -3
  26. package/examples/platformer/game.js +1 -1
  27. package/examples/platformer/gameLevel.js +2 -2
  28. package/examples/platformer/gameObjects.js +0 -1
  29. package/examples/platformer/index.html +10 -9
  30. package/examples/puzzle/game.js +1 -1
  31. package/examples/puzzle/index.html +4 -3
  32. package/examples/starter/game.js +4 -4
  33. package/examples/starter/index.html +15 -14
  34. package/examples/stress/index.html +3 -2
  35. package/examples/typescript/game.js +1 -1
  36. package/examples/typescript/index.html +3 -2
  37. package/examples/uiSystem/game.js +134 -0
  38. package/examples/uiSystem/index.html +25 -0
  39. package/examples/uiSystem/tiles.png +0 -0
  40. package/jsconfig.json +11 -0
  41. package/package.json +1 -1
  42. package/plugins/box2d.js +2 -1
  43. package/plugins/postProcess.js +6 -7
  44. package/plugins/uiSystem.js +243 -0
  45. package/src/engine.js +48 -20
  46. package/src/engineAudio.js +10 -13
  47. package/src/engineDebug.js +1 -1
  48. package/src/engineDraw.js +101 -27
  49. package/src/engineExport.js +18 -2
  50. package/src/engineInput.js +2 -2
  51. package/src/engineSettings.js +1 -1
  52. package/src/engineUtilities.js +1 -1
  53. package/src/engineWebGL.js +11 -4
package/dist/littlejs.js CHANGED
@@ -128,7 +128,6 @@ function debugPoint(pos, color, time, angle)
128
128
  * @memberof Debug */
129
129
  function debugLine(posA, posB, color, thickness=.1, time)
130
130
  {
131
- ASSERT(typeof color == 'string', 'pass in css color strings');
132
131
  const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
133
132
  const size = vec2(thickness, halfDelta.length()*2);
134
133
  debugRect(posA.add(halfDelta), size, color, time, halfDelta.angle(), true);
@@ -204,6 +203,7 @@ function debugShowErrors()
204
203
 
205
204
  const showError = (message)=>
206
205
  {
206
+ // replace entire page with error message
207
207
  document.body.style.backgroundColor = '#111';
208
208
  document.body.innerHTML = `<pre style=color:#f00;font-size:50px>` + message;
209
209
  }
@@ -1205,7 +1205,7 @@ class Color
1205
1205
  * @return {String} */
1206
1206
  toString(useAlpha = true)
1207
1207
  {
1208
- const toHex = (c)=> ((c=c*255|0)<16 ? '0' : '') + c.toString(16);
1208
+ const toHex = (c)=> ((c=clamp(c)*255|0)<16 ? '0' : '') + c.toString(16);
1209
1209
  return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (useAlpha ? toHex(this.a) : '');
1210
1210
  }
1211
1211
 
@@ -1435,7 +1435,7 @@ let tileSizeDefault = vec2(16);
1435
1435
  * @type {Number}
1436
1436
  * @default
1437
1437
  * @memberof Settings */
1438
- let tileFixBleedScale = .5;
1438
+ let tileFixBleedScale = 0;
1439
1439
 
1440
1440
  ///////////////////////////////////////////////////////////////////////////////
1441
1441
  // Object settings
@@ -2337,12 +2337,13 @@ let drawCount;
2337
2337
  ///////////////////////////////////////////////////////////////////////////////
2338
2338
 
2339
2339
  /**
2340
- * Create a tile info object
2340
+ * Create a tile info object using a grid based system
2341
2341
  * - This can take vecs or floats for easier use and conversion
2342
2342
  * - If an index is passed in, the tile size and index will determine the position
2343
- * @param {(Number|Vector2)} [pos=(0,0)] - Top left corner of tile in pixels or index
2343
+ * @param {(Number|Vector2)} [pos=0] - Index of tile in sheet
2344
2344
  * @param {(Number|Vector2)} [size=tileSizeDefault] - Size of tile in pixels
2345
2345
  * @param {Number} [textureIndex] - Texture index to use
2346
+ * @param {Number} [padding] - How many pixels padding around tiles
2346
2347
  * @return {TileInfo}
2347
2348
  * @example
2348
2349
  * tile(2) // a tile at index 2 using the default tile size of 16
@@ -2351,7 +2352,7 @@ let drawCount;
2351
2352
  * tile(vec2(4,8), vec2(30,10)) // a tile at pixel location (4,8) with a size of (30,10)
2352
2353
  * @memberof Draw
2353
2354
  */
2354
- function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0)
2355
+ function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0, padding=0)
2355
2356
  {
2356
2357
  if (headlessMode)
2357
2358
  return new TileInfo;
@@ -2363,17 +2364,17 @@ function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0)
2363
2364
  size = vec2(size);
2364
2365
  }
2365
2366
 
2366
- // if pos is a number, use it as a tile index
2367
+ // use pos as a tile index
2368
+ const textureInfo = textureInfos[textureIndex];
2369
+ ASSERT(textureInfo, 'Texture not loaded');
2370
+ const sizePadded = size.add(vec2(padding*2));
2371
+ const cols = textureInfo.size.x / sizePadded.x |0;
2367
2372
  if (typeof pos === 'number')
2368
- {
2369
- const textureInfo = textureInfos[textureIndex];
2370
- ASSERT(textureInfo, 'Texture not loaded');
2371
- const cols = textureInfo.size.x / size.x |0;
2372
- pos = vec2((pos%cols)*size.x, (pos/cols|0)*size.y);
2373
- }
2373
+ pos = vec2(pos%cols, pos/cols|0);
2374
+ pos = vec2(pos.x*sizePadded.x+padding, pos.y*sizePadded.y+padding);
2374
2375
 
2375
2376
  // return a tile info object
2376
- return new TileInfo(pos, size, textureIndex);
2377
+ return new TileInfo(pos, size, textureIndex, padding);
2377
2378
  }
2378
2379
 
2379
2380
  /**
@@ -2385,8 +2386,9 @@ class TileInfo
2385
2386
  * @param {Vector2} [pos=(0,0)] - Top left corner of tile in pixels
2386
2387
  * @param {Vector2} [size=tileSizeDefault] - Size of tile in pixels
2387
2388
  * @param {Number} [textureIndex] - Texture index to use
2389
+ * @param {Number} [padding] - How many pixels padding around tiles
2388
2390
  */
2389
- constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0)
2391
+ constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0, padding=0)
2390
2392
  {
2391
2393
  /** @property {Vector2} - Top left corner of tile in pixels */
2392
2394
  this.pos = pos.copy();
@@ -2394,6 +2396,8 @@ class TileInfo
2394
2396
  this.size = size.copy();
2395
2397
  /** @property {Number} - Texture index to use */
2396
2398
  this.textureIndex = textureIndex;
2399
+ /** @property {Number} - How many pixels padding around tiles */
2400
+ this.padding = padding;
2397
2401
  }
2398
2402
 
2399
2403
  /** Returns a copy of this tile offset by a vector
@@ -2410,7 +2414,7 @@ class TileInfo
2410
2414
  frame(frame)
2411
2415
  {
2412
2416
  ASSERT(typeof frame == 'number');
2413
- return this.offset(vec2(frame*this.size.x, 0));
2417
+ return this.offset(vec2(frame*(this.size.x+this.padding*2), 0));
2414
2418
  }
2415
2419
 
2416
2420
  /** Returns the texture info for this tile
@@ -2435,8 +2439,6 @@ class TextureInfo
2435
2439
  this.size = vec2(image.width, image.height);
2436
2440
  /** @property {WebGLTexture} - webgl texture */
2437
2441
  this.glTexture = glEnable && glCreateTexture(image);
2438
- /** @property {Vector2} - size to adjust tile to fix bleeding */
2439
- this.fixBleedSize = vec2(tileFixBleedScale).divide(this.size);
2440
2442
  }
2441
2443
  }
2442
2444
 
@@ -2505,11 +2507,12 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
2505
2507
  if (textureInfo)
2506
2508
  {
2507
2509
  // calculate uvs and render
2508
- const x = tileInfo.pos.x / textureInfo.size.x;
2509
- const y = tileInfo.pos.y / textureInfo.size.y;
2510
- const w = tileInfo.size.x / textureInfo.size.x;
2511
- const h = tileInfo.size.y / textureInfo.size.y;
2512
- const tileImageFixBleed = textureInfo.fixBleedSize;
2510
+ const sizeInverse = vec2(1).divide(textureInfo.size);
2511
+ const x = tileInfo.pos.x * sizeInverse.x;
2512
+ const y = tileInfo.pos.y * sizeInverse.y;
2513
+ const w = tileInfo.size.x * sizeInverse.x;
2514
+ const h = tileInfo.size.y * sizeInverse.y;
2515
+ const tileImageFixBleed = sizeInverse.scale(tileFixBleedScale);
2513
2516
  glSetTexture(textureInfo.glTexture);
2514
2517
  glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
2515
2518
  x + tileImageFixBleed.x, y + tileImageFixBleed.y,
@@ -2526,6 +2529,7 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
2526
2529
  {
2527
2530
  // normal canvas 2D rendering method (slower)
2528
2531
  showWatermark && ++drawCount;
2532
+ size = vec2(size.x, -size.y); // fix upside down sprites
2529
2533
  drawCanvas2D(pos, size, angle, mirror, (context)=>
2530
2534
  {
2531
2535
  if (textureInfo)
@@ -2563,6 +2567,74 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
2563
2567
  drawTile(pos, size, undefined, color, angle, false, undefined, useWebGL, screenSpace, context);
2564
2568
  }
2565
2569
 
2570
+ /** Draw colored polygon using passed in points
2571
+ * @param {Array} points - Array of Vector2 points
2572
+ * @param {Color} [color=(1,1,1,1)]
2573
+ * @param {Number} [lineWidth=0]
2574
+ * @param {Color} [lineColor=(0,0,0,1)]
2575
+ * @param {Boolean} [screenSpace=false]
2576
+ * @param {CanvasRenderingContext2D} [context=mainContext]
2577
+ * @memberof Draw */
2578
+ function drawPoly(points, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=mainContext)
2579
+ {
2580
+ context.fillStyle = color.toString();
2581
+ context.beginPath();
2582
+ for (const point of screenSpace ? points : points.map(worldToScreen))
2583
+ context.lineTo(point.x, point.y);
2584
+ context.closePath();
2585
+ context.fill();
2586
+ if (lineWidth)
2587
+ {
2588
+ context.strokeStyle = lineColor.toString();
2589
+ context.lineWidth = screenSpace ? lineWidth : lineWidth*cameraScale;
2590
+ context.stroke();
2591
+ }
2592
+ }
2593
+
2594
+ /** Draw colored ellipse using passed in point
2595
+ * @param {Vector2} pos
2596
+ * @param {Number} [width=1]
2597
+ * @param {Number} [height=1]
2598
+ * @param {Number} [angle=0]
2599
+ * @param {Color} [color=(1,1,1,1)]
2600
+ * @param {Number} [lineWidth=0]
2601
+ * @param {Color} [lineColor=(0,0,0,1)]
2602
+ * @param {Boolean} [screenSpace=false]
2603
+ * @param {CanvasRenderingContext2D} [context=mainContext]
2604
+ * @memberof Draw */
2605
+ function drawEllipse(pos, width=1, height=1, angle=0, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=mainContext)
2606
+ {
2607
+ if (!screenSpace)
2608
+ {
2609
+ pos = worldToScreen(pos);
2610
+ width *= cameraScale;
2611
+ height *= cameraScale;
2612
+ lineWidth *= cameraScale;
2613
+ }
2614
+ context.fillStyle = color.toString();
2615
+ context.beginPath();
2616
+ context.ellipse(pos.x, pos.y, width, height, angle, 0, 9);
2617
+ context.fill();
2618
+ if (lineWidth)
2619
+ {
2620
+ context.strokeStyle = lineColor.toString();
2621
+ context.lineWidth = lineWidth;
2622
+ context.stroke();
2623
+ }
2624
+ }
2625
+
2626
+ /** Draw colored circle using passed in point
2627
+ * @param {Vector2} pos
2628
+ * @param {Number} [radius=1]
2629
+ * @param {Color} [color=(1,1,1,1)]
2630
+ * @param {Number} [lineWidth=0]
2631
+ * @param {Color} [lineColor=(0,0,0,1)]
2632
+ * @param {Boolean} [screenSpace=false]
2633
+ * @param {CanvasRenderingContext2D} [context=mainContext]
2634
+ * @memberof Draw */
2635
+ function drawCircle(pos, radius=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=mainContext)
2636
+ { drawEllipse(pos, radius, radius, 0, color, lineWidth, lineColor, screenSpace, context); }
2637
+
2566
2638
  /** Draw colored line between two points
2567
2639
  * @param {Vector2} posA
2568
2640
  * @param {Vector2} posB
@@ -2633,10 +2705,11 @@ function setBlendMode(additive, useWebGL=glEnable, context)
2633
2705
  * @param {CanvasTextAlign} [textAlign='center']
2634
2706
  * @param {String} [font=fontDefault]
2635
2707
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
2708
+ * @param {Number} [maxWidth]
2636
2709
  * @memberof Draw */
2637
- function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, context)
2710
+ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, context, maxWidth)
2638
2711
  {
2639
- drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, context);
2712
+ drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, context, maxWidth);
2640
2713
  }
2641
2714
 
2642
2715
  /** Draw text on overlay canvas in screen space
@@ -2650,8 +2723,9 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
2650
2723
  * @param {CanvasTextAlign} [textAlign]
2651
2724
  * @param {String} [font=fontDefault]
2652
2725
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
2726
+ * @param {Number} [maxWidth]
2653
2727
  * @memberof Draw */
2654
- function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, context=overlayContext)
2728
+ 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)
2655
2729
  {
2656
2730
  context.fillStyle = color.toString();
2657
2731
  context.lineWidth = lineWidth;
@@ -2664,8 +2738,8 @@ function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineCol
2664
2738
  pos = pos.copy();
2665
2739
  (text+'').split('\n').forEach(line=>
2666
2740
  {
2667
- lineWidth && context.strokeText(line, pos.x, pos.y);
2668
- context.fillText(line, pos.x, pos.y);
2741
+ lineWidth && context.strokeText(line, pos.x, pos.y, maxWidth);
2742
+ context.fillText(line, pos.x, pos.y, maxWidth);
2669
2743
  pos.y += size;
2670
2744
  });
2671
2745
  }
@@ -2773,8 +2847,8 @@ function toggleFullscreen()
2773
2847
  if (document.exitFullscreen)
2774
2848
  document.exitFullscreen();
2775
2849
  }
2776
- else if (document.body.requestFullscreen)
2777
- document.body.requestFullscreen();
2850
+ else if (engineRoot.requestFullscreen)
2851
+ engineRoot.requestFullscreen();
2778
2852
  }
2779
2853
  /**
2780
2854
  * LittleJS Input System
@@ -2945,7 +3019,7 @@ function inputInit()
2945
3019
 
2946
3020
  onkeydown = (e)=>
2947
3021
  {
2948
- if (debug && e.target != document.body) return;
3022
+ if (debug && e.target != engineRoot) return;
2949
3023
  if (!e.repeat)
2950
3024
  {
2951
3025
  isUsingGamepad = false;
@@ -2958,7 +3032,7 @@ function inputInit()
2958
3032
 
2959
3033
  onkeyup = (e)=>
2960
3034
  {
2961
- if (debug && e.target != document.body) return;
3035
+ if (debug && e.target != engineRoot) return;
2962
3036
  inputData[0][e.code] = 4;
2963
3037
  if (inputWASDEmulateDirection)
2964
3038
  inputData[0][remapKey(e.code)] = 4;
@@ -3610,17 +3684,6 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
3610
3684
  {
3611
3685
  if (!soundEnable || headlessMode) return;
3612
3686
 
3613
- // prevent sounds from building up if they can't be played
3614
- if (audioContext.state != 'running')
3615
- {
3616
- // fix stalled audio
3617
- audioContext.resume().then(()=>
3618
- playSamples(sampleChannels, volume, rate, pan, loop, sampleRate, gainNode));
3619
-
3620
- // prevent suspended sounds from building up
3621
- return;
3622
- }
3623
-
3624
3687
  // create buffer and source
3625
3688
  const channelCount = sampleChannels.length;
3626
3689
  const sampleLength = sampleChannels[0].length;
@@ -3642,8 +3705,16 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
3642
3705
  const pannerNode = new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)});
3643
3706
  source.connect(pannerNode).connect(gainNode);
3644
3707
 
3645
- // play and return sound
3646
- source.start();
3708
+ // play the sound
3709
+ if (audioContext.state != 'running')
3710
+ {
3711
+ // fix stalled audio and play
3712
+ audioContext.resume().then(()=>source.start());
3713
+ }
3714
+ else
3715
+ source.start();
3716
+
3717
+ // return sound
3647
3718
  return source;
3648
3719
  }
3649
3720
 
@@ -4803,10 +4874,10 @@ function glInit()
4803
4874
 
4804
4875
  // create the canvas and textures
4805
4876
  glCanvas = document.createElement('canvas');
4806
- glContext = glCanvas.getContext('webgl2');
4877
+ glContext = glCanvas.getContext('webgl2', {antialias:!canvasPixelated});
4807
4878
 
4808
4879
  // some browsers are much faster without copying the gl buffer so we just overlay it instead
4809
- glOverlay && document.body.appendChild(glCanvas);
4880
+ glOverlay && engineRoot.appendChild(glCanvas);
4810
4881
 
4811
4882
  // setup vertex and fragment shaders
4812
4883
  glShader = glCreateProgram(
@@ -4861,7 +4932,8 @@ function glPreRender()
4861
4932
  // set up the shader
4862
4933
  glContext.useProgram(glShader);
4863
4934
  glContext.activeTexture(gl_TEXTURE0);
4864
- glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = textureInfos[0].glTexture);
4935
+ if (textureInfos[0])
4936
+ glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = textureInfos[0].glTexture);
4865
4937
 
4866
4938
  // set vertex attributes
4867
4939
  let offset = glAdditive = glBatchAdditive = 0;
@@ -4959,8 +5031,14 @@ function glCreateTexture(image)
4959
5031
  // build the texture
4960
5032
  const texture = glContext.createTexture();
4961
5033
  glContext.bindTexture(gl_TEXTURE_2D, texture);
4962
- if (image)
5034
+ if (image && image.width)
4963
5035
  glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
5036
+ else
5037
+ {
5038
+ // create a white texture
5039
+ const whitePixel = new Uint8Array([255, 255, 255, 255]);
5040
+ glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, 1, 1, 0, gl_RGBA, gl_UNSIGNED_BYTE, whitePixel);
5041
+ }
4964
5042
 
4965
5043
  // use point filtering for pixelated rendering
4966
5044
  const filter = canvasPixelated ? gl_NEAREST : gl_LINEAR;
@@ -5103,7 +5181,7 @@ const engineName = 'LittleJS';
5103
5181
  * @type {String}
5104
5182
  * @default
5105
5183
  * @memberof Engine */
5106
- const engineVersion = '1.9.11';
5184
+ const engineVersion = '1.10.2';
5107
5185
 
5108
5186
  /** Frames per second to update
5109
5187
  * @type {Number}
@@ -5148,6 +5226,12 @@ let timeReal = 0;
5148
5226
  * @memberof Engine */
5149
5227
  let paused = false;
5150
5228
 
5229
+ /** The root element that engine is attached to
5230
+ * @type {HTMLElement}
5231
+ * @default document.body
5232
+ * @memberof Engine */
5233
+ let engineRoot;
5234
+
5151
5235
  /** Set if game is paused
5152
5236
  * @param {Boolean} isPaused
5153
5237
  * @memberof Engine */
@@ -5181,8 +5265,9 @@ function engineAddPlugin(updateFunction, renderFunction)
5181
5265
  * @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects
5182
5266
  * @param {Function} gameRenderPost - Called after objects are rendered, draw effects or hud that appear above all objects
5183
5267
  * @param {Array} [imageSources=['tiles.png']] - Image to load
5268
+ * @param {HTMLElement} [rootElement] - Root element to attach to, the document body by default
5184
5269
  * @memberof Engine */
5185
- function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=['tiles.png'])
5270
+ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
5186
5271
  {
5187
5272
  ASSERT(Array.isArray(imageSources), 'pass in images as array');
5188
5273
 
@@ -5224,6 +5309,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
5224
5309
  for (const o of engineObjects)
5225
5310
  o.parent || o.updateTransforms();
5226
5311
  inputUpdate();
5312
+ pluginUpdateList.forEach(f=>f());
5227
5313
  debugUpdate();
5228
5314
  gameUpdatePost();
5229
5315
  inputUpdatePost();
@@ -5339,16 +5425,22 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
5339
5425
  }
5340
5426
 
5341
5427
  // setup html
5342
- const styleBody =
5428
+ const styleRoot =
5343
5429
  'margin:0;overflow:hidden;' + // fill the window
5430
+ 'width:100vw;height:100vh;' + // fill the window
5431
+ 'display:flex;' + // use flexbox
5432
+ 'align-items:center;' + // horizontal center
5433
+ (canvasPixelated ? 'image-rendering:pixelated;' : '') + // pixel art
5434
+ 'justify-content:center;' + // vertical center
5344
5435
  'background:#000;' + // set background color
5345
5436
  'user-select:none;' + // prevent hold to select
5346
5437
  '-webkit-user-select:none;' + // compatibility for ios
5347
5438
  (!touchInputEnable ? '' : // no touch css setttings
5348
5439
  'touch-action:none;' + // prevent mobile pinch to resize
5349
5440
  '-webkit-touch-callout:none');// compatibility for ios
5350
- document.body.style.cssText = styleBody;
5351
- document.body.appendChild(mainCanvas = document.createElement('canvas'));
5441
+ engineRoot = rootElement;
5442
+ engineRoot.style.cssText = styleRoot;
5443
+ engineRoot.appendChild(mainCanvas = document.createElement('canvas'));
5352
5444
  mainContext = mainCanvas.getContext('2d');
5353
5445
 
5354
5446
  // init stuff and start engine
@@ -5358,13 +5450,14 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
5358
5450
  glInit();
5359
5451
 
5360
5452
  // create overlay canvas for hud to appear above gl canvas
5361
- document.body.appendChild(overlayCanvas = document.createElement('canvas'));
5453
+ engineRoot.appendChild(overlayCanvas = document.createElement('canvas'));
5362
5454
  overlayContext = overlayCanvas.getContext('2d');
5363
5455
 
5364
5456
  // set canvas style
5365
- const styleCanvas = 'position:absolute;' + // position
5366
- 'top:50%;left:50%;transform:translate(-50%,-50%)'; // center
5367
- (glCanvas||mainCanvas).style.cssText = mainCanvas.style.cssText = overlayCanvas.style.cssText = styleCanvas;
5457
+ const styleCanvas = 'position:absolute'; // allow canvases to overlap
5458
+ mainCanvas.style.cssText = overlayCanvas.style.cssText = styleCanvas;
5459
+ if (glCanvas)
5460
+ glCanvas.style.cssText = styleCanvas;
5368
5461
  updateCanvas();
5369
5462
 
5370
5463
  // create promises for loading images
@@ -5381,19 +5474,32 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
5381
5474
  })
5382
5475
  );
5383
5476
 
5384
- // draw splash screen
5385
- showSplashScreen && promises.push(new Promise(resolve =>
5477
+ if (!imageSources.length)
5386
5478
  {
5387
- let t = 0;
5388
- console.log(`${engineName} Engine v${engineVersion}`);
5389
- updateSplash();
5390
- function updateSplash()
5479
+ // no images to load
5480
+ promises.push(new Promise(resolve =>
5391
5481
  {
5392
- clearInput();
5393
- drawEngineSplashScreen(t+=.01);
5394
- t>1 ? resolve() : setTimeout(updateSplash, 16);
5395
- }
5396
- }));
5482
+ textureInfos[0] = new TextureInfo(new Image);
5483
+ resolve();
5484
+ }));
5485
+ }
5486
+
5487
+ if (showSplashScreen)
5488
+ {
5489
+ // draw splash screen
5490
+ promises.push(new Promise(resolve =>
5491
+ {
5492
+ let t = 0;
5493
+ console.log(`${engineName} Engine v${engineVersion}`);
5494
+ updateSplash();
5495
+ function updateSplash()
5496
+ {
5497
+ clearInput();
5498
+ drawEngineSplashScreen(t+=.01);
5499
+ t>1 ? resolve() : setTimeout(updateSplash, 16);
5500
+ }
5501
+ }));
5502
+ }
5397
5503
 
5398
5504
  // load all of the images
5399
5505
  Promise.all(promises).then(startEngine);