littlejsengine 1.8.5 → 1.8.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 (40) hide show
  1. package/README.md +13 -3
  2. package/build/littlejs.d.ts +22 -15
  3. package/build/littlejs.esm.js +255 -55
  4. package/build/littlejs.esm.min.js +1 -1
  5. package/build/littlejs.js +253 -55
  6. package/build/littlejs.min.js +1 -1
  7. package/build/littlejs.release.js +253 -55
  8. package/examples/breakout/game.js +3 -0
  9. package/examples/breakout/index.html +3 -3
  10. package/examples/breakoutTutorial/index.html +2 -2
  11. package/examples/electron/game.js +3 -0
  12. package/examples/electron/index.html +2 -2
  13. package/examples/js13k/index.html +13 -13
  14. package/examples/logo.png +0 -0
  15. package/examples/module/game.js +3 -0
  16. package/examples/module/index.html +1 -1
  17. package/examples/particles/index.html +1 -1
  18. package/examples/platformer/game.js +3 -0
  19. package/examples/platformer/gamePlayer.js +1 -1
  20. package/examples/platformer/index.html +6 -6
  21. package/examples/puzzle/game.js +3 -0
  22. package/examples/puzzle/index.html +2 -2
  23. package/examples/starter/game.js +3 -0
  24. package/examples/starter/index.html +13 -13
  25. package/examples/stress/index.html +3 -1
  26. package/examples/typescript/game.js +3 -1
  27. package/examples/typescript/game.ts +3 -0
  28. package/examples/typescript/index.html +1 -1
  29. package/package.json +1 -1
  30. package/src/engine.js +186 -9
  31. package/src/engineAudio.js +1 -1
  32. package/src/engineDraw.js +1 -1
  33. package/src/engineExport.js +2 -0
  34. package/src/engineInput.js +24 -14
  35. package/src/engineObject.js +1 -1
  36. package/src/engineParticles.js +1 -1
  37. package/src/engineSettings.js +11 -0
  38. package/src/engineTileLayer.js +8 -8
  39. package/src/engineUtilities.js +5 -6
  40. package/src/engineWebGL.js +15 -14
package/README.md CHANGED
@@ -1,4 +1,8 @@
1
- # ![LittleJS Logo](examples/favicon.png) LittleJS - The Tiny JavaScript Game Engine That Can
1
+ # ![LittleJS Logo](examples/favicon.png) LittleJS - The Tiny Fast JavaScript Game Engine
2
+
3
+ <div align="center">
4
+
5
+ ![LittleJS Screenshot](examples/logo.png)
2
6
 
3
7
  [![NPM Package][npm]][npm-url]
4
8
  [![Build Size][build-size]][build-size-url]
@@ -6,12 +10,18 @@
6
10
  [![DeepScan][deepscan]][deepscan-url]
7
11
  [![Discord][discord]][discord-url]
8
12
 
13
+ </div>
14
+
9
15
  ## 🚂 All aboard!
10
16
 
11
- LittleJS is a lightweight open source HTML5 game engine for modern web development.
17
+ LittleJS is a fast lightweight open source HTML5 game engine for modern web development.
12
18
  It's small footprint is packed with a comprehensive feature set including hybrid rendering, physics, particles, sound effects, music, and input handling.
13
19
  The code is very clean and well documented with some fun examples to get you started. Choo-Choo! 🚂
14
20
 
