littlejsengine 1.11.13 → 1.12.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 (79) hide show
  1. package/dist/littlejs.d.ts +1418 -109
  2. package/dist/littlejs.esm.js +3495 -581
  3. package/dist/littlejs.esm.min.js +1 -1
  4. package/dist/littlejs.js +3258 -399
  5. package/dist/littlejs.min.js +1 -1
  6. package/dist/littlejs.release.js +3147 -379
  7. package/examples/box2d/game.js +73 -45
  8. package/examples/box2d/gameObjects.js +96 -92
  9. package/examples/box2d/index.html +2 -7
  10. package/examples/box2d/scenes.js +82 -92
  11. package/examples/breakout/game.js +62 -30
  12. package/examples/breakout/gameObjects.js +45 -47
  13. package/examples/breakout/index.html +1 -5
  14. package/examples/breakoutTutorial/game.js +36 -29
  15. package/examples/breakoutTutorial/index.html +1 -3
  16. package/examples/empty/game.js +5 -2
  17. package/examples/empty/index.html +1 -3
  18. package/examples/htmlMenu/game.js +25 -19
  19. package/examples/htmlMenu/index.html +5 -7
  20. package/examples/index.html +2 -3
  21. package/examples/module/game.js +32 -34
  22. package/examples/module/index.html +1 -1
  23. package/examples/particles/index.html +27 -22
  24. package/examples/platformer/game.js +71 -48
  25. package/examples/platformer/gameCharacter.js +42 -34
  26. package/examples/platformer/gameEffects.js +82 -75
  27. package/examples/platformer/gameLevel.js +67 -38
  28. package/examples/platformer/{gameLevelData.js → gameLevelData.json} +1 -11
  29. package/examples/platformer/gameObjects.js +70 -64
  30. package/examples/platformer/gamePlayer.js +12 -7
  31. package/examples/platformer/index.html +1 -9
  32. package/examples/puzzle/game.js +58 -42
  33. package/examples/puzzle/index.html +1 -3
  34. package/examples/shorts/base.html +2 -2
  35. package/examples/shorts/particles.js +1 -1
  36. package/examples/shorts/platformer.js +1 -1
  37. package/examples/shorts/tileLayer.js +5 -6
  38. package/examples/shorts/tiles.png +0 -0
  39. package/examples/starter/build.js +4 -4
  40. package/examples/starter/game.js +9 -12
  41. package/examples/starter/index.html +13 -13
  42. package/examples/stress/index.html +44 -43
  43. package/examples/typescript/build.js +17 -18
  44. package/examples/typescript/game.js +32 -34
  45. package/examples/typescript/game.ts +32 -34
  46. package/examples/typescript/index.html +1 -1
  47. package/examples/uiSystem/game.js +33 -36
  48. package/examples/uiSystem/index.html +1 -5
  49. package/package.json +3 -3
  50. package/plugins/box2d.js +1556 -640
  51. package/plugins/{Box2D_v2.3.1_min.wasm.js → box2d.wasm.js} +1 -1
  52. package/plugins/newgrounds.js +7 -8
  53. package/plugins/pluginExport.js +50 -0
  54. package/plugins/postProcess.js +94 -93
  55. package/plugins/uiSystem.js +145 -161
  56. package/plugins/zzfxm.js +163 -0
  57. package/reference.md +29 -27
  58. package/src/engine.js +15 -20
  59. package/src/engineAudio.js +74 -204
  60. package/src/engineBuild.js +29 -4
  61. package/src/engineDebug.js +105 -9
  62. package/src/engineDraw.js +27 -14
  63. package/src/engineExport.js +12 -8
  64. package/src/engineInput.js +3 -1
  65. package/src/engineObject.js +18 -14
  66. package/src/engineParticles.js +6 -1
  67. package/src/engineRelease.js +6 -1
  68. package/src/engineSettings.js +8 -8
  69. package/src/engineTileLayer.js +179 -96
  70. package/src/engineUtilities.js +5 -11
  71. package/src/engineWebGL.js +19 -7
  72. package/examples/starter/build/index.html +0 -2
  73. package/examples/starter/build/index.js +0 -1
  74. package/examples/starter/build/tiles.png +0 -0
  75. package/examples/starter/game.zip +0 -0
  76. package/examples/typescript/build/dist/littlejs.esm.js +0 -4834
  77. package/examples/typescript/build/examples/typescript/build.js +0 -24
  78. package/examples/typescript/build/examples/typescript/game.js +0 -102
  79. /package/plugins/{Box2D_v2.3.1_min.wasm.wasm → box2d.wasm.wasm} +0 -0
@@ -72,8 +72,10 @@ function ASSERT(assert, output)
72
72
  * @memberof Debug */
73
73
  function debugRect(pos, size=vec2(), color='#fff', time=0, angle=0, fill=false)
74
74
  {
75
+ if (typeof size == 'number')
76
+ size = vec2(size); // allow passing in floats
75
77
  ASSERT(typeof color == 'string', 'pass in css color strings');
76
- debugPrimitives.push({pos, size:vec2(size), color, time:new Timer(time), angle, fill});
78
+ debugPrimitives.push({pos, size, color, time:new Timer(time), angle, fill});
77
79
  }
78
80
 
79
81
  /** Draw a debug poly in world space
@@ -197,17 +199,17 @@ function debugSaveDataURL(dataURL, filename)
197
199
  * @memberof Debug */
198
200
  function debugShowErrors()
199
201
  {
200
- onunhandledrejection = (event)=>showError(event.reason);
201
- onerror = (event, source, lineno, colno)=>
202
- showError(`${event}\n${source}\nLn ${lineno}, Col ${colno}`);
203
-
204
202
  const showError = (message)=>
205
203
  {
206
204
  // replace entire page with error message
207
205
  document.body.style.display = '';
208
206
  document.body.style.backgroundColor = '#111';
209
- document.body.innerHTML = `<pre style=color:#f00;font-size:50px>` + message;
207
+ document.body.innerHTML = `<pre style=color:#f00;font-size:50px;white-space:pre-wrap>` + message;
210
208
  }
209
+ onunhandledrejection = (event)=>
210
+ showError(event.reason.stack || event.reason);
211
+ onerror = (message, source, lineno, colno)=>
212
+ showError(`${message}\n${source}\nLn ${lineno}, Col ${colno}`);
211
213
  }
212
214
 
213
215
  ///////////////////////////////////////////////////////////////////////////////
@@ -240,11 +242,16 @@ function debugUpdate()
240
242
  debugRaycast = !debugRaycast;
241
243
  if (keyWasPressed('Digit5'))
242
244
  debugScreenshot();
245
+ if (keyWasPressed('Digit6'))
246
+ debugVideoCaptureIsActive() ? debugVideoCaptureStop() : debugVideoCaptureStart();
243
247
  }
244
248
  }
245
249
 
246
250
  function debugRender()
247
251
  {
252
+ if (debugVideoCaptureIsActive())
253
+ return; // don't show debug info when capturing video
254
+
248
255
  glCopyToContext(mainContext);
249
256
 
250
257
  if (debugTakeScreenshot)
@@ -322,11 +329,9 @@ function debugRender()
322
329
  }
323
330
  }
324
331
 
325
- if (tileCollisionSize.x > 0 && tileCollisionSize.y > 0)
332
+ if (tileCollisionLayers.length) // show floored tile pick if there is tile collision
326
333
  drawRect(mousePos.floor().add(vec2(.5)), vec2(1), rgb(0,0,1,.5), 0, false);
327
334
  mainContext = saveContext;
328
-
329
- //glCopyToContext(mainContext = saveContext);
330
335
  }
331
336
 
332
337
  {
@@ -439,6 +444,7 @@ function debugRender()
439
444
  overlayContext.fillText('4: Debug Raycasts', x, y += h);
440
445
  overlayContext.fillStyle = '#fff';
441
446
  overlayContext.fillText('5: Save Screenshot', x, y += h);
447
+ overlayContext.fillText('6: Capture Video', x, y += h);
442
448
 
443
449
  let keysPressed = '';
444
450
  for(const i in inputData[0])
@@ -467,4 +473,94 @@ function debugRender()
467
473
 
468
474
  overlayContext.restore();
469
475
  }
476
+ }
477
+
478
+ ///////////////////////////////////////////////////////////////////////////////
479
+ // video capture - records video and audio at 60 fps using MediaRecorder API
480
+
481
+ // internal variables used to capture video
482
+ let debugVideoCapture, debugVideoCaptureTrack, debugVideoCaptureIcon, debugVideoCaptureTimer;
483
+
484
+ /** Check if video capture is active
485
+ * @memberof Debug */
486
+ function debugVideoCaptureIsActive() { return !!debugVideoCapture; }
487
+
488
+ /** Start capturing video
489
+ * @memberof Debug */
490
+ function debugVideoCaptureStart()
491
+ {
492
+ if (debugVideoCaptureIsActive())
493
+ return; // already recording
494
+
495
+ // captureStream passing in 0 to only capture when requestFrame() is called
496
+ const stream = mainCanvas.captureStream(0);
497
+ const chunks = [];
498
+ debugVideoCaptureTrack = stream.getVideoTracks()[0];
499
+ if (debugVideoCaptureTrack.applyConstraints)
500
+ debugVideoCaptureTrack.applyConstraints({frameRate:frameRate}); // force 60 fps
501
+ debugVideoCapture = new MediaRecorder(stream, {mimeType:'video/webm;codecs=vp8'});
502
+ debugVideoCapture.ondataavailable = (e)=> chunks.push(e.data);
503
+ debugVideoCapture.onstop = ()=>
504
+ {
505
+ const blob = new Blob(chunks, {type: 'video/webm'});
506
+ const url = URL.createObjectURL(blob);
507
+ downloadLink.download = 'capture.webm';
508
+ downloadLink.href = url;
509
+ downloadLink.click();
510
+ URL.revokeObjectURL(url);
511
+ };
512
+
513
+ if (audioMasterGain)
514
+ {
515
+ // connect to audio master gain node
516
+ const audioStreamDestination = audioContext.createMediaStreamDestination();
517
+ audioMasterGain.connect(audioStreamDestination);
518
+ for (const track of audioStreamDestination.stream.getAudioTracks())
519
+ stream.addTrack(track); // add audio tracks to capture stream
520
+ }
521
+
522
+ // start recording
523
+ console.log('Video capture started.');
524
+ debugVideoCapture.start();
525
+ debugVideoCaptureTimer = new Timer(0);
526
+
527
+ if (!debugVideoCaptureIcon)
528
+ {
529
+ // create recording icon to show it is capturing video
530
+ debugVideoCaptureIcon = document.createElement('div');
531
+ debugVideoCaptureIcon.style.position = 'absolute';
532
+ debugVideoCaptureIcon.style.padding = '9px';
533
+ debugVideoCaptureIcon.style.color = '#f00';
534
+ debugVideoCaptureIcon.style.font = '50px monospace';
535
+ document.body.appendChild(debugVideoCaptureIcon);
536
+ }
537
+ // show recording icon
538
+ debugVideoCaptureIcon.textContent = '';
539
+ debugVideoCaptureIcon.style.display = '';
540
+ }
541
+
542
+ /** Stop capturing video and save to disk
543
+ * @memberof Debug */
544
+ function debugVideoCaptureStop()
545
+ {
546
+ if (!debugVideoCaptureIsActive())
547
+ return; // not recording
548
+
549
+ // stop recording
550
+ console.log(`Video capture ended. ${debugVideoCaptureTimer.get().toFixed(2)} seconds recorded.`);
551
+ debugVideoCapture.stop();
552
+ debugVideoCapture = 0;
553
+ debugVideoCaptureIcon.style.display = 'none';
554
+ }
555
+
556
+ // update video capture, called automatically by engine
557
+ function debugVideoCaptureUpdate()
558
+ {
559
+ if (!debugVideoCaptureIsActive())
560
+ return; // not recording
561
+
562
+ // save the video frame
563
+ combineCanvases();
564
+ debugVideoCaptureTrack.requestFrame();
565
+ debugVideoCaptureIcon.textContent = '● REC ' + formatTime(debugVideoCaptureTimer);
470
566
  }
package/src/engineDraw.js CHANGED
@@ -160,6 +160,8 @@ class TextureInfo
160
160
  this.image = image;
161
161
  /** @property {Vector2} - size of the image */
162
162
  this.size = vec2(image.width, image.height);
163
+ /** @property {Vector2} - inverse of the size, cached for rendering */
164
+ this.sizeInverse = vec2(1/image.width, 1/image.height);
163
165
  /** @property {WebGLTexture} - webgl texture */
164
166
  this.glTexture = glEnable && glCreateTexture(image);
165
167
  }
@@ -205,19 +207,19 @@ function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
205
207
  * @param {Color} [color=(1,1,1,1)] - Color to modulate with
206
208
  * @param {number} [angle] - Angle to rotate by
207
209
  * @param {boolean} [mirror] - If true image is flipped along the Y axis
208
- * @param {Color} [additiveColor=(0,0,0,0)] - Additive color to be applied
210
+ * @param {Color} [additiveColor] - Additive color to be applied if any
209
211
  * @param {boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
210
212
  * @param {boolean} [screenSpace=false] - If true the pos and size are in screen space
211
213
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
212
214
  * @memberof Draw */
213
215
  function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
214
- angle=0, mirror, additiveColor=new Color(0,0,0,0), useWebGL=glEnable, screenSpace, context)
216
+ angle=0, mirror, additiveColor, useWebGL=glEnable, screenSpace, context)
215
217
  {
216
218
  ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
217
219
  ASSERT(typeof tileInfo !== 'number' || !tileInfo,
218
220
  'this is an old style calls, to fix replace it with tile(tileIndex, tileSize)');
219
221
  ASSERT(isVector2(pos) && isVector2(size));
220
- ASSERT(isColor(color) && isColor(additiveColor));
222
+ ASSERT(isColor(color) && (!additiveColor || isColor(additiveColor)));
221
223
 
222
224
  const textureInfo = tileInfo && tileInfo.getTextureInfo();
223
225
  if (useWebGL)
@@ -228,21 +230,30 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
228
230
  pos = screenToWorld(pos);
229
231
  size = size.scale(1/cameraScale);
230
232
  }
231
-
232
233
  if (textureInfo)
233
234
  {
234
235
  // calculate uvs and render
235
- const sizeInverse = vec2(1).divide(textureInfo.size);
236
+ const sizeInverse = textureInfo.sizeInverse;
236
237
  const x = tileInfo.pos.x * sizeInverse.x;
237
238
  const y = tileInfo.pos.y * sizeInverse.y;
238
239
  const w = tileInfo.size.x * sizeInverse.x;
239
240
  const h = tileInfo.size.y * sizeInverse.y;
240
- const tileImageFixBleed = sizeInverse.scale(tileFixBleedScale);
241
241
  glSetTexture(textureInfo.glTexture);
242
- glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
243
- x + tileImageFixBleed.x, y + tileImageFixBleed.y,
244
- x - tileImageFixBleed.x + w, y - tileImageFixBleed.y + h,
245
- color.rgbaInt(), additiveColor.rgbaInt());
242
+ if (tileFixBleedScale)
243
+ {
244
+ const tileImageFixBleedX = sizeInverse.x*tileFixBleedScale;
245
+ const tileImageFixBleedY = sizeInverse.y*tileFixBleedScale;
246
+ glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
247
+ x + tileImageFixBleedX, y + tileImageFixBleedY,
248
+ x - tileImageFixBleedX + w, y - tileImageFixBleedY + h,
249
+ color.rgbaInt(), additiveColor && additiveColor.rgbaInt());
250
+ }
251
+ else
252
+ {
253
+ glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
254
+ x, y, x + w, y + h,
255
+ color.rgbaInt(), additiveColor && additiveColor.rgbaInt());
256
+ }
246
257
  }
247
258
  else
248
259
  {
@@ -454,16 +465,15 @@ function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textA
454
465
  function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, maxWidth=undefined, context=overlayContext)
455
466
  {
456
467
  context.fillStyle = color.toString();
457
- context.lineWidth = lineWidth;
458
468
  context.strokeStyle = lineColor.toString();
469
+ context.lineWidth = lineWidth;
459
470
  context.textAlign = textAlign;
460
471
  context.font = size + 'px '+ font;
461
472
  context.textBaseline = 'middle';
462
473
  context.lineJoin = 'round';
463
474
 
464
- pos = pos.copy();
465
-
466
475
  const lines = (text+'').split('\n');
476
+ pos = pos.copy();
467
477
  pos.y -= (lines.length-1) * size/2; // center text vertically
468
478
  lines.forEach(line=>
469
479
  {
@@ -533,7 +543,10 @@ class FontImage
533
543
  {
534
544
  // load default font image
535
545
  if (!engineFontImage)
536
- (engineFontImage = new Image).src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
546
+ {
547
+ engineFontImage = new Image;
548
+ engineFontImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
549
+ }
537
550
 
538
551
  this.image = image || engineFontImage;
539
552
  this.tileSize = tileSize;
@@ -1,6 +1,5 @@
1
1
  /**
2
2
  * LittleJS Module Export
3
- * - Export engine as a module
4
3
  */
5
4
 
6
5
  export
@@ -43,6 +42,10 @@ export
43
42
  debugSaveCanvas,
44
43
  debugSaveText,
45
44
  debugSaveDataURL,
45
+ debugShowErrors,
46
+ debugVideoCaptureIsActive,
47
+ debugVideoCaptureStart,
48
+ debugVideoCaptureStop,
46
49
 
47
50
  // Settings
48
51
  cameraPos,
@@ -216,6 +219,7 @@ export
216
219
  glCopyToContext,
217
220
  glCreateProgram,
218
221
  glCreateTexture,
222
+ glSetTextureData,
219
223
  glDraw,
220
224
  glFlush,
221
225
  glSetTexture,
@@ -245,7 +249,8 @@ export
245
249
  mousePosScreen,
246
250
  mouseWheel,
247
251
  isUsingGamepad,
248
- preventDefaultInput,
252
+ inputPreventDefault,
253
+ setInputPreventDefault,
249
254
  gamepadIsDown,
250
255
  gamepadWasPressed,
251
256
  gamepadWasReleased,
@@ -258,28 +263,27 @@ export
258
263
  // Audio
259
264
  Sound,
260
265
  SoundWave,
261
- Music,
262
266
  playAudioFile,
263
267
  speak,
264
268
  speakStop,
265
269
  getNoteFrequency,
266
- audioContext,
267
270
  playSamples,
268
271
  zzfx,
272
+ zzfxG,
273
+ zzfxR,
274
+ audioContext,
269
275
 
270
276
  // Base Object
271
277
  EngineObject,
272
278
 
273
279
  // Tiles
274
- tileCollision,
275
- tileCollisionSize,
276
- initTileCollision,
277
- setTileCollisionData,
280
+ tileCollisionLayers,
278
281
  getTileCollisionData,
279
282
  tileCollisionTest,
280
283
  tileCollisionRaycast,
281
284
  TileLayerData,
282
285
  TileLayer,
286
+ TileCollisionLayer,
283
287
 
284
288
  // Particles
285
289
  ParticleEmitter,
@@ -314,7 +314,9 @@ function gamepadsUpdate()
314
314
  const button = gamepad.buttons[j];
315
315
  const wasDown = gamepadIsDown(j,i);
316
316
  data[j] = button.pressed ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
317
- isUsingGamepad ||= !i && button.pressed;
317
+ if (!button.value || button.value > .9) // must be a full press
318
+ if (!i && button.pressed)
319
+ isUsingGamepad = true;
318
320
  }
319
321
 
320
322
  if (gamepadDirectionEmulateStick)
@@ -158,7 +158,10 @@ class EngineObject
158
158
  this.velocity.x *= this.damping;
159
159
  this.velocity.y *= this.damping;
160
160
  if (this.mass) // don't apply gravity to static objects
161
- this.velocity.y += gravity * this.gravityScale;
161
+ {
162
+ this.velocity.x += gravity.x * this.gravityScale;
163
+ this.velocity.y += gravity.y * this.gravityScale;
164
+ }
162
165
  this.pos.x += this.velocity.x;
163
166
  this.pos.y += this.velocity.y;
164
167
  this.angle += this.angleVelocity *= this.angleDamping;
@@ -173,9 +176,9 @@ class EngineObject
173
176
  if (this.groundObject)
174
177
  {
175
178
  // apply friction in local space of ground object
176
- const groundSpeed = this.groundObject != this && this.groundObject.velocity ?
177
- this.groundObject.velocity.x : 0;
178
- this.velocity.x = groundSpeed + (this.velocity.x - groundSpeed) * this.friction;
179
+ const friction = max(this.friction, this.groundObject.friction);
180
+ const groundSpeed = this.groundObject.velocity ? this.groundObject.velocity.x : 0;
181
+ this.velocity.x = groundSpeed + (this.velocity.x - groundSpeed) * friction;
179
182
  this.groundObject = undefined;
180
183
  //debugOverlay && debugPhysics && debugPoint(this.pos.subtract(vec2(0,this.size.y/2)), '#0f0');
181
184
  }
@@ -217,7 +220,7 @@ class EngineObject
217
220
 
218
221
  // check for collision
219
222
  const sizeBoth = this.size.add(o.size);
220
- const smallStepUp = (oldPos.y - o.pos.y)*2 > sizeBoth.y + gravity; // prefer to push up if small delta
223
+ const smallStepUp = (oldPos.y - o.pos.y)*2 > sizeBoth.y + gravity.y; // prefer to push up if small delta
221
224
  const isBlockedX = abs(oldPos.y - o.pos.y)*2 < sizeBoth.y;
222
225
  const isBlockedY = abs(oldPos.x - o.pos.x)*2 < sizeBoth.x;
223
226
  const elasticity = max(this.elasticity, o.elasticity);
@@ -279,19 +282,21 @@ class EngineObject
279
282
  if (this.collideTiles)
280
283
  {
281
284
  // check collision against tiles
282
- if (tileCollisionTest(this.pos, this.size, this))
285
+ const hitLayer = tileCollisionTest(this.pos, this.size, this)
286
+ if (hitLayer)
283
287
  {
284
288
  // if already was stuck in collision, don't do anything
285
289
  // this should not happen unless something starts in collision
286
290
  if (!tileCollisionTest(oldPos, this.size, this))
287
291
  {
288
292
  // test which side we bounced off (or both if a corner)
289
- const isBlockedY = tileCollisionTest(vec2(oldPos.x, this.pos.y), this.size, this);
290
- const isBlockedX = tileCollisionTest(vec2(this.pos.x, oldPos.y), this.size, this);
291
- if (isBlockedY || !isBlockedX)
293
+ const blockedLayerY = tileCollisionTest(vec2(oldPos.x, this.pos.y), this.size, this);
294
+ const blockedLayerX = tileCollisionTest(vec2(this.pos.x, oldPos.y), this.size, this);
295
+ if (blockedLayerY || !blockedLayerX)
292
296
  {
293
297
  // bounce velocity
294
- this.velocity.y *= -this.elasticity;
298
+ const elasticity = max(this.elasticity, hitLayer.elasticity);
299
+ this.velocity.y *= -elasticity;
295
300
 
296
301
  if (wasMovingDown)
297
302
  {
@@ -300,9 +305,8 @@ class EngineObject
300
305
  const epsilon = .0001;
301
306
  this.pos.y = (oldPos.y-this.size.y/2|0)+this.size.y/2+epsilon;
302
307
 
303
- // set ground object to self for tile collision
304
- // TODO: rework system so tile collision is its own object
305
- this.groundObject = this;
308
+ // set ground object for tile collision
309
+ this.groundObject = hitLayer;
306
310
  }
307
311
  else
308
312
  {
@@ -311,7 +315,7 @@ class EngineObject
311
315
  this.groundObject = undefined;
312
316
  }
313
317
  }
314
- if (isBlockedX)
318
+ if (blockedLayerX)
315
319
  {
316
320
  // move to previous position and bounce
317
321
  this.pos.x = oldPos.x;
@@ -166,7 +166,12 @@ class ParticleEmitter extends EngineObject
166
166
  else
167
167
  this.destroy();
168
168
 
169
- debugParticles && debugRect(this.pos, vec2(this.emitSize), '#0f0', 0, this.angle);
169
+ if (debugParticles)
170
+ {
171
+ // show emitter bounds
172
+ const emitSize = typeof this.emitSize === 'number' ? vec2(this.emitSize) : this.emitSize;
173
+ debugRect(this.pos, emitSize, '#0f0', 0, this.angle);
174
+ }
170
175
  }
171
176
 
172
177
  /** Spawn one particle
@@ -32,4 +32,9 @@ function debugClear (){}
32
32
  function debugScreenshot (){}
33
33
  function debugSaveCanvas (){}
34
34
  function debugSaveText (){}
35
- function debugSaveDataURL(){}
35
+ function debugSaveDataURL(){}
36
+ function debugShowErrors(){}
37
+ function debugVideoCaptureIsActive(){ return false; }
38
+ function debugVideoCaptureStart (){}
39
+ function debugVideoCaptureStop (){}
40
+ function debugVideoCaptureUpdate(){}
@@ -37,7 +37,7 @@ let canvasMaxSize = vec2(1920, 1080);
37
37
  * @memberof Settings */
38
38
  let canvasFixedSize = vec2();
39
39
 
40
- /** Use nearest neighbor scaling algorithm for canvas for more pixelated look
40
+ /** Use nearest neighbor canvas scaling for more pixelated look
41
41
  * - Must be set before startup to take effect
42
42
  * - If enabled sets css image-rendering:pixelated
43
43
  * @type {boolean}
@@ -147,11 +147,11 @@ let objectDefaultFriction = .8;
147
147
  * @memberof Settings */
148
148
  let objectMaxSpeed = 1;
149
149
 
150
- /** How much gravity to apply to objects along the Y axis, negative is down
151
- * @type {number}
150
+ /** How much gravity to apply to objects, negative Y is down
151
+ * @type {Vector2}
152
152
  * @default
153
153
  * @memberof Settings */
154
- let gravity = 0;
154
+ let gravity = vec2();
155
155
 
156
156
  /** Scales emit rate of particles, useful for low graphics mode (0 disables particle emitters)
157
157
  * @type {number}
@@ -377,8 +377,8 @@ function setObjectDefaultFriction(friction) { objectDefaultFriction = friction;
377
377
  * @memberof Settings */
378
378
  function setObjectMaxSpeed(speed) { objectMaxSpeed = speed; }
379
379
 
380
- /** Set how much gravity to apply to objects along the Y axis
381
- * @param {number} newGravity
380
+ /** Set how much gravity to apply to objects
381
+ * @param {Vector2} newGravity
382
382
  * @memberof Settings */
383
383
  function setGravity(newGravity) { gravity = newGravity; }
384
384
 
@@ -443,8 +443,8 @@ function setSoundEnable(enable) { soundEnable = enable; }
443
443
  function setSoundVolume(volume)
444
444
  {
445
445
  soundVolume = volume;
446
- if (soundEnable && !headlessMode && audioGainNode)
447
- audioGainNode.gain.value = volume; // update gain immediately
446
+ if (soundEnable && !headlessMode && audioMasterGain)
447
+ audioMasterGain.gain.value = volume; // update gain immediately
448
448
  }
449
449
 
450
450
  /** Set default range where sound no longer plays