littlejsengine 1.9.9 → 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 (59) hide show
  1. package/README.md +6 -0
  2. package/dist/littlejs.d.ts +107 -14
  3. package/dist/littlejs.esm.js +250 -102
  4. package/dist/littlejs.esm.min.js +1 -1
  5. package/dist/littlejs.js +226 -98
  6. package/dist/littlejs.min.js +1 -1
  7. package/dist/littlejs.release.js +208 -95
  8. package/examples/box2d/game.js +29 -10
  9. package/examples/box2d/gameObjects.js +485 -0
  10. package/examples/box2d/index.html +7 -5
  11. package/examples/box2d/scenes.js +28 -247
  12. package/examples/box2d/tiles.png +0 -0
  13. package/examples/breakout/game.js +1 -1
  14. package/examples/breakout/index.html +5 -4
  15. package/examples/breakoutTutorial/index.html +4 -3
  16. package/examples/electron/game.js +1 -1
  17. package/examples/electron/index.html +4 -3
  18. package/examples/empty/game.js +1 -1
  19. package/examples/empty/index.html +2 -1
  20. package/examples/htmlMenu/game.js +67 -0
  21. package/examples/htmlMenu/index.html +56 -0
  22. package/examples/htmlMenu/tiles.png +0 -0
  23. package/examples/index.html +33 -0
  24. package/examples/js13k/game.js +1 -1
  25. package/examples/js13k/index.html +17 -14
  26. package/examples/module/game.js +1 -1
  27. package/examples/module/index.html +3 -2
  28. package/examples/particles/index.html +4 -3
  29. package/examples/platformer/game.js +1 -1
  30. package/examples/platformer/gameCharacter.js +1 -0
  31. package/examples/platformer/gameEffects.js +0 -2
  32. package/examples/platformer/gameLevel.js +2 -2
  33. package/examples/platformer/gameObjects.js +1 -2
  34. package/examples/platformer/index.html +10 -9
  35. package/examples/puzzle/game.js +1 -1
  36. package/examples/puzzle/index.html +4 -3
  37. package/examples/starter/game.js +5 -5
  38. package/examples/starter/index.html +15 -14
  39. package/examples/stress/index.html +3 -2
  40. package/examples/typescript/game.js +1 -1
  41. package/examples/typescript/index.html +3 -2
  42. package/examples/uiSystem/game.js +134 -0
  43. package/examples/uiSystem/index.html +25 -0
  44. package/examples/uiSystem/tiles.png +0 -0
  45. package/jsconfig.json +11 -0
  46. package/package.json +1 -1
  47. package/plugins/box2d.js +116 -42
  48. package/plugins/postProcess.js +6 -7
  49. package/plugins/uiSystem.js +243 -0
  50. package/src/engine.js +48 -20
  51. package/src/engineAudio.js +22 -25
  52. package/src/engineDebug.js +18 -3
  53. package/src/engineDraw.js +101 -27
  54. package/src/engineExport.js +23 -4
  55. package/src/engineInput.js +9 -5
  56. package/src/engineObject.js +15 -12
  57. package/src/engineSettings.js +1 -1
  58. package/src/engineUtilities.js +1 -1
  59. package/src/engineWebGL.js +11 -4
package/src/engine.js CHANGED
@@ -30,7 +30,7 @@ const engineName = 'LittleJS';
30
30
  * @type {String}
31
31
  * @default
32
32
  * @memberof Engine */
33
- const engineVersion = '1.9.9';
33
+ const engineVersion = '1.10.2';
34
34
 
35
35
  /** Frames per second to update
36
36
  * @type {Number}
@@ -75,6 +75,12 @@ let timeReal = 0;
75
75
  * @memberof Engine */
76
76
  let paused = false;
77
77
 
78
+ /** The root element that engine is attached to
79
+ * @type {HTMLElement}
80
+ * @default document.body
81
+ * @memberof Engine */
82
+ let engineRoot;
83
+
78
84
  /** Set if game is paused
79
85
  * @param {Boolean} isPaused
80
86
  * @memberof Engine */
@@ -108,8 +114,9 @@ function engineAddPlugin(updateFunction, renderFunction)
108
114
  * @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects
109
115
  * @param {Function} gameRenderPost - Called after objects are rendered, draw effects or hud that appear above all objects
110
116
  * @param {Array} [imageSources=['tiles.png']] - Image to load
117
+ * @param {HTMLElement} [rootElement] - Root element to attach to, the document body by default
111
118
  * @memberof Engine */
112
- function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=['tiles.png'])
119
+ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
113
120
  {
114
121
  ASSERT(Array.isArray(imageSources), 'pass in images as array');
115
122
 
@@ -151,6 +158,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
151
158
  for (const o of engineObjects)
152
159
  o.parent || o.updateTransforms();
153
160
  inputUpdate();
161
+ pluginUpdateList.forEach(f=>f());
154
162
  debugUpdate();
155
163
  gameUpdatePost();
156
164
  inputUpdatePost();
@@ -266,16 +274,22 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
266
274
  }
267
275
 
268
276
  // setup html
269
- const styleBody =
277
+ const styleRoot =
270
278
  'margin:0;overflow:hidden;' + // fill the window
279
+ 'width:100vw;height:100vh;' + // fill the window
280
+ 'display:flex;' + // use flexbox
281
+ 'align-items:center;' + // horizontal center
282
+ (canvasPixelated ? 'image-rendering:pixelated;' : '') + // pixel art
283
+ 'justify-content:center;' + // vertical center
271
284
  'background:#000;' + // set background color
272
285
  'user-select:none;' + // prevent hold to select
273
286
  '-webkit-user-select:none;' + // compatibility for ios
274
287
  (!touchInputEnable ? '' : // no touch css setttings
275
288
  'touch-action:none;' + // prevent mobile pinch to resize
276
289
  '-webkit-touch-callout:none');// compatibility for ios
277
- document.body.style.cssText = styleBody;
278
- document.body.appendChild(mainCanvas = document.createElement('canvas'));
290
+ engineRoot = rootElement;
291
+ engineRoot.style.cssText = styleRoot;
292
+ engineRoot.appendChild(mainCanvas = document.createElement('canvas'));
279
293
  mainContext = mainCanvas.getContext('2d');
280
294
 
281
295
  // init stuff and start engine
@@ -285,13 +299,14 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
285
299
  glInit();
286
300
 
287
301
  // create overlay canvas for hud to appear above gl canvas
288
- document.body.appendChild(overlayCanvas = document.createElement('canvas'));
302
+ engineRoot.appendChild(overlayCanvas = document.createElement('canvas'));
289
303
  overlayContext = overlayCanvas.getContext('2d');
290
304
 
291
305
  // set canvas style
292
- const styleCanvas = 'position:absolute;' + // position
293
- 'top:50%;left:50%;transform:translate(-50%,-50%)'; // center
294
- (glCanvas||mainCanvas).style.cssText = mainCanvas.style.cssText = overlayCanvas.style.cssText = styleCanvas;
306
+ const styleCanvas = 'position:absolute'; // allow canvases to overlap
307
+ mainCanvas.style.cssText = overlayCanvas.style.cssText = styleCanvas;
308
+ if (glCanvas)
309
+ glCanvas.style.cssText = styleCanvas;
295
310
  updateCanvas();
296
311
 
297
312
  // create promises for loading images
@@ -308,19 +323,32 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
308
323
  })
309
324
  );
310
325
 
311
- // draw splash screen
312
- showSplashScreen && promises.push(new Promise(resolve =>
326
+ if (!imageSources.length)
313
327
  {
314
- let t = 0;
315
- console.log(`${engineName} Engine v${engineVersion}`);
316
- updateSplash();
317
- function updateSplash()
328
+ // no images to load
329
+ promises.push(new Promise(resolve =>
318
330
  {
319
- clearInput();
320
- drawEngineSplashScreen(t+=.01);
321
- t>1 ? resolve() : setTimeout(updateSplash, 16);
322
- }
323
- }));
331
+ textureInfos[0] = new TextureInfo(new Image);
332
+ resolve();
333
+ }));
334
+ }
335
+
336
+ if (showSplashScreen)
337
+ {
338
+ // draw splash screen
339
+ promises.push(new Promise(resolve =>
340
+ {
341
+ let t = 0;
342
+ console.log(`${engineName} Engine v${engineVersion}`);
343
+ updateSplash();
344
+ function updateSplash()
345
+ {
346
+ clearInput();
347
+ drawEngineSplashScreen(t+=.01);
348
+ t>1 ? resolve() : setTimeout(updateSplash, 16);
349
+ }
350
+ }));
351
+ }
324
352
 
325
353
  // load all of the images
326
354
  Promise.all(promises).then(startEngine);
@@ -73,6 +73,7 @@ class Sound
73
73
  // generate zzfx sound now for fast playback
74
74
  const defaultRandomness = .05;
75
75
  this.randomness = zzfxSound[1] || defaultRandomness;
76
+ zzfxSound[1] = 0; // generate without randomness
76
77
  this.sampleChannels = [zzfxG(...zzfxSound)];
77
78
  this.sampleRate = zzfxR;
78
79
  }
@@ -305,10 +306,6 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
305
306
 
306
307
  ///////////////////////////////////////////////////////////////////////////////
307
308
 
308
- // internal tracking if audio was suspended when last sound was played
309
- // allows first suspended sound to play when audio is resumed
310
- let audioSuspended = false;
311
-
312
309
  /** Play cached audio samples with given settings
313
310
  * @param {Array} sampleChannels - Array of arrays of samples to play (for stereo playback)
314
311
  * @param {Number} [volume] - How much to scale volume by
@@ -323,21 +320,11 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
323
320
  {
324
321
  if (!soundEnable || headlessMode) return;
325
322
 
326
- // prevent sounds from building up if they can't be played
327
- const audioWasSuspended = audioSuspended;
328
- if (audioSuspended = audioContext.state != 'running')
329
- {
330
- // fix stalled audio
331
- audioContext.resume();
332
-
333
- // prevent suspended sounds from building up
334
- if (audioWasSuspended)
335
- return;
336
- }
337
-
338
323
  // create buffer and source
339
- const buffer = audioContext.createBuffer(sampleChannels.length, sampleChannels[0].length, sampleRate),
340
- source = audioContext.createBufferSource();
324
+ const channelCount = sampleChannels.length;
325
+ const sampleLength = sampleChannels[0].length;
326
+ const buffer = audioContext.createBuffer(channelCount, sampleLength, sampleRate);
327
+ const source = audioContext.createBufferSource();
341
328
 
342
329
  // copy samples to buffer and setup source
343
330
  sampleChannels.forEach((c,i)=> buffer.getChannelData(i).set(c));
@@ -351,10 +338,19 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
351
338
  gainNode.connect(audioGainNode);
352
339
 
353
340
  // connect source to stereo panner and gain
354
- source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)})).connect(gainNode);
341
+ const pannerNode = new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)});
342
+ source.connect(pannerNode).connect(gainNode);
343
+
344
+ // play the sound
345
+ if (audioContext.state != 'running')
346
+ {
347
+ // fix stalled audio and play
348
+ audioContext.resume().then(()=>source.start());
349
+ }
350
+ else
351
+ source.start();
355
352
 
356
- // play and return sound
357
- source.start();
353
+ // return sound
358
354
  return source;
359
355
  }
360
356
 
@@ -367,7 +363,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
367
363
  * @param {Array} zzfxSound - Array of ZzFX parameters, ex. [.5,.5]
368
364
  * @return {AudioBufferSourceNode} - The audio node of the sound played
369
365
  * @memberof Audio */
370
- function zzfx(...zzfxSound) { return new Sound(zzfxSound).play(); }
366
+ function zzfx(...zzfxSound) { return playSamples([zzfxG(...zzfxSound)]); }
371
367
 
372
368
  /** Sample rate used for all ZzFX sounds
373
369
  * @default 44100
@@ -376,7 +372,7 @@ const zzfxR = 44100;
376
372
 
377
373
  /** Generate samples for a ZzFX sound
378
374
  * @param {Number} [volume] - Volume scale (percent)
379
- * @param {Number} [randomness] - Unused in this fuction, handled by Sound class
375
+ * @param {Number} [randomness] - How much to randomize frequency (percent Hz)
380
376
  * @param {Number} [frequency] - Frequency of sound (Hz)
381
377
  * @param {Number} [attack] - Attack time, how fast sound starts (seconds)
382
378
  * @param {Number} [sustain] - Sustain time, how long sound holds (seconds)
@@ -402,7 +398,7 @@ const zzfxR = 44100;
402
398
  function zzfxG
403
399
  (
404
400
  // parameters
405
- volume = 1, randomness = 0, frequency = 220, attack = 0, sustain = 0,
401
+ volume = 1, randomness = .05, frequency = 220, attack = 0, sustain = 0,
406
402
  release = .1, shape = 0, shapeCurve = 1, slide = 0, deltaSlide = 0,
407
403
  pitchJump = 0, pitchJumpTime = 0, repeatTime = 0, noise = 0, modulation = 0,
408
404
  bitCrush = 0, delay = 0, sustainVolume = 1, decay = 0, tremolo = 0, filter = 0
@@ -413,7 +409,8 @@ function zzfxG
413
409
  // init parameters
414
410
  let PI2 = PI*2, sampleRate = zzfxR,
415
411
  startSlide = slide *= 500 * PI2 / sampleRate / sampleRate,
416
- startFrequency = frequency *= PI2 / sampleRate,
412
+ startFrequency = frequency *=
413
+ rand(1 + randomness, 1-randomness) * PI2 / sampleRate,
417
414
  b = [], t = 0, tm = 0, i = 0, j = 1, r = 0, c = 0, s = 0, f, length,
418
415
 
419
416
  // biquad LP/HP filter
@@ -124,7 +124,6 @@ function debugPoint(pos, color, time, angle)
124
124
  * @memberof Debug */
125
125
  function debugLine(posA, posB, color, thickness=.1, time)
126
126
  {
127
- ASSERT(typeof color == 'string', 'pass in css color strings');
128
127
  const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
129
128
  const size = vec2(thickness, halfDelta.length()*2);
130
129
  debugRect(posA.add(halfDelta), size, color, time, halfDelta.angle(), true);
@@ -190,8 +189,24 @@ function debugSaveDataURL(dataURL, filename)
190
189
  downloadLink.click();
191
190
  }
192
191
 
192
+ /** Show error as full page of red text
193
+ * @memberof Debug */
194
+ function debugShowErrors()
195
+ {
196
+ onunhandledrejection = (event)=>showError(event.reason);
197
+ onerror = (event, source, lineno, colno)=>
198
+ showError(`${event}\n${source}\nLn ${lineno}, Col ${colno}`);
199
+
200
+ const showError = (message)=>
201
+ {
202
+ // replace entire page with error message
203
+ document.body.style.backgroundColor = '#111';
204
+ document.body.innerHTML = `<pre style=color:#f00;font-size:50px>` + message;
205
+ }
206
+ }
207
+
193
208
  ///////////////////////////////////////////////////////////////////////////////
194
- // Engine debug function (called automatically)
209
+ // Engine debug functions (called automatically)
195
210
 
196
211
  function debugInit()
197
212
  {
@@ -322,7 +337,7 @@ function debugRender()
322
337
  const pos = worldToScreen(p.pos);
323
338
  overlayContext.translate(pos.x|0, pos.y|0);
324
339
  overlayContext.rotate(p.angle);
325
- overlayContext.scale(1, -1);
340
+ overlayContext.scale(1, p.text ? 1 : -1);
326
341
  overlayContext.fillStyle = overlayContext.strokeStyle = p.color;
327
342
 
328
343
  if (p.text != undefined)
package/src/engineDraw.js CHANGED
@@ -58,12 +58,13 @@ let drawCount;
58
58
  ///////////////////////////////////////////////////////////////////////////////
59
59
 
60
60
  /**
61
- * Create a tile info object
61
+ * Create a tile info object using a grid based system
62
62
  * - This can take vecs or floats for easier use and conversion
63
63
  * - If an index is passed in, the tile size and index will determine the position
64
- * @param {(Number|Vector2)} [pos=(0,0)] - Top left corner of tile in pixels or index
64
+ * @param {(Number|Vector2)} [pos=0] - Index of tile in sheet
65
65
  * @param {(Number|Vector2)} [size=tileSizeDefault] - Size of tile in pixels
66
66
  * @param {Number} [textureIndex] - Texture index to use
67
+ * @param {Number} [padding] - How many pixels padding around tiles
67
68
  * @return {TileInfo}
68
69
  * @example
69
70
  * tile(2) // a tile at index 2 using the default tile size of 16
@@ -72,7 +73,7 @@ let drawCount;
72
73
  * tile(vec2(4,8), vec2(30,10)) // a tile at pixel location (4,8) with a size of (30,10)
73
74
  * @memberof Draw
74
75
  */
75
- function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0)
76
+ function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0, padding=0)
76
77
  {
77
78
  if (headlessMode)
78
79
  return new TileInfo;
@@ -84,17 +85,17 @@ function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0)
84
85
  size = vec2(size);
85
86
  }
86
87
 
87
- // if pos is a number, use it as a tile index
88
+ // use pos as a tile index
89
+ const textureInfo = textureInfos[textureIndex];
90
+ ASSERT(textureInfo, 'Texture not loaded');
91
+ const sizePadded = size.add(vec2(padding*2));
92
+ const cols = textureInfo.size.x / sizePadded.x |0;
88
93
  if (typeof pos === 'number')
89
- {
90
- const textureInfo = textureInfos[textureIndex];
91
- ASSERT(textureInfo, 'Texture not loaded');
92
- const cols = textureInfo.size.x / size.x |0;
93
- pos = vec2((pos%cols)*size.x, (pos/cols|0)*size.y);
94
- }
94
+ pos = vec2(pos%cols, pos/cols|0);
95
+ pos = vec2(pos.x*sizePadded.x+padding, pos.y*sizePadded.y+padding);
95
96
 
96
97
  // return a tile info object
97
- return new TileInfo(pos, size, textureIndex);
98
+ return new TileInfo(pos, size, textureIndex, padding);
98
99
  }
99
100
 
100
101
  /**
@@ -106,8 +107,9 @@ class TileInfo
106
107
  * @param {Vector2} [pos=(0,0)] - Top left corner of tile in pixels
107
108
  * @param {Vector2} [size=tileSizeDefault] - Size of tile in pixels
108
109
  * @param {Number} [textureIndex] - Texture index to use
110
+ * @param {Number} [padding] - How many pixels padding around tiles
109
111
  */
110
- constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0)
112
+ constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0, padding=0)
111
113
  {
112
114
  /** @property {Vector2} - Top left corner of tile in pixels */
113
115
  this.pos = pos.copy();
@@ -115,6 +117,8 @@ class TileInfo
115
117
  this.size = size.copy();
116
118
  /** @property {Number} - Texture index to use */
117
119
  this.textureIndex = textureIndex;
120
+ /** @property {Number} - How many pixels padding around tiles */
121
+ this.padding = padding;
118
122
  }
119
123
 
120
124
  /** Returns a copy of this tile offset by a vector
@@ -131,7 +135,7 @@ class TileInfo
131
135
  frame(frame)
132
136
  {
133
137
  ASSERT(typeof frame == 'number');
134
- return this.offset(vec2(frame*this.size.x, 0));
138
+ return this.offset(vec2(frame*(this.size.x+this.padding*2), 0));
135
139
  }
136
140
 
137
141
  /** Returns the texture info for this tile
@@ -156,8 +160,6 @@ class TextureInfo
156
160
  this.size = vec2(image.width, image.height);
157
161
  /** @property {WebGLTexture} - webgl texture */
158
162
  this.glTexture = glEnable && glCreateTexture(image);
159
- /** @property {Vector2} - size to adjust tile to fix bleeding */
160
- this.fixBleedSize = vec2(tileFixBleedScale).divide(this.size);
161
163
  }
162
164
  }
163
165
 
@@ -226,11 +228,12 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
226
228
  if (textureInfo)
227
229
  {
228
230
  // calculate uvs and render
229
- const x = tileInfo.pos.x / textureInfo.size.x;
230
- const y = tileInfo.pos.y / textureInfo.size.y;
231
- const w = tileInfo.size.x / textureInfo.size.x;
232
- const h = tileInfo.size.y / textureInfo.size.y;
233
- const tileImageFixBleed = textureInfo.fixBleedSize;
231
+ const sizeInverse = vec2(1).divide(textureInfo.size);
232
+ const x = tileInfo.pos.x * sizeInverse.x;
233
+ const y = tileInfo.pos.y * sizeInverse.y;
234
+ const w = tileInfo.size.x * sizeInverse.x;
235
+ const h = tileInfo.size.y * sizeInverse.y;
236
+ const tileImageFixBleed = sizeInverse.scale(tileFixBleedScale);
234
237
  glSetTexture(textureInfo.glTexture);
235
238
  glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
236
239
  x + tileImageFixBleed.x, y + tileImageFixBleed.y,
@@ -247,6 +250,7 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
247
250
  {
248
251
  // normal canvas 2D rendering method (slower)
249
252
  showWatermark && ++drawCount;
253
+ size = vec2(size.x, -size.y); // fix upside down sprites
250
254
  drawCanvas2D(pos, size, angle, mirror, (context)=>
251
255
  {
252
256
  if (textureInfo)
@@ -284,6 +288,74 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
284
288
  drawTile(pos, size, undefined, color, angle, false, undefined, useWebGL, screenSpace, context);
285
289
  }
286
290
 
291
+ /** Draw colored polygon using passed in points
292
+ * @param {Array} points - Array of Vector2 points
293
+ * @param {Color} [color=(1,1,1,1)]
294
+ * @param {Number} [lineWidth=0]
295
+ * @param {Color} [lineColor=(0,0,0,1)]
296
+ * @param {Boolean} [screenSpace=false]
297
+ * @param {CanvasRenderingContext2D} [context=mainContext]
298
+ * @memberof Draw */
299
+ function drawPoly(points, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=mainContext)
300
+ {
301
+ context.fillStyle = color.toString();
302
+ context.beginPath();
303
+ for (const point of screenSpace ? points : points.map(worldToScreen))
304
+ context.lineTo(point.x, point.y);
305
+ context.closePath();
306
+ context.fill();
307
+ if (lineWidth)
308
+ {
309
+ context.strokeStyle = lineColor.toString();
310
+ context.lineWidth = screenSpace ? lineWidth : lineWidth*cameraScale;
311
+ context.stroke();
312
+ }
313
+ }
314
+
315
+ /** Draw colored ellipse using passed in point
316
+ * @param {Vector2} pos
317
+ * @param {Number} [width=1]
318
+ * @param {Number} [height=1]
319
+ * @param {Number} [angle=0]
320
+ * @param {Color} [color=(1,1,1,1)]
321
+ * @param {Number} [lineWidth=0]
322
+ * @param {Color} [lineColor=(0,0,0,1)]
323
+ * @param {Boolean} [screenSpace=false]
324
+ * @param {CanvasRenderingContext2D} [context=mainContext]
325
+ * @memberof Draw */
326
+ function drawEllipse(pos, width=1, height=1, angle=0, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=mainContext)
327
+ {
328
+ if (!screenSpace)
329
+ {
330
+ pos = worldToScreen(pos);
331
+ width *= cameraScale;
332
+ height *= cameraScale;
333
+ lineWidth *= cameraScale;
334
+ }
335
+ context.fillStyle = color.toString();
336
+ context.beginPath();
337
+ context.ellipse(pos.x, pos.y, width, height, angle, 0, 9);
338
+ context.fill();
339
+ if (lineWidth)
340
+ {
341
+ context.strokeStyle = lineColor.toString();
342
+ context.lineWidth = lineWidth;
343
+ context.stroke();
344
+ }
345
+ }
346
+
347
+ /** Draw colored circle using passed in point
348
+ * @param {Vector2} pos
349
+ * @param {Number} [radius=1]
350
+ * @param {Color} [color=(1,1,1,1)]
351
+ * @param {Number} [lineWidth=0]
352
+ * @param {Color} [lineColor=(0,0,0,1)]
353
+ * @param {Boolean} [screenSpace=false]
354
+ * @param {CanvasRenderingContext2D} [context=mainContext]
355
+ * @memberof Draw */
356
+ function drawCircle(pos, radius=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=mainContext)
357
+ { drawEllipse(pos, radius, radius, 0, color, lineWidth, lineColor, screenSpace, context); }
358
+
287
359
  /** Draw colored line between two points
288
360
  * @param {Vector2} posA
289
361
  * @param {Vector2} posB
@@ -354,10 +426,11 @@ function setBlendMode(additive, useWebGL=glEnable, context)
354
426
  * @param {CanvasTextAlign} [textAlign='center']
355
427
  * @param {String} [font=fontDefault]
356
428
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
429
+ * @param {Number} [maxWidth]
357
430
  * @memberof Draw */
358
- function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, context)
431
+ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, context, maxWidth)
359
432
  {
360
- drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, context);
433
+ drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, context, maxWidth);
361
434
  }
362
435
 
363
436
  /** Draw text on overlay canvas in screen space
@@ -371,8 +444,9 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
371
444
  * @param {CanvasTextAlign} [textAlign]
372
445
  * @param {String} [font=fontDefault]
373
446
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
447
+ * @param {Number} [maxWidth]
374
448
  * @memberof Draw */
375
- function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, context=overlayContext)
449
+ 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)
376
450
  {
377
451
  context.fillStyle = color.toString();
378
452
  context.lineWidth = lineWidth;
@@ -385,8 +459,8 @@ function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineCol
385
459
  pos = pos.copy();
386
460
  (text+'').split('\n').forEach(line=>
387
461
  {
388
- lineWidth && context.strokeText(line, pos.x, pos.y);
389
- context.fillText(line, pos.x, pos.y);
462
+ lineWidth && context.strokeText(line, pos.x, pos.y, maxWidth);
463
+ context.fillText(line, pos.x, pos.y, maxWidth);
390
464
  pos.y += size;
391
465
  });
392
466
  }
@@ -494,6 +568,6 @@ function toggleFullscreen()
494
568
  if (document.exitFullscreen)
495
569
  document.exitFullscreen();
496
570
  }
497
- else if (document.body.requestFullscreen)
498
- document.body.requestFullscreen();
571
+ else if (engineRoot.requestFullscreen)
572
+ engineRoot.requestFullscreen();
499
573
  }
@@ -1,4 +1,4 @@
1
- /**
1
+ /**
2
2
  * LittleJS Module Export
3
3
  * - Export engine as a module
4
4
  */
@@ -20,8 +20,9 @@ export {
20
20
  engineObjectsUpdate,
21
21
  engineObjectsDestroy,
22
22
  engineObjectsCallback,
23
+ engineObjectsRaycast,
23
24
  engineAddPlugin,
24
-
25
+
25
26
  // Globals
26
27
  debug,
27
28
  debugOverlay,
@@ -140,6 +141,7 @@ export {
140
141
  smoothStep,
141
142
  nearestPowerOfTwo,
142
143
  isOverlapping,
144
+ isIntersecting,
143
145
  wave,
144
146
  formatTime,
145
147
 
@@ -159,6 +161,20 @@ export {
159
161
  vec2,
160
162
  rgb,
161
163
  hsl,
164
+ isColor,
165
+
166
+ // Default Colors
167
+ WHITE,
168
+ BLACK,
169
+ GRAY,
170
+ RED,
171
+ ORANGE,
172
+ YELLOW,
173
+ GREEN,
174
+ CYAN,
175
+ BLUE,
176
+ PURPLE,
177
+ MAGENTA,
162
178
 
163
179
  // Draw
164
180
  textureInfos,
@@ -188,10 +204,13 @@ export {
188
204
  // WebGL
189
205
  glCanvas,
190
206
  glContext,
191
- glSetTexture,
192
207
  glCompileShader,
208
+ glCopyToContext,
193
209
  glCreateProgram,
194
210
  glCreateTexture,
211
+ glDraw,
212
+ glFlush,
213
+ glSetTexture,
195
214
 
196
215
  // Input
197
216
  keyIsDown,
@@ -251,4 +270,4 @@ export {
251
270
  medalsPreventUnlock,
252
271
  medalsInit,
253
272
  Medal,
254
- };
273
+ };
@@ -167,7 +167,7 @@ function inputInit()
167
167
 
168
168
  onkeydown = (e)=>
169
169
  {
170
- if (debug && e.target != document.body) return;
170
+ if (debug && e.target != engineRoot) return;
171
171
  if (!e.repeat)
172
172
  {
173
173
  isUsingGamepad = false;
@@ -180,7 +180,7 @@ function inputInit()
180
180
 
181
181
  onkeyup = (e)=>
182
182
  {
183
- if (debug && e.target != document.body) return;
183
+ if (debug && e.target != engineRoot) return;
184
184
  inputData[0][e.code] = 4;
185
185
  if (inputWASDEmulateDirection)
186
186
  inputData[0][remapKey(e.code)] = 4;
@@ -199,6 +199,10 @@ function inputInit()
199
199
  // mouse event handlers
200
200
  onmousedown = (e)=>
201
201
  {
202
+ // fix stalled audio requiring user interaction
203
+ if (soundEnable && !headlessMode && audioContext && audioContext.state != 'running')
204
+ audioContext.resume();
205
+
202
206
  isUsingGamepad = false;
203
207
  inputData[0][e.button] = 3;
204
208
  mousePosScreen = mouseToScreen(e);
@@ -210,7 +214,7 @@ function inputInit()
210
214
  oncontextmenu = (e)=> false; // prevent right click menu
211
215
 
212
216
  // init touch input
213
- if (isTouchDevice && touchInputEnable && !headlessMode)
217
+ if (isTouchDevice && touchInputEnable)
214
218
  touchInputInit();
215
219
  }
216
220
 
@@ -367,8 +371,8 @@ function touchInputInit()
367
371
  function handleTouchDefault(e)
368
372
  {
369
373
  // fix stalled audio requiring user interaction
370
- if (soundEnable && audioContext && audioContext.state != 'running')
371
- zzfx(0);
374
+ if (soundEnable && !headlessMode && audioContext && audioContext.state != 'running')
375
+ audioContext.resume();
372
376
 
373
377
  // check if touching and pass to mouse events
374
378
  const touching = e.touches.length;
@@ -153,9 +153,9 @@ class EngineObject
153
153
 
154
154
  // apply physics
155
155
  const oldPos = this.pos.copy();
156
- this.velocity.y += gravity * this.gravityScale;
157
156
  this.pos.x += this.velocity.x *= this.damping;
158
- this.pos.y += this.velocity.y *= this.damping;
157
+ this.pos.y += this.velocity.y = this.damping * this.velocity.y
158
+ + gravity * this.gravityScale;
159
159
  this.angle += this.angleVelocity *= this.angleDamping;
160
160
 
161
161
  // physics sanity checks
@@ -284,19 +284,22 @@ class EngineObject
284
284
  const isBlockedX = tileCollisionTest(vec2(this.pos.x, oldPos.y), this.size, this);
285
285
  if (isBlockedY || !isBlockedX)
286
286
  {
287
- // set if landed on ground
288
- this.groundObject = wasMovingDown;
289
-
290
287
  // bounce velocity
291
288
  this.velocity.y *= -this.elasticity;
292
289
 
293
- // adjust next velocity to settle on ground
294
- const o = (oldPos.y - this.size.y/2|0) - (oldPos.y - this.size.y/2);
295
- if (o < 0 && o > this.damping * this.velocity.y + gravity * this.gravityScale)
296
- this.velocity.y = this.damping ? (o - gravity * this.gravityScale) / this.damping : 0;
297
-
298
- // move to previous position
299
- this.pos.y = oldPos.y;
290
+ // set if landed on ground
291
+ if (this.groundObject = wasMovingDown)
292
+ {
293
+ // adjust position to slightly above nearest tile boundary
294
+ // this prevents gap between object and ground
295
+ const epsilon = .0001;
296
+ this.pos.y = (oldPos.y-this.size.y/2|0)+this.size.y/2+epsilon;
297
+ }
298
+ else
299
+ {
300
+ // move to previous position
301
+ this.pos.y = oldPos.y;
302
+ }
300
303
  }
301
304
  if (isBlockedX)
302
305
  {
@@ -89,7 +89,7 @@ let tileSizeDefault = vec2(16);
89
89
  * @type {Number}
90
90
  * @default
91
91
  * @memberof Settings */
92
- let tileFixBleedScale = .5;
92
+ let tileFixBleedScale = 0;
93
93
 
94
94
  ///////////////////////////////////////////////////////////////////////////////
95
95
  // Object settings