21
+ ### 🚀 Join the First Ever LittleJS Game Jam
22
+
23
+ *The LittleJS Game Jam will take place From Nov 11 to Dec 12! Unleash your creativity and develop amazing games using the LittleJS game engine. đŸ•šī¸đŸŽŽ [Sign up today and get more info about the jam on itch.io!](https://itch.io/jam/littlejs-game-jam)*
24
+
15
25
  <div align="center">
16
26
 
17
27
  ## [Demo](https://killedbyapixel.github.io/LittleJS/examples/starter/) | [Docs](https://killedbyapixel.github.io/LittleJS/docs) | [Trailer](https://youtu.be/chuBzGjv7Ms) | [Discord](https://discord.gg/zb7hcGkyZe) | [Tutorial](https://github.com/KilledByAPixel/LittleJS/blob/main/examples/breakoutTutorial/README.md)
@@ -27,7 +37,7 @@ LittleJS is a small but powerful game engine with many features and no dependenc
27
37
  ### ✨ Graphics
28
38
 
29
39
  - Super fast sprite and tile map rendering engine with WebGL
30
- - Update and render over 10,000 objects at a solid 60fps
40
+ - Update and render well over 10,000 sprites at a solid 60fps
31
41
  - Apply [Shadertoy](https://www.shadertoy.com) compatible shaders for post-processing effects
32
42
  - Robust particle effect system and [effect design tool](https://killedbyapixel.github.io/LittleJS/examples/particles/)
33
43
 
@@ -204,6 +204,11 @@ declare module "littlejs.esm" {
204
204
  * @default
205
205
  * @memberof Settings */
206
206
  export let fontDefault: string;
207
+ /** Enable to show the LittleJS splash screen be shown on startup
208
+ * @type {Boolean}
209
+ * @default
210
+ * @memberof Settings */
211
+ export let showSplashScreen: boolean;
207
212
  /** Default size of tiles in pixels
208
213
  * @type {Vector2}
209
214
  * @default Vector2(16,16)
@@ -375,6 +380,10 @@ declare module "littlejs.esm" {
375
380
  * @param {String} font
376
381
  * @memberof Settings */
377
382
  export function setFontDefault(font: string): void;
383
+ /** Set if the LittleJS splash screen be shown on startup
384
+ * @param {Boolean} show
385
+ * @memberof Settings */
386
+ export function setShowSplashScreen(show: boolean): void;
378
387
  /** Set if webgl rendering is enabled
379
388
  * @param {Boolean} enable
380
389
  * @memberof Settings */
@@ -795,9 +804,9 @@ declare module "littlejs.esm" {
795
804
  * @return {Boolean} */
796
805
  arrayCheck(arraySize: Vector2): boolean;
797
806
  /** Returns this vector expressed as a string
798
- * @param {float} digits - precision to display
807
+ * @param {Number} digits - precision to display
799
808
  * @return {String} */
800
- toString(digits?: float): string;
809
+ toString(digits?: number): string;
801
810
  }
802
811
  /**
803
812
  * Color object (red, green, blue, alpha) with some helpful functions
@@ -1225,7 +1234,7 @@ declare module "littlejs.esm" {
1225
1234
  * @return {WebGLProgram}
1226
1235
  * @memberof WebGL */
1227
1236
  export function glCreateProgram(vsSource: WebGLShader, fsSource: WebGLShader): WebGLProgram;
1228
- /** Create WebGL texture from an image and set the texture settings
1237
+ /** Create WebGL texture from an image and init the texture settings
1229
1238
  * @param {Image} image
1230
1239
  * @return {WebGLTexture}
1231
1240
  * @memberof WebGL */
@@ -1475,7 +1484,7 @@ declare module "littlejs.esm" {
1475
1484
  * @param {Boolean} [loop=1] - True if the music should loop
1476
1485
  * @return {AudioBufferSourceNode} - The audio source node
1477
1486
  */
1478
- play(volume?: number, loop?: boolean): AudioBufferSourceNode;
1487
+ playMusic(volume?: number, loop?: boolean): AudioBufferSourceNode;
1479
1488
  }
1480
1489
  /** Play an mp3, ogg, or wav audio from a local file or url
1481
1490
  * @param {String} url - Location of sound file to play
@@ -1590,7 +1599,7 @@ declare module "littlejs.esm" {
1590
1599
  angleVelocity: number;
1591
1600
  spawnTime: number;
1592
1601
  children: any[];
1593
- collideTiles: number;
1602
+ collideTiles: boolean;
1594
1603
  /** Update the object transform and physics, called automatically by engine once each frame */
1595
1604
  update(): void;
1596
1605
  groundObject: any;
@@ -1775,17 +1784,17 @@ declare module "littlejs.esm" {
1775
1784
  /** Draw directly to the 2D canvas in world space (bipass webgl)
1776
1785
  * @param {Vector2} pos
1777
1786
  * @param {Vector2} size
1778
- * @param {Number} [angle=0]
1779
- * @param {Boolean} [mirror=0]
1787
+ * @param {Number} angle
1788
+ * @param {Boolean} mirror
1780
1789
  * @param {Function} drawFunction */
1781
- drawCanvas2D(pos: Vector2, size: Vector2, angle?: number, mirror?: boolean, drawFunction: Function): void;
1790
+ drawCanvas2D(pos: Vector2, size: Vector2, angle: number, mirror: boolean, drawFunction: Function): void;
1782
1791
  /** Draw a tile directly onto the layer canvas
1783
- * @param {Vector2} pos
1784
- * @param {Vector2} [size=Vector2(1,1)]
1792
+ * @param {Vector2} pos
1793
+ * @param {Vector2} [size=Vector2(1,1)]
1785
1794
  * @param {TileInfo} [tileInfo]
1786
- * @param {Color} [color=Color()]
1787
- * @param {Number} [angle=0]
1788
- * @param {Boolean} [mirror=0] */
1795
+ * @param {Color} [color=Color()]
1796
+ * @param {Number} [angle=0]
1797
+ * @param {Boolean} [mirror=0] */
1789
1798
  drawTile(pos: Vector2, size?: Vector2, tileInfo?: TileInfo, color?: Color, angle?: number, mirror?: boolean): void;
1790
1799
  /** Draw a rectangle directly onto the layer canvas
1791
1800
  * @param {Vector2} pos
@@ -1879,8 +1888,6 @@ declare module "littlejs.esm" {
1879
1888
  fadeRate: number;
1880
1889
  /** @property {Number} - Apply extra randomness percent */
1881
1890
  randomness: number;
1882
- /** @property {Number} - Do particles collide against tiles */
1883
- collideTiles: boolean;
1884
1891
  /** @property {Number} - Should particles use addtive blend */
1885
1892
  additive: boolean;
1886
1893
  /** @property {Boolean} - Should it be in local space of emitter */
@@ -819,7 +819,7 @@ class Vector2
819
819
  arrayCheck(arraySize) { return this.x >= 0 && this.y >= 0 && this.x < arraySize.x && this.y < arraySize.y; }
820
820
 
821
821
  /** Returns this vector expressed as a string
822
- * @param {float} digits - precision to display
822
+ * @param {Number} digits - precision to display
823
823
  * @return {String} */
824
824
  toString(digits=3)
825
825
  { if (debug) { return `(${(this.x<0?'':' ') + this.x.toFixed(digits)},${(this.y<0?'':' ') + this.y.toFixed(digits)} )`; }}
@@ -1008,11 +1008,10 @@ class Color
1008
1008
  * @return {Number} */
1009
1009
  rgbaInt()
1010
1010
  {
1011
- const toByte = (c)=> clamp(c)*255|0;
1012
- const r = toByte(this.r);
1013
- const g = toByte(this.g)<<8;
1014
- const b = toByte(this.b)<<16;
1015
- const a = toByte(this.a)<<24;
1011
+ const r = clamp(this.r)*255|0;
1012
+ const g = clamp(this.g)*255<<8;
1013
+ const b = clamp(this.b)*255<<16;
1014
+ const a = clamp(this.a)*255<<24;
1016
1015
  return r + g + b + a;
1017
1016
  }
1018
1017
  }
@@ -1120,6 +1119,12 @@ let canvasPixelated = 1;
1120
1119
  * @memberof Settings */
1121
1120
  let fontDefault = 'arial';
1122
1121
 
1122
+ /** Enable to show the LittleJS splash screen be shown on startup
1123
+ * @type {Boolean}
1124
+ * @default
1125
+ * @memberof Settings */
1126
+ let showSplashScreen = 0;
1127
+
1123
1128
  ///////////////////////////////////////////////////////////////////////////////
1124
1129
  // WebGL settings
1125
1130
 
@@ -1353,6 +1358,11 @@ function setCanvasPixelated(pixelated) { canvasPixelated = pixelated; }
1353
1358
  * @memberof Settings */
1354
1359
  function setFontDefault(font) { fontDefault = font; }
1355
1360
 
1361
+ /** Set if the LittleJS splash screen be shown on startup
1362
+ * @param {Boolean} show
1363
+ * @memberof Settings */
1364
+ function setShowSplashScreen(show) { showSplashScreen = show; }
1365
+
1356
1366
  /** Set if webgl rendering is enabled
1357
1367
  * @param {Boolean} enable
1358
1368
  * @memberof Settings */
@@ -1599,7 +1609,7 @@ class EngineObject
1599
1609
  // init other internal object stuff
1600
1610
  this.spawnTime = time;
1601
1611
  this.children = [];
1602
- this.collideTiles = 1;
1612
+ this.collideTiles = false;
1603
1613
 
1604
1614
  // add to list of objects
1605
1615
  engineObjects.push(this);
@@ -1944,7 +1954,7 @@ let mainCanvasSize = vec2();
1944
1954
  * @memberof Draw */
1945
1955
  let textureInfos = [];
1946
1956
 
1947
- // Engine internal variables not exposed to documentation
1957
+ // Keep track of how many draw calls there were each frame for debugging
1948
1958
  let drawCount;
1949
1959
 
1950
1960
  ///////////////////////////////////////////////////////////////////////////////
@@ -2547,23 +2557,33 @@ function inputUpdatePost()
2547
2557
  ///////////////////////////////////////////////////////////////////////////////
2548
2558
  // Keyboard event handlers
2549
2559
 
2550
- onkeydown = (e)=>
2551
2560
  {
2552
- if (debug && e.target != document.body) return;
2553
- e.repeat || (inputData[isUsingGamepad = 0][remapKey(e.which)] = 3);
2554
- preventDefaultInput && e.preventDefault();
2555
- }
2561
+ onkeydown = (e)=>
2562
+ {
2563
+ if (debug && e.target != document.body) return;
2564
+ if (!e.repeat)
2565
+ {
2566
+ inputData[isUsingGamepad = 0][e.which] = 3;
2567
+ if (inputWASDEmulateDirection)
2568
+ inputData[0][remapKey(e.which)] = 3;
2569
+ }
2570
+ preventDefaultInput && e.preventDefault();
2571
+ }
2556
2572
 
2557
- onkeyup = (e)=>
2558
- {
2559
- if (debug && e.target != document.body) return;
2560
- inputData[0][remapKey(e.which)] = 4;
2561
- }
2573
+ onkeyup = (e)=>
2574
+ {
2575
+ if (debug && e.target != document.body) return;
2576
+ inputData[0][e.which] = 4;
2577
+ if (inputWASDEmulateDirection)
2578
+ inputData[0][remapKey(e.which)] = 4;
2579
+ }
2562
2580
 
2563
- function remapKey(c)
2564
- {
2565
- return inputWASDEmulateDirection ?
2566
- c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
2581
+ // handle remapping wasd keys to directions
2582
+ function remapKey(c)
2583
+ {
2584
+ return inputWASDEmulateDirection ?
2585
+ c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
2586
+ }
2567
2587
  }
2568
2588
 
2569
2589
  ///////////////////////////////////////////////////////////////////////////////
@@ -3062,7 +3082,7 @@ class Music extends Sound
3062
3082
  * @param {Boolean} [loop=1] - True if the music should loop
3063
3083
  * @return {AudioBufferSourceNode} - The audio source node
3064
3084
  */
3065
- play(volume, loop = 1)
3085
+ playMusic(volume, loop = 1)
3066
3086
  { return super.play(0, volume, 1, 1, loop); }
3067
3087
  }
3068
3088
 
@@ -3709,10 +3729,10 @@ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderO
3709
3729
  /** Draw directly to the 2D canvas in world space (bipass webgl)
3710
3730
  * @param {Vector2} pos
3711
3731
  * @param {Vector2} size
3712
- * @param {Number} [angle=0]
3713
- * @param {Boolean} [mirror=0]
3732
+ * @param {Number} angle
3733
+ * @param {Boolean} mirror
3714
3734
  * @param {Function} drawFunction */
3715
- drawCanvas2D(pos, size, angle=0, mirror, drawFunction)
3735
+ drawCanvas2D(pos, size, angle, mirror, drawFunction)
3716
3736
  {
3717
3737
  const context = this.context;
3718
3738
  context.save();
@@ -3726,12 +3746,12 @@ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderO
3726
3746
  }
3727
3747
 
3728
3748
  /** Draw a tile directly onto the layer canvas
3729
- * @param {Vector2} pos
3730
- * @param {Vector2} [size=Vector2(1,1)]
3749
+ * @param {Vector2} pos
3750
+ * @param {Vector2} [size=Vector2(1,1)]
3731
3751
  * @param {TileInfo} [tileInfo]
3732
- * @param {Color} [color=Color()]
3733
- * @param {Number} [angle=0]
3734
- * @param {Boolean} [mirror=0] */
3752
+ * @param {Color} [color=Color()]
3753
+ * @param {Number} [angle=0]
3754
+ * @param {Boolean} [mirror=0] */
3735
3755
  drawTile(pos, size=vec2(1), tileInfo, color=new Color, angle, mirror)
3736
3756
  {
3737
3757
  this.drawCanvas2D(pos, size, angle, mirror, (context)=>
@@ -3894,7 +3914,7 @@ class ParticleEmitter extends EngineObject
3894
3914
  this.fadeRate = fadeRate;
3895
3915
  /** @property {Number} - Apply extra randomness percent */
3896
3916
  this.randomness = randomness;
3897
- /** @property {Number} - Do particles collide against tiles */
3917
+ /** @property {Boolean} - Do particles collide against tiles */
3898
3918
  this.collideTiles = collideTiles;
3899
3919
  /** @property {Number} - Should particles use addtive blend */
3900
3920
  this.additive = additive;
@@ -4382,7 +4402,7 @@ let glCanvas;
4382
4402
  let glContext;
4383
4403
 
4384
4404
  // WebGL internal variables not exposed to documentation
4385
- let glActiveTexture, glShader, glArrayBuffer, glPositionData, glColorData, glBatchCount, glBatchAdditive, glAdditive;
4405
+ let glActiveTexture, glShader, glArrayBuffer, glVertexData, glPositionData, glColorData, glBatchCount, glBatchAdditive, glAdditive;
4386
4406
 
4387
4407
  ///////////////////////////////////////////////////////////////////////////////
4388
4408
 
@@ -4419,10 +4439,10 @@ function glInit()
4419
4439
  );
4420
4440
 
4421
4441
  // init buffers
4422
- const vertexData = new ArrayBuffer(gl_VERTEX_BUFFER_SIZE);
4442
+ glVertexData = new ArrayBuffer(gl_VERTEX_BUFFER_SIZE);
4443
+ glPositionData = new Float32Array(glVertexData);
4444
+ glColorData = new Uint32Array(glVertexData);
4423
4445
  glArrayBuffer = glContext.createBuffer();
4424
- glPositionData = new Float32Array(vertexData);
4425
- glColorData = new Uint32Array(vertexData);
4426
4446
  glBatchCount = 0;
4427
4447
  }
4428
4448
 
@@ -4450,7 +4470,7 @@ function glPreRender()
4450
4470
  glContext.vertexAttribPointer(location, size, type, normalize, gl_VERTEX_BYTE_STRIDE, offset);
4451
4471
  offset += size*typeSize;
4452
4472
  }
4453
- initVertexAttribArray('p', gl_FLOAT, 4, 4); // position & texture coords
4473
+ initVertexAttribArray('p', gl_FLOAT, 4, 4); // position & texture
4454
4474
  initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4, 1); // color
4455
4475
  initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
4456
4476
 
@@ -4520,7 +4540,7 @@ function glCreateProgram(vsSource, fsSource)
4520
4540
  return program;
4521
4541
  }
4522
4542
 
4523
- /** Create WebGL texture from an image and set the texture settings
4543
+ /** Create WebGL texture from an image and init the texture settings
4524
4544
  * @param {Image} image
4525
4545
  * @return {WebGLTexture}
4526
4546
  * @memberof WebGL */
@@ -4529,7 +4549,8 @@ function glCreateTexture(image)
4529
4549
  // build the texture
4530
4550
  const texture = glContext.createTexture();
4531
4551
  glContext.bindTexture(gl_TEXTURE_2D, texture);
4532
- image && image.width && glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
4552
+ if (image)
4553
+ glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
4533
4554
 
4534
4555
  // use point filtering for pixelated rendering
4535
4556
  const filter = canvasPixelated ? gl_NEAREST : gl_LINEAR;
@@ -4537,6 +4558,7 @@ function glCreateTexture(image)
4537
4558
  glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, filter);
4538
4559
  glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE);
4539
4560
  glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE);
4561
+
4540
4562
  return texture;
4541
4563
  }
4542
4564
 
@@ -4551,8 +4573,7 @@ function glFlush()
4551
4573
  glContext.enable(gl_BLEND);
4552
4574
 
4553
4575
  // draw all the sprites in the batch and reset the buffer
4554
- glContext.bufferSubData(gl_ARRAY_BUFFER, 0,
4555
- glPositionData.subarray(0, glBatchCount * gl_INDICIES_PER_VERT));
4576
+ glContext.bufferSubData(gl_ARRAY_BUFFER, 0, glVertexData);
4556
4577
  glContext.drawArrays(gl_TRIANGLE_STRIP, 0, glBatchCount);
4557
4578
  glBatchCount = 0;
4558
4579
  glBatchAdditive = glAdditive;
@@ -4607,11 +4628,11 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdd
4607
4628
  // setup 2 triangle strip quad
4608
4629
  for(let i = vertCount, offset = glBatchCount * gl_INDICIES_PER_VERT; i--;)
4609
4630
  {
4610
- let j = clamp(i-1, 0, 3)*4; // degenerate tri at ends
4611
- glPositionData[offset++] = positionData[j++];
4612
- glPositionData[offset++] = positionData[j++];
4613
- glPositionData[offset++] = positionData[j++];
4614
- glPositionData[offset++] = positionData[j++];
4631
+ const j = clamp(i-1, 0, 3)*4; // degenerate tri at ends
4632
+ glPositionData[offset++] = positionData[j+0];
4633
+ glPositionData[offset++] = positionData[j+1];
4634
+ glPositionData[offset++] = positionData[j+2];
4635
+ glPositionData[offset++] = positionData[j+3];
4615
4636
  glColorData[offset++] = rgba;
4616
4637
  glColorData[offset++] = rgbaAdditive;
4617
4638
  }
@@ -4812,7 +4833,7 @@ const engineName = 'LittleJS';
4812
4833
  * @type {String}
4813
4834
  * @default
4814
4835
  * @memberof Engine */
4815
- const engineVersion = '1.8.5';
4836
+ const engineVersion = '1.8.8';
4816
4837
 
4817
4838
  /** Frames per second to update objects
4818
4839
  * @type {Number}
@@ -4995,7 +5016,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4995
5016
  }
4996
5017
 
4997
5018
  // setup html
4998
- const styleBody =
5019
+ const styleBody =
4999
5020
  'margin:0;overflow:hidden;' + // fill the window
5000
5021
  'background:#000;' + // set background color
5001
5022
  'touch-action:none;' + // prevent mobile pinch to resize
@@ -5016,14 +5037,13 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
5016
5037
 
5017
5038
  // set canvas style
5018
5039
  const styleCanvas =
5019
- 'position:absolute;' + // position
5020
- 'top:50%;left:50%;transform:translate(-50%,-50%);' + // center
5021
- (canvasPixelated?'image-rendering:pixelated':''); // pixelated rendering
5040
+ 'position:absolute;' + // position
5041
+ 'top:50%;left:50%;transform:translate(-50%,-50%)'; // center
5022
5042
  (glCanvas||mainCanvas).style = mainCanvas.style = overlayCanvas.style = styleCanvas;
5023
5043
 
5024
- // load all of the images
5025
- Promise.all(imageSources.map((src, textureIndex)=>
5026
- new Promise((resolve, reject)=>
5044
+ // create promises for loading images
5045
+ const promises = imageSources.map((src, textureIndex)=>
5046
+ new Promise(resolve =>
5027
5047
  {
5028
5048
  const image = new Image;
5029
5049
  image.onerror = image.onload = ()=>
@@ -5033,7 +5053,24 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
5033
5053
  }
5034
5054
  image.src = src;
5035
5055
  })
5036
- )).then(()=>
5056
+ )
5057
+
5058
+ // draw splash screen
5059
+ showSplashScreen && promises.push(new Promise(resolve =>
5060
+ {
5061
+ let t = 0;
5062
+ console.log(`LittleJS Engine v${engineVersion}`);
5063
+ updateSplash();
5064
+ function updateSplash()
5065
+ {
5066
+ clearInput();
5067
+ drawEngineSplashScreen(t+=.01);
5068
+ t>1 ? resolve() : setTimeout(updateSplash,16);
5069
+ }
5070
+ }));
5071
+
5072
+ // load all of the images
5073
+ Promise.all(promises).then(()=>
5037
5074
  {
5038
5075
  // start the engine
5039
5076
  gameInit();
@@ -5111,6 +5148,167 @@ function engineObjectsCallback(pos, size, callbackFunction, objects=engineObject
5111
5148
  for (const o of objects)
5112
5149
  pos.distanceSquared(o.pos) < sizeSquared && callbackFunction(o);
5113
5150
  }
5151
+ }
5152
+
5153
+ ///////////////////////////////////////////////////////////////////////////////
5154
+ // LittleJS splash screen and logo
5155
+
5156
+ function drawEngineSplashScreen(t)
5157
+ {
5158
+ // background
5159
+ const grayscale = 0;
5160
+ const x = mainContext;
5161
+ const w = mainCanvas.width = innerWidth;
5162
+ const h = mainCanvas.height = innerHeight;
5163
+ const p3 = percent(t, 1, .8);
5164
+ const p4 = percent(t, 0, .5);
5165
+ const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,Math.hypot(w,h)*.7);
5166
+ g.addColorStop(0,hsl(0,0,lerp(p4,0,p3/2),p3));
5167
+ g.addColorStop(1,hsl(0,0,0,p3));
5168
+ x.save();
5169
+ x.fillStyle = g;
5170
+ x.fillRect(0,0,w,h);
5171
+
5172
+ // logo - fade in and out
5173
+ const rect = (X, Y, W, H, C)=>
5174
+ {
5175
+ x.beginPath();
5176
+ x.rect(X,Y,W,C?H*p:H);
5177
+ x.fillStyle = C;
5178
+ C ? x.fill() : x.stroke();
5179
+ };
5180
+ const line = (X, Y, Z, W)=>
5181
+ {
5182
+ x.beginPath();
5183
+ x.lineTo(X,Y);
5184
+ x.lineTo(Z,W);
5185
+ x.stroke();
5186
+ };
5187
+ const circle = (X, Y, R, A=0, B=2*PI, C, F)=>
5188
+ {
5189
+ const D = (A+B)/2, E = p*(B-A)/2;
5190
+ x.beginPath();
5191
+ F && x.lineTo(X,Y);
5192
+ x.arc(X,Y,R,D-E,D+E);
5193
+ x.fillStyle = C;
5194
+ C ? x.fill() : x.stroke();
5195
+ };
5196
+ const color = (c=0, l=0) =>
5197
+ hsl([.98,.3,.57,.14][c%4]-10,grayscale?0:.8,[0,.3,.5,.8,.9][l]);
5198
+ const alpha = wave(1,1,t);
5199
+ const p = percent(alpha, .1, .5);
5200
+
5201
+ // setup
5202
+ x.translate(w/2,h/2);
5203
+ const size = min(6, min(w,h)/99); // fit to screen
5204
+ x.scale(size,size);
5205
+ x.translate(-40,-35);
5206
+ x.lineJoin = x.lineCap = 'round';
5207
+ x.lineWidth = 1+p;
5208
+
5209
+ // drawing effect
5210
+ const p2 = percent(alpha,.1,1);
5211
+ x.setLineDash([99*p2,99]);
5212
+
5213
+ // cab top
5214
+ rect(7,17,18,-8,color(2,2));
5215
+ rect(7,9,18,4,color(2,3));
5216
+ rect(25,9,8,8,color(2,1));
5217
+ rect(25,9,-18,8);
5218
+ rect(25,9,8,8);
5219
+
5220
+ // cab
5221
+ rect(25,17,7,22,color());
5222
+ rect(11,40,14,-23,color(1,1));
5223
+ rect(11,17,14,17,color(1,2));
5224
+ rect(11,17,14,9,color(1,3));
5225
+ rect(15,31,6,-9,color(2,2));
5226
+ circle(15,23,5,0,PI/2,color(2,4),1);
5227
+ rect(25,17,-14,23);
5228
+ rect(21,22,-6,9);
5229
+
5230
+ // little stack
5231
+ rect(37,14,9,6,color(3,2));
5232
+ rect(37,14,4,6,color(3,3));
5233
+ rect(37,14,9,6);
5234
+
5235
+ // big stack
5236
+ rect(50,20,10,-10,color(0,1));
5237
+ rect(50,20,6,-10,color(0,2));
5238
+ rect(50,20,3,-10,color(0,3));
5239
+ rect(50,10,10,10);
5240
+ circle(55,2,11,.5,PI-.5,color(3,3));
5241
+ circle(55,2,11,.5,PI/2,color(3,2),1);
5242
+ circle(55,2,11,.5,PI-.5);
5243
+ rect(45,7,20,-7,color(0,2));
5244
+ rect(45,0,20,3,color(0,3));
5245
+ rect(45,0,20,7);
5246
+
5247
+ // engine
5248
+ for (let i=5; i--;)
5249
+ {
5250
+ // stagger radius to fix slight seam
5251
+ circle(60-i*6,30, 9.9,0,2*PI,color(i+2,3));
5252
+ circle(60-i*6,30,10.0,-.5,PI+.5,color(i+2,2));
5253
+ circle(60-i*6,30,10.1,.5,PI-.5,color(i+2,1));
5254
+ }
5255
+
5256
+ // engine outline
5257
+ circle(36,30,10,PI/2,PI*3/2);
5258
+ circle(47,30,10,PI/2,PI*3/2);
5259
+ circle(60,30,10);
5260
+ line(36,20,60,20);
5261
+
5262
+ // engine front light
5263
+ circle(60,30,4,PI,3*PI,color(3,2));
5264
+ circle(60,30,4,PI,2*PI,color(3,3));
5265
+ circle(60,30,4,PI,3*PI);
5266
+
5267
+ // front brush
5268
+ for (let i=6; i--;)
5269
+ {
5270
+ x.beginPath();
5271
+ x.lineTo(53,54);
5272
+ x.lineTo(53,40);
5273
+ x.lineTo(53+(1+i*2.9)*p,40);
5274
+ x.lineTo(53+(4+i*3.5)*p,54);
5275
+ x.fillStyle = color(0,i%2+2);
5276
+ x.fill() || i%2 && x.stroke();
5277
+ }
5278
+
5279
+ // wheels
5280
+ rect(5,40,9,6,color());
5281
+ rect(15,54,38,-14,color())
5282
+ for (let i=3; i--;)
5283
+ for (let j=2; j--;)
5284
+ {
5285
+ circle(15*i+15,47,j?7:1,PI,3*PI,color(i,3));
5286
+ x.stroke();
5287
+ circle(15*i+15,47,j?7:1,0,PI,color(i,2));
5288
+ x.stroke();
5289
+ }
5290
+ line(6,40,68,40) // center
5291
+ line(77,54,4,54) // bottom
5292
+
5293
+ // text
5294
+ const s = engineName;
5295
+ x.font = '900 16px arial';
5296
+ x.textAlign = 'center';
5297
+ x.textBaseline = 'top';
5298
+ x.lineWidth = 1+p*3
5299
+ let w2 = 0;
5300
+ for (let i=0; i<s.length; ++i)
5301
+ w2 += x.measureText(s[i]).width;
5302
+ for (let j=2; j--;)
5303
+ for (let i=0, X=41-w2/2; i<s.length; ++i)
5304
+ {
5305
+ x.fillStyle = color(i,grayscale?3:2);
5306
+ const w = x.measureText(s[i]).width;
5307
+ x[j?'strokeText':'fillText'](s[i],X+w/2,55.5,17*p);
5308
+ X += w;
5309
+ }
5310
+
5311
+ x.restore();
5114
5312
  }
5115
5313
 
5116
5314
  /**
@@ -5158,6 +5356,7 @@ export {
5158
5356
  canvasFixedSize,
5159
5357
  canvasPixelated,
5160
5358
  fontDefault,
5359
+ showSplashScreen,
5161
5360
  tileSizeDefault,
5162
5361
  tileFixBleedScale,
5163
5362
  enablePhysicsSolver,
@@ -5195,6 +5394,7 @@ export {
5195
5394
  setCanvasFixedSize,
5196
5395
  setCanvasPixelated,
5197
5396
  setFontDefault,
5397
+ setShowSplashScreen,
5198
5398
  setGlEnable,
5199
5399
  setGlOverlay,
5200
5400
  setTileSizeDefault,