littlejsengine 1.11.13 → 1.11.17
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.
- package/dist/littlejs.d.ts +1306 -66
- package/dist/littlejs.esm.js +3097 -269
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +253 -265
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +155 -260
- package/examples/box2d/game.js +7 -10
- package/examples/box2d/gameObjects.js +33 -33
- package/examples/box2d/index.html +6 -6
- package/examples/box2d/scenes.js +28 -28
- package/examples/breakout/game.js +1 -4
- package/examples/breakout/index.html +4 -4
- package/examples/breakoutTutorial/index.html +2 -2
- package/examples/htmlMenu/game.js +3 -6
- package/examples/htmlMenu/index.html +2 -2
- package/examples/module/index.html +1 -1
- package/examples/particles/index.html +1 -1
- package/examples/platformer/game.js +0 -3
- package/examples/platformer/index.html +8 -8
- package/examples/puzzle/game.js +0 -3
- package/examples/puzzle/index.html +2 -2
- package/examples/shorts/base.html +1 -1
- package/examples/starter/build.js +4 -4
- package/examples/starter/index.html +13 -13
- package/examples/stress/index.html +3 -2
- package/examples/uiSystem/game.js +1 -7
- package/examples/uiSystem/index.html +3 -3
- package/package.json +3 -3
- package/plugins/box2d.js +1552 -640
- package/plugins/{Box2D_v2.3.1_min.wasm.js → box2d.wasm.js} +1 -1
- package/plugins/newgrounds.js +7 -8
- package/plugins/pluginExport.js +51 -0
- package/plugins/postProcess.js +94 -93
- package/plugins/uiSystem.js +145 -161
- package/plugins/zzfxm.js +163 -0
- package/src/engine.js +15 -20
- package/src/engineAudio.js +74 -204
- package/src/engineBuild.js +21 -3
- package/src/engineDebug.js +104 -6
- package/src/engineDraw.js +27 -14
- package/src/engineExport.js +12 -4
- package/src/engineParticles.js +6 -1
- package/src/engineRelease.js +6 -1
- package/src/engineSettings.js +2 -2
- package/src/engineUtilities.js +5 -11
- package/src/engineWebGL.js +19 -7
- package/examples/starter/build/index.html +0 -2
- package/examples/starter/build/index.js +0 -1
- package/examples/starter/build/tiles.png +0 -0
- package/examples/starter/game.zip +0 -0
- /package/plugins/{Box2D_v2.3.1_min.wasm.wasm → box2d.wasm.wasm} +0 -0
package/dist/littlejs.js
CHANGED
|
@@ -77,8 +77,10 @@ function ASSERT(assert, output)
|
|
|
77
77
|
* @memberof Debug */
|
|
78
78
|
function debugRect(pos, size=vec2(), color='#fff', time=0, angle=0, fill=false)
|
|
79
79
|
{
|
|
80
|
+
if (typeof size == 'number')
|
|
81
|
+
size = vec2(size); // allow passing in floats
|
|
80
82
|
ASSERT(typeof color == 'string', 'pass in css color strings');
|
|
81
|
-
debugPrimitives.push({pos, size
|
|
83
|
+
debugPrimitives.push({pos, size, color, time:new Timer(time), angle, fill});
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
/** Draw a debug poly in world space
|
|
@@ -202,17 +204,17 @@ function debugSaveDataURL(dataURL, filename)
|
|
|
202
204
|
* @memberof Debug */
|
|
203
205
|
function debugShowErrors()
|
|
204
206
|
{
|
|
205
|
-
onunhandledrejection = (event)=>showError(event.reason);
|
|
206
|
-
onerror = (event, source, lineno, colno)=>
|
|
207
|
-
showError(`${event}\n${source}\nLn ${lineno}, Col ${colno}`);
|
|
208
|
-
|
|
209
207
|
const showError = (message)=>
|
|
210
208
|
{
|
|
211
209
|
// replace entire page with error message
|
|
212
210
|
document.body.style.display = '';
|
|
213
211
|
document.body.style.backgroundColor = '#111';
|
|
214
|
-
document.body.innerHTML = `<pre style=color:#f00;font-size:50px>` + message;
|
|
212
|
+
document.body.innerHTML = `<pre style=color:#f00;font-size:50px;white-space:pre-wrap>` + message;
|
|
215
213
|
}
|
|
214
|
+
onunhandledrejection = (event)=>
|
|
215
|
+
showError(event.reason.stack || event.reason);
|
|
216
|
+
onerror = (message, source, lineno, colno)=>
|
|
217
|
+
showError(`${message}\n${source}\nLn ${lineno}, Col ${colno}`);
|
|
216
218
|
}
|
|
217
219
|
|
|
218
220
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -245,11 +247,16 @@ function debugUpdate()
|
|
|
245
247
|
debugRaycast = !debugRaycast;
|
|
246
248
|
if (keyWasPressed('Digit5'))
|
|
247
249
|
debugScreenshot();
|
|
250
|
+
if (keyWasPressed('Digit6'))
|
|
251
|
+
debugVideoCaptureIsActive() ? debugVideoCaptureStop() : debugVideoCaptureStart();
|
|
248
252
|
}
|
|
249
253
|
}
|
|
250
254
|
|
|
251
255
|
function debugRender()
|
|
252
256
|
{
|
|
257
|
+
if (debugVideoCaptureIsActive())
|
|
258
|
+
return; // don't show debug info when capturing video
|
|
259
|
+
|
|
253
260
|
glCopyToContext(mainContext);
|
|
254
261
|
|
|
255
262
|
if (debugTakeScreenshot)
|
|
@@ -444,6 +451,7 @@ function debugRender()
|
|
|
444
451
|
overlayContext.fillText('4: Debug Raycasts', x, y += h);
|
|
445
452
|
overlayContext.fillStyle = '#fff';
|
|
446
453
|
overlayContext.fillText('5: Save Screenshot', x, y += h);
|
|
454
|
+
overlayContext.fillText('6: Capture Video', x, y += h);
|
|
447
455
|
|
|
448
456
|
let keysPressed = '';
|
|
449
457
|
for(const i in inputData[0])
|
|
@@ -472,6 +480,96 @@ function debugRender()
|
|
|
472
480
|
|
|
473
481
|
overlayContext.restore();
|
|
474
482
|
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
486
|
+
// video capture - records video and audio at 60 fps using MediaRecorder API
|
|
487
|
+
|
|
488
|
+
// internal variables used to capture video
|
|
489
|
+
let debugVideoCapture, debugVideoCaptureTrack, debugVideoCaptureIcon, debugVideoCaptureTimer;
|
|
490
|
+
|
|
491
|
+
/** Check if video capture is active
|
|
492
|
+
* @memberof Debug */
|
|
493
|
+
function debugVideoCaptureIsActive() { return !!debugVideoCapture; }
|
|
494
|
+
|
|
495
|
+
/** Start capturing video
|
|
496
|
+
* @memberof Debug */
|
|
497
|
+
function debugVideoCaptureStart()
|
|
498
|
+
{
|
|
499
|
+
if (debugVideoCaptureIsActive())
|
|
500
|
+
return; // already recording
|
|
501
|
+
|
|
502
|
+
// captureStream passing in 0 to only capture when requestFrame() is called
|
|
503
|
+
const stream = mainCanvas.captureStream(0);
|
|
504
|
+
const chunks = [];
|
|
505
|
+
debugVideoCaptureTrack = stream.getVideoTracks()[0];
|
|
506
|
+
if (debugVideoCaptureTrack.applyConstraints)
|
|
507
|
+
debugVideoCaptureTrack.applyConstraints({frameRate:frameRate}); // force 60 fps
|
|
508
|
+
debugVideoCapture = new MediaRecorder(stream, {mimeType:'video/webm;codecs=vp8'});
|
|
509
|
+
debugVideoCapture.ondataavailable = (e)=> chunks.push(e.data);
|
|
510
|
+
debugVideoCapture.onstop = ()=>
|
|
511
|
+
{
|
|
512
|
+
const blob = new Blob(chunks, {type: 'video/webm'});
|
|
513
|
+
const url = URL.createObjectURL(blob);
|
|
514
|
+
downloadLink.download = 'capture.webm';
|
|
515
|
+
downloadLink.href = url;
|
|
516
|
+
downloadLink.click();
|
|
517
|
+
URL.revokeObjectURL(url);
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
if (audioMasterGain)
|
|
521
|
+
{
|
|
522
|
+
// connect to audio master gain node
|
|
523
|
+
const audioStreamDestination = audioContext.createMediaStreamDestination();
|
|
524
|
+
audioMasterGain.connect(audioStreamDestination);
|
|
525
|
+
for (const track of audioStreamDestination.stream.getAudioTracks())
|
|
526
|
+
stream.addTrack(track); // add audio tracks to capture stream
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// start recording
|
|
530
|
+
console.log('Video capture started.');
|
|
531
|
+
debugVideoCapture.start();
|
|
532
|
+
debugVideoCaptureTimer = new Timer(0);
|
|
533
|
+
|
|
534
|
+
if (!debugVideoCaptureIcon)
|
|
535
|
+
{
|
|
536
|
+
// create recording icon to show it is capturing video
|
|
537
|
+
debugVideoCaptureIcon = document.createElement('div');
|
|
538
|
+
debugVideoCaptureIcon.style.position = 'absolute';
|
|
539
|
+
debugVideoCaptureIcon.style.padding = '9px';
|
|
540
|
+
debugVideoCaptureIcon.style.color = '#f00';
|
|
541
|
+
debugVideoCaptureIcon.style.font = '50px monospace';
|
|
542
|
+
document.body.appendChild(debugVideoCaptureIcon);
|
|
543
|
+
}
|
|
544
|
+
// show recording icon
|
|
545
|
+
debugVideoCaptureIcon.textContent = '';
|
|
546
|
+
debugVideoCaptureIcon.style.display = '';
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/** Stop capturing video and save to disk
|
|
550
|
+
* @memberof Debug */
|
|
551
|
+
function debugVideoCaptureStop()
|
|
552
|
+
{
|
|
553
|
+
if (!debugVideoCaptureIsActive())
|
|
554
|
+
return; // not recording
|
|
555
|
+
|
|
556
|
+
// stop recording
|
|
557
|
+
console.log(`Video capture ended. ${debugVideoCaptureTimer.get().toFixed(2)} seconds recorded.`);
|
|
558
|
+
debugVideoCapture.stop();
|
|
559
|
+
debugVideoCapture = 0;
|
|
560
|
+
debugVideoCaptureIcon.style.display = 'none';
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// update video capture, called automatically by engine
|
|
564
|
+
function debugVideoCaptureUpdate()
|
|
565
|
+
{
|
|
566
|
+
if (!debugVideoCaptureIsActive())
|
|
567
|
+
return; // not recording
|
|
568
|
+
|
|
569
|
+
// save the video frame
|
|
570
|
+
combineCanvases();
|
|
571
|
+
debugVideoCaptureTrack.requestFrame();
|
|
572
|
+
debugVideoCaptureIcon.textContent = '● REC ' + formatTime(debugVideoCaptureTimer);
|
|
475
573
|
}
|
|
476
574
|
/**
|
|
477
575
|
* LittleJS Utility Classes and Functions
|
|
@@ -566,7 +664,7 @@ function distanceWrap(valueA, valueB, wrapSize=1)
|
|
|
566
664
|
* @returns {number}
|
|
567
665
|
* @memberof Utilities */
|
|
568
666
|
function lerpWrap(percent, valueA, valueB, wrapSize=1)
|
|
569
|
-
{ return
|
|
667
|
+
{ return valueA + clamp(percent) * distanceWrap(valueB, valueA, wrapSize); }
|
|
570
668
|
|
|
571
669
|
/** Returns signed wrapped distance between the two angles passed in
|
|
572
670
|
* @param {number} angleA
|
|
@@ -764,23 +862,17 @@ class RandomGenerator
|
|
|
764
862
|
///////////////////////////////////////////////////////////////////////////////
|
|
765
863
|
|
|
766
864
|
/**
|
|
767
|
-
* Create a 2d vector, can take
|
|
768
|
-
* @param {
|
|
769
|
-
* @param {number} [y]
|
|
865
|
+
* Create a 2d vector, can take 1 or 2 scalar values
|
|
866
|
+
* @param {number} [x]
|
|
867
|
+
* @param {number} [y] - if y is undefined, x is used for both
|
|
770
868
|
* @return {Vector2}
|
|
771
869
|
* @example
|
|
772
870
|
* let a = vec2(0, 1); // vector with coordinates (0, 1)
|
|
773
|
-
* let b = vec2(a); // copy a into b
|
|
774
871
|
* a = vec2(5); // set a to (5, 5)
|
|
775
872
|
* b = vec2(); // set b to (0, 0)
|
|
776
873
|
* @memberof Utilities
|
|
777
874
|
*/
|
|
778
|
-
function vec2(x=0, y)
|
|
779
|
-
{
|
|
780
|
-
return typeof x == 'number' ?
|
|
781
|
-
new Vector2(x, y == undefined? x : y) :
|
|
782
|
-
new Vector2(x.x, x.y);
|
|
783
|
-
}
|
|
875
|
+
function vec2(x=0, y) { return new Vector2(x, y == undefined? x : y); }
|
|
784
876
|
|
|
785
877
|
/**
|
|
786
878
|
* Check if object is a valid Vector2
|
|
@@ -1844,8 +1936,8 @@ function setSoundEnable(enable) { soundEnable = enable; }
|
|
|
1844
1936
|
function setSoundVolume(volume)
|
|
1845
1937
|
{
|
|
1846
1938
|
soundVolume = volume;
|
|
1847
|
-
if (soundEnable && !headlessMode &&
|
|
1848
|
-
|
|
1939
|
+
if (soundEnable && !headlessMode && audioMasterGain)
|
|
1940
|
+
audioMasterGain.gain.value = volume; // update gain immediately
|
|
1849
1941
|
}
|
|
1850
1942
|
|
|
1851
1943
|
/** Set default range where sound no longer plays
|
|
@@ -2511,6 +2603,8 @@ class TextureInfo
|
|
|
2511
2603
|
this.image = image;
|
|
2512
2604
|
/** @property {Vector2} - size of the image */
|
|
2513
2605
|
this.size = vec2(image.width, image.height);
|
|
2606
|
+
/** @property {Vector2} - inverse of the size, cached for rendering */
|
|
2607
|
+
this.sizeInverse = vec2(1/image.width, 1/image.height);
|
|
2514
2608
|
/** @property {WebGLTexture} - webgl texture */
|
|
2515
2609
|
this.glTexture = glEnable && glCreateTexture(image);
|
|
2516
2610
|
}
|
|
@@ -2556,19 +2650,19 @@ function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
|
|
|
2556
2650
|
* @param {Color} [color=(1,1,1,1)] - Color to modulate with
|
|
2557
2651
|
* @param {number} [angle] - Angle to rotate by
|
|
2558
2652
|
* @param {boolean} [mirror] - If true image is flipped along the Y axis
|
|
2559
|
-
* @param {Color} [additiveColor
|
|
2653
|
+
* @param {Color} [additiveColor] - Additive color to be applied if any
|
|
2560
2654
|
* @param {boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
|
|
2561
2655
|
* @param {boolean} [screenSpace=false] - If true the pos and size are in screen space
|
|
2562
2656
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
|
|
2563
2657
|
* @memberof Draw */
|
|
2564
2658
|
function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
|
|
2565
|
-
angle=0, mirror, additiveColor
|
|
2659
|
+
angle=0, mirror, additiveColor, useWebGL=glEnable, screenSpace, context)
|
|
2566
2660
|
{
|
|
2567
2661
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
2568
2662
|
ASSERT(typeof tileInfo !== 'number' || !tileInfo,
|
|
2569
2663
|
'this is an old style calls, to fix replace it with tile(tileIndex, tileSize)');
|
|
2570
2664
|
ASSERT(isVector2(pos) && isVector2(size));
|
|
2571
|
-
ASSERT(isColor(color) && isColor(additiveColor));
|
|
2665
|
+
ASSERT(isColor(color) && (!additiveColor || isColor(additiveColor)));
|
|
2572
2666
|
|
|
2573
2667
|
const textureInfo = tileInfo && tileInfo.getTextureInfo();
|
|
2574
2668
|
if (useWebGL)
|
|
@@ -2579,21 +2673,30 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
|
|
|
2579
2673
|
pos = screenToWorld(pos);
|
|
2580
2674
|
size = size.scale(1/cameraScale);
|
|
2581
2675
|
}
|
|
2582
|
-
|
|
2583
2676
|
if (textureInfo)
|
|
2584
2677
|
{
|
|
2585
2678
|
// calculate uvs and render
|
|
2586
|
-
const sizeInverse =
|
|
2679
|
+
const sizeInverse = textureInfo.sizeInverse;
|
|
2587
2680
|
const x = tileInfo.pos.x * sizeInverse.x;
|
|
2588
2681
|
const y = tileInfo.pos.y * sizeInverse.y;
|
|
2589
2682
|
const w = tileInfo.size.x * sizeInverse.x;
|
|
2590
2683
|
const h = tileInfo.size.y * sizeInverse.y;
|
|
2591
|
-
const tileImageFixBleed = sizeInverse.scale(tileFixBleedScale);
|
|
2592
2684
|
glSetTexture(textureInfo.glTexture);
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2685
|
+
if (tileFixBleedScale)
|
|
2686
|
+
{
|
|
2687
|
+
const tileImageFixBleedX = sizeInverse.x*tileFixBleedScale;
|
|
2688
|
+
const tileImageFixBleedY = sizeInverse.y*tileFixBleedScale;
|
|
2689
|
+
glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
|
|
2690
|
+
x + tileImageFixBleedX, y + tileImageFixBleedY,
|
|
2691
|
+
x - tileImageFixBleedX + w, y - tileImageFixBleedY + h,
|
|
2692
|
+
color.rgbaInt(), additiveColor && additiveColor.rgbaInt());
|
|
2693
|
+
}
|
|
2694
|
+
else
|
|
2695
|
+
{
|
|
2696
|
+
glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
|
|
2697
|
+
x, y, x + w, y + h,
|
|
2698
|
+
color.rgbaInt(), additiveColor && additiveColor.rgbaInt());
|
|
2699
|
+
}
|
|
2597
2700
|
}
|
|
2598
2701
|
else
|
|
2599
2702
|
{
|
|
@@ -2805,16 +2908,15 @@ function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textA
|
|
|
2805
2908
|
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)
|
|
2806
2909
|
{
|
|
2807
2910
|
context.fillStyle = color.toString();
|
|
2808
|
-
context.lineWidth = lineWidth;
|
|
2809
2911
|
context.strokeStyle = lineColor.toString();
|
|
2912
|
+
context.lineWidth = lineWidth;
|
|
2810
2913
|
context.textAlign = textAlign;
|
|
2811
2914
|
context.font = size + 'px '+ font;
|
|
2812
2915
|
context.textBaseline = 'middle';
|
|
2813
2916
|
context.lineJoin = 'round';
|
|
2814
2917
|
|
|
2815
|
-
pos = pos.copy();
|
|
2816
|
-
|
|
2817
2918
|
const lines = (text+'').split('\n');
|
|
2919
|
+
pos = pos.copy();
|
|
2818
2920
|
pos.y -= (lines.length-1) * size/2; // center text vertically
|
|
2819
2921
|
lines.forEach(line=>
|
|
2820
2922
|
{
|
|
@@ -2884,7 +2986,10 @@ class FontImage
|
|
|
2884
2986
|
{
|
|
2885
2987
|
// load default font image
|
|
2886
2988
|
if (!engineFontImage)
|
|
2887
|
-
|
|
2989
|
+
{
|
|
2990
|
+
engineFontImage = new Image;
|
|
2991
|
+
engineFontImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
|
|
2992
|
+
}
|
|
2888
2993
|
|
|
2889
2994
|
this.image = image || engineFontImage;
|
|
2890
2995
|
this.tileSize = tileSize;
|
|
@@ -3515,16 +3620,15 @@ let audioContext = new AudioContext;
|
|
|
3515
3620
|
/** Master gain node for all audio to pass through
|
|
3516
3621
|
* @type {GainNode}
|
|
3517
3622
|
* @memberof Audio */
|
|
3518
|
-
let
|
|
3623
|
+
let audioMasterGain;
|
|
3519
3624
|
|
|
3520
3625
|
function audioInit()
|
|
3521
3626
|
{
|
|
3522
3627
|
if (!soundEnable || headlessMode) return;
|
|
3523
3628
|
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
audioGainNode.gain.value = soundVolume; // set starting value
|
|
3629
|
+
audioMasterGain = audioContext.createGain();
|
|
3630
|
+
audioMasterGain.connect(audioContext.destination);
|
|
3631
|
+
audioMasterGain.gain.value = soundVolume; // set starting value
|
|
3528
3632
|
}
|
|
3529
3633
|
|
|
3530
3634
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -3653,6 +3757,8 @@ class Sound
|
|
|
3653
3757
|
isLoading() { return !this.sampleChannels; }
|
|
3654
3758
|
}
|
|
3655
3759
|
|
|
3760
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
3761
|
+
|
|
3656
3762
|
/**
|
|
3657
3763
|
* Sound Wave Object - Stores a wave sound for later use and can be played positionally
|
|
3658
3764
|
* - this can be used to play wave, mp3, and ogg files
|
|
@@ -3704,59 +3810,7 @@ function playAudioFile(filename, volume=1, loop=false)
|
|
|
3704
3810
|
return new SoundWave(filename,0,0,0, s=>s.play(undefined, volume, 1, 1, loop));
|
|
3705
3811
|
}
|
|
3706
3812
|
|
|
3707
|
-
|
|
3708
|
-
* Music Object - Stores a zzfx music track for later use
|
|
3709
|
-
*
|
|
3710
|
-
* <a href=https://keithclark.github.io/ZzFXM/>Create music with the ZzFXM tracker.</a>
|
|
3711
|
-
* @example
|
|
3712
|
-
* // create some music
|
|
3713
|
-
* const music_example = new Music(
|
|
3714
|
-
* [
|
|
3715
|
-
* [ // instruments
|
|
3716
|
-
* [,0,400] // simple note
|
|
3717
|
-
* ],
|
|
3718
|
-
* [ // patterns
|
|
3719
|
-
* [ // pattern 1
|
|
3720
|
-
* [ // channel 0
|
|
3721
|
-
* 0, -1, // instrument 0, left speaker
|
|
3722
|
-
* 1, 0, 9, 1 // channel notes
|
|
3723
|
-
* ],
|
|
3724
|
-
* [ // channel 1
|
|
3725
|
-
* 0, 1, // instrument 0, right speaker
|
|
3726
|
-
* 0, 12, 17, -1 // channel notes
|
|
3727
|
-
* ]
|
|
3728
|
-
* ],
|
|
3729
|
-
* ],
|
|
3730
|
-
* [0, 0, 0, 0], // sequence, play pattern 0 four times
|
|
3731
|
-
* 90 // BPM
|
|
3732
|
-
* ]);
|
|
3733
|
-
*
|
|
3734
|
-
* // play the music
|
|
3735
|
-
* music_example.play();
|
|
3736
|
-
*/
|
|
3737
|
-
class Music extends Sound
|
|
3738
|
-
{
|
|
3739
|
-
/** Create a music object and cache the zzfx music samples for later use
|
|
3740
|
-
* @param {[Array, Array, Array, number]} zzfxMusic - Array of zzfx music parameters
|
|
3741
|
-
*/
|
|
3742
|
-
constructor(zzfxMusic)
|
|
3743
|
-
{
|
|
3744
|
-
super(undefined);
|
|
3745
|
-
|
|
3746
|
-
if (!soundEnable || headlessMode) return;
|
|
3747
|
-
this.randomness = 0;
|
|
3748
|
-
this.sampleChannels = zzfxM(...zzfxMusic);
|
|
3749
|
-
this.sampleRate = zzfxR;
|
|
3750
|
-
}
|
|
3751
|
-
|
|
3752
|
-
/** Play the music
|
|
3753
|
-
* @param {number} [volume=1] - How much to scale volume by
|
|
3754
|
-
* @param {boolean} [loop] - True if the music should loop
|
|
3755
|
-
* @return {AudioBufferSourceNode} - The audio source node
|
|
3756
|
-
*/
|
|
3757
|
-
playMusic(volume, loop=false)
|
|
3758
|
-
{ return super.play(undefined, volume, 1, 1, loop); }
|
|
3759
|
-
}
|
|
3813
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
3760
3814
|
|
|
3761
3815
|
/** Speak text with passed in settings
|
|
3762
3816
|
* @param {string} text - The text to speak
|
|
@@ -3828,7 +3882,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
3828
3882
|
// create and connect gain node
|
|
3829
3883
|
gainNode = gainNode || audioContext.createGain();
|
|
3830
3884
|
gainNode.gain.value = volume;
|
|
3831
|
-
gainNode.connect(
|
|
3885
|
+
gainNode.connect(audioMasterGain);
|
|
3832
3886
|
|
|
3833
3887
|
// connect source to stereo panner and gain
|
|
3834
3888
|
const pannerNode = new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)});
|
|
@@ -3848,7 +3902,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
3848
3902
|
}
|
|
3849
3903
|
|
|
3850
3904
|
///////////////////////////////////////////////////////////////////////////////
|
|
3851
|
-
// ZzFXMicro - Zuper Zmall Zound Zynth - v1.3.
|
|
3905
|
+
// ZzFXMicro - Zuper Zmall Zound Zynth - v1.3.2 by Frank Force
|
|
3852
3906
|
|
|
3853
3907
|
/** Generate and play a ZzFX sound
|
|
3854
3908
|
*
|
|
@@ -3890,21 +3944,45 @@ const zzfxR = 44100;
|
|
|
3890
3944
|
*/
|
|
3891
3945
|
function zzfxG
|
|
3892
3946
|
(
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3947
|
+
volume = 1,
|
|
3948
|
+
randomness = .05,
|
|
3949
|
+
frequency = 220,
|
|
3950
|
+
attack = 0,
|
|
3951
|
+
sustain = 0,
|
|
3952
|
+
release = .1,
|
|
3953
|
+
shape = 0,
|
|
3954
|
+
shapeCurve = 1,
|
|
3955
|
+
slide = 0,
|
|
3956
|
+
deltaSlide = 0,
|
|
3957
|
+
pitchJump = 0,
|
|
3958
|
+
pitchJumpTime = 0,
|
|
3959
|
+
repeatTime = 0,
|
|
3960
|
+
noise = 0,
|
|
3961
|
+
modulation = 0,
|
|
3962
|
+
bitCrush = 0,
|
|
3963
|
+
delay = 0,
|
|
3964
|
+
sustainVolume = 1,
|
|
3965
|
+
decay = 0,
|
|
3966
|
+
tremolo = 0,
|
|
3967
|
+
filter = 0
|
|
3898
3968
|
)
|
|
3899
3969
|
{
|
|
3900
|
-
// LJS Note: ZZFX modded so randomness is handled by Sound class
|
|
3901
|
-
|
|
3902
3970
|
// init parameters
|
|
3903
|
-
let
|
|
3971
|
+
let sampleRate = zzfxR,
|
|
3972
|
+
PI2 = PI*2,
|
|
3904
3973
|
startSlide = slide *= 500 * PI2 / sampleRate / sampleRate,
|
|
3905
3974
|
startFrequency = frequency *=
|
|
3906
|
-
|
|
3907
|
-
|
|
3975
|
+
(1 + rand(randomness,-randomness)) * PI2 / sampleRate,
|
|
3976
|
+
modOffset = 0, // modulation offset
|
|
3977
|
+
repeat = 0, // repeat offset
|
|
3978
|
+
crush = 0, // bit crush offset
|
|
3979
|
+
jump = 1, // pitch jump timer
|
|
3980
|
+
length, // sample length
|
|
3981
|
+
b = [], // sample buffer
|
|
3982
|
+
t = 0, // sample time
|
|
3983
|
+
i = 0, // sample index
|
|
3984
|
+
s = 0, // sample value
|
|
3985
|
+
f, // wave frequency
|
|
3908
3986
|
|
|
3909
3987
|
// biquad LP/HP filter
|
|
3910
3988
|
quality = 2, w = PI2 * abs(filter) * 2 / sampleRate,
|
|
@@ -3914,35 +3992,37 @@ function zzfxG
|
|
|
3914
3992
|
b1 = -(sign(filter) + cos) / a0, b2 = b0,
|
|
3915
3993
|
x2 = 0, x1 = 0, y2 = 0, y1 = 0;
|
|
3916
3994
|
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3995
|
+
// scale by sample rate
|
|
3996
|
+
const minAttack = 9; // prevent pop if attack is 0
|
|
3997
|
+
attack = attack * sampleRate || minAttack;
|
|
3998
|
+
decay *= sampleRate;
|
|
3999
|
+
sustain *= sampleRate;
|
|
4000
|
+
release *= sampleRate;
|
|
4001
|
+
delay *= sampleRate;
|
|
4002
|
+
deltaSlide *= 500 * PI2 / sampleRate**3;
|
|
4003
|
+
modulation *= PI2 / sampleRate;
|
|
4004
|
+
pitchJump *= PI2 / sampleRate;
|
|
4005
|
+
pitchJumpTime *= sampleRate;
|
|
4006
|
+
repeatTime = repeatTime * sampleRate | 0;
|
|
3928
4007
|
|
|
3929
4008
|
// generate waveform
|
|
3930
4009
|
for(length = attack + decay + sustain + release + delay | 0;
|
|
3931
|
-
i < length; b[i++] = s * volume)
|
|
4010
|
+
i < length; b[i++] = s * volume) // sample
|
|
3932
4011
|
{
|
|
3933
|
-
if (!(++
|
|
4012
|
+
if (!(++crush%(bitCrush*100|0))) // bit crush
|
|
3934
4013
|
{
|
|
3935
|
-
s = shape? shape>1? shape>2? shape>3?
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
1-
|
|
3940
|
-
Math.
|
|
4014
|
+
s = shape? shape>1? shape>2? shape>3? shape>4? // wave shape
|
|
4015
|
+
(t/PI2%1 < shapeCurve/2? 1 : -1) : // 5 square duty
|
|
4016
|
+
Math.sin(t**3) : // 4 noise
|
|
4017
|
+
Math.max(Math.min(Math.tan(t),1),-1): // 3 tan
|
|
4018
|
+
1-(2*t/PI2%2+2)%2: // 2 saw
|
|
4019
|
+
1-4*abs(Math.round(t/PI2)-t/PI2): // 1 triangle
|
|
4020
|
+
Math.sin(t); // 0 sin
|
|
3941
4021
|
|
|
3942
4022
|
s = (repeatTime ?
|
|
3943
4023
|
1 - tremolo + tremolo*Math.sin(PI2*i/repeatTime) // tremolo
|
|
3944
4024
|
: 1) *
|
|
3945
|
-
sign(s)*
|
|
4025
|
+
(shape>4?s:sign(s)*abs(s)**shapeCurve) * // shape curve
|
|
3946
4026
|
(i < attack ? i/attack : // attack
|
|
3947
4027
|
i < attack + decay ? // decay
|
|
3948
4028
|
1-((i-attack)/decay)*(1-sustainVolume) : // decay falloff
|
|
@@ -3957,136 +4037,32 @@ function zzfxG
|
|
|
3957
4037
|
(i<length-delay? 1 : (length-i)/delay) * // release delay
|
|
3958
4038
|
b[i-delay|0]/2/volume) : s; // sample delay
|
|
3959
4039
|
|
|
3960
|
-
if (filter)
|
|
4040
|
+
if (filter) // apply filter
|
|
3961
4041
|
s = y1 = b2*x2 + b1*(x2=x1) + b0*(x1=s) - a2*y2 - a1*(y2=y1);
|
|
3962
4042
|
}
|
|
3963
4043
|
|
|
3964
4044
|
f = (frequency += slide += deltaSlide) *// frequency
|
|
3965
|
-
Math.cos(modulation*
|
|
4045
|
+
Math.cos(modulation*modOffset++); // modulation
|
|
3966
4046
|
t += f + f*noise*Math.sin(i**5); // noise
|
|
3967
4047
|
|
|
3968
|
-
if (
|
|
4048
|
+
if (jump && ++jump > pitchJumpTime) // pitch jump
|
|
3969
4049
|
{
|
|
3970
4050
|
frequency += pitchJump; // apply pitch jump
|
|
3971
4051
|
startFrequency += pitchJump; // also apply to start
|
|
3972
|
-
|
|
4052
|
+
jump = 0; // stop pitch jump time
|
|
3973
4053
|
}
|
|
3974
4054
|
|
|
3975
|
-
if (repeatTime && !(++
|
|
4055
|
+
if (repeatTime && !(++repeat % repeatTime)) // repeat
|
|
3976
4056
|
{
|
|
3977
|
-
frequency = startFrequency;
|
|
3978
|
-
slide = startSlide;
|
|
3979
|
-
|
|
4057
|
+
frequency = startFrequency; // reset frequency
|
|
4058
|
+
slide = startSlide; // reset slide
|
|
4059
|
+
jump ||= 1; // reset pitch jump time
|
|
3980
4060
|
}
|
|
3981
4061
|
}
|
|
3982
4062
|
|
|
3983
|
-
return b;
|
|
4063
|
+
return b; // return sample buffer
|
|
3984
4064
|
}
|
|
3985
|
-
|
|
3986
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
3987
|
-
// ZzFX Music Renderer v2.0.3 by Keith Clark and Frank Force
|
|
3988
|
-
|
|
3989
|
-
/** Generate samples for a ZzFM song with given parameters
|
|
3990
|
-
* @param {Array} instruments - Array of ZzFX sound parameters
|
|
3991
|
-
* @param {Array} patterns - Array of pattern data
|
|
3992
|
-
* @param {Array} sequence - Array of pattern indexes
|
|
3993
|
-
* @param {number} [BPM] - Playback speed of the song in BPM
|
|
3994
|
-
* @return {Array} - Left and right channel sample data
|
|
3995
|
-
* @memberof Audio */
|
|
3996
|
-
function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
3997
|
-
{
|
|
3998
|
-
let i, j, k;
|
|
3999
|
-
let instrumentParameters;
|
|
4000
|
-
let note;
|
|
4001
|
-
let sample;
|
|
4002
|
-
let patternChannel;
|
|
4003
|
-
let notFirstBeat;
|
|
4004
|
-
let stop;
|
|
4005
|
-
let instrument;
|
|
4006
|
-
let attenuation;
|
|
4007
|
-
let outSampleOffset;
|
|
4008
|
-
let isSequenceEnd;
|
|
4009
|
-
let sampleOffset = 0;
|
|
4010
|
-
let nextSampleOffset;
|
|
4011
|
-
let sampleBuffer = [];
|
|
4012
|
-
let leftChannelBuffer = [];
|
|
4013
|
-
let rightChannelBuffer = [];
|
|
4014
|
-
let channelIndex = 0;
|
|
4015
|
-
let panning = 0;
|
|
4016
|
-
let hasMore = 1;
|
|
4017
|
-
let sampleCache = {};
|
|
4018
|
-
let beatLength = zzfxR / BPM * 60 >> 2;
|
|
4019
|
-
|
|
4020
|
-
// for each channel in order until there are no more
|
|
4021
|
-
for (; hasMore; channelIndex++) {
|
|
4022
|
-
|
|
4023
|
-
// reset current values
|
|
4024
|
-
sampleBuffer = [hasMore = notFirstBeat = outSampleOffset = 0];
|
|
4025
|
-
|
|
4026
|
-
// for each pattern in sequence
|
|
4027
|
-
sequence.forEach((patternIndex, sequenceIndex) => {
|
|
4028
|
-
// get pattern for current channel, use empty 1 note pattern if none found
|
|
4029
|
-
patternChannel = patterns[patternIndex][channelIndex] || [0, 0, 0];
|
|
4030
|
-
|
|
4031
|
-
// check if there are more channels
|
|
4032
|
-
hasMore |= patterns[patternIndex][channelIndex]&&1;
|
|
4033
|
-
|
|
4034
|
-
// get next offset, use the length of first channel
|
|
4035
|
-
nextSampleOffset = outSampleOffset + (patterns[patternIndex][0].length - 2 - (notFirstBeat?0:1)) * beatLength;
|
|
4036
|
-
// for each beat in pattern, plus one extra if end of sequence
|
|
4037
|
-
isSequenceEnd = sequenceIndex == sequence.length - 1;
|
|
4038
|
-
for (i = 2, k = outSampleOffset; i < patternChannel.length + isSequenceEnd; notFirstBeat = ++i) {
|
|
4039
|
-
|
|
4040
|
-
// <channel-note>
|
|
4041
|
-
note = patternChannel[i];
|
|
4042
|
-
|
|
4043
|
-
// stop if end, different instrument or new note
|
|
4044
|
-
stop = i == patternChannel.length + isSequenceEnd - 1 && isSequenceEnd ||
|
|
4045
|
-
instrument != (patternChannel[0] || 0) || note | 0;
|
|
4046
|
-
|
|
4047
|
-
// fill buffer with samples for previous beat, most cpu intensive part
|
|
4048
|
-
for (j = 0; j < beatLength && notFirstBeat;
|
|
4049
|
-
|
|
4050
|
-
// fade off attenuation at end of beat if stopping note, prevents clicking
|
|
4051
|
-
j++ > beatLength - 99 && stop && attenuation < 1? attenuation += 1 / 99 : 0
|
|
4052
|
-
) {
|
|
4053
|
-
// copy sample to stereo buffers with panning
|
|
4054
|
-
sample = (1 - attenuation) * sampleBuffer[sampleOffset++] / 2 || 0;
|
|
4055
|
-
leftChannelBuffer[k] = (leftChannelBuffer[k] || 0) - sample * panning + sample;
|
|
4056
|
-
rightChannelBuffer[k] = (rightChannelBuffer[k++] || 0) + sample * panning + sample;
|
|
4057
|
-
}
|
|
4058
|
-
|
|
4059
|
-
// set up for next note
|
|
4060
|
-
if (note) {
|
|
4061
|
-
// set attenuation
|
|
4062
|
-
attenuation = note % 1;
|
|
4063
|
-
panning = patternChannel[1] || 0;
|
|
4064
|
-
if (note |= 0) {
|
|
4065
|
-
// get cached sample
|
|
4066
|
-
sampleBuffer = sampleCache[
|
|
4067
|
-
[
|
|
4068
|
-
instrument = patternChannel[sampleOffset = 0] || 0,
|
|
4069
|
-
note
|
|
4070
|
-
]
|
|
4071
|
-
] = sampleCache[[instrument, note]] || (
|
|
4072
|
-
// add sample to cache
|
|
4073
|
-
instrumentParameters = [...instruments[instrument]],
|
|
4074
|
-
instrumentParameters[2] *= 2 ** ((note - 12) / 12),
|
|
4075
|
-
|
|
4076
|
-
// allow negative values to stop notes
|
|
4077
|
-
note > 0 ? zzfxG(...instrumentParameters) : []
|
|
4078
|
-
);
|
|
4079
|
-
}
|
|
4080
|
-
}
|
|
4081
|
-
}
|
|
4082
|
-
|
|
4083
|
-
// update the sample offset
|
|
4084
|
-
outSampleOffset = nextSampleOffset;
|
|
4085
|
-
});
|
|
4086
|
-
}
|
|
4087
|
-
|
|
4088
|
-
return [leftChannelBuffer, rightChannelBuffer];
|
|
4089
|
-
}
|
|
4065
|
+
|
|
4090
4066
|
/**
|
|
4091
4067
|
* LittleJS Tile Layer System
|
|
4092
4068
|
* - Caches arrays of tiles to off screen canvas for fast rendering
|
|
@@ -4643,7 +4619,12 @@ class ParticleEmitter extends EngineObject
|
|
|
4643
4619
|
else
|
|
4644
4620
|
this.destroy();
|
|
4645
4621
|
|
|
4646
|
-
|
|
4622
|
+
if (debugParticles)
|
|
4623
|
+
{
|
|
4624
|
+
// show emitter bounds
|
|
4625
|
+
const emitSize = typeof this.emitSize === 'number' ? vec2(this.emitSize) : this.emitSize;
|
|
4626
|
+
debugRect(this.pos, emitSize, '#0f0', 0, this.angle);
|
|
4627
|
+
}
|
|
4647
4628
|
}
|
|
4648
4629
|
|
|
4649
4630
|
/** Spawn one particle
|
|
@@ -5105,12 +5086,12 @@ function glPreRender()
|
|
|
5105
5086
|
|
|
5106
5087
|
// set vertex attributes
|
|
5107
5088
|
let offset = glAdditive = glBatchAdditive = 0;
|
|
5108
|
-
|
|
5089
|
+
const initVertexAttribArray = (name, type, typeSize, size)=>
|
|
5109
5090
|
{
|
|
5110
5091
|
const location = glContext.getAttribLocation(glShader, name);
|
|
5111
5092
|
const stride = typeSize && gl_INSTANCE_BYTE_STRIDE; // only if not geometry
|
|
5112
5093
|
const divisor = typeSize && 1; // only if not geometry
|
|
5113
|
-
const normalize = typeSize==1; // only if color
|
|
5094
|
+
const normalize = typeSize == 1; // only if color
|
|
5114
5095
|
glContext.enableVertexAttribArray(location);
|
|
5115
5096
|
glContext.vertexAttribPointer(location, size, type, normalize, stride, offset);
|
|
5116
5097
|
glContext.vertexAttribDivisor(location, divisor);
|
|
@@ -5209,14 +5190,14 @@ function glCreateTexture(image)
|
|
|
5209
5190
|
const texture = glContext.createTexture();
|
|
5210
5191
|
glContext.bindTexture(glContext.TEXTURE_2D, texture);
|
|
5211
5192
|
if (image && image.width)
|
|
5212
|
-
|
|
5193
|
+
glSetTextureData(texture, image);
|
|
5213
5194
|
else
|
|
5214
5195
|
{
|
|
5215
5196
|
// create a white texture
|
|
5216
5197
|
const whitePixel = new Uint8Array([255, 255, 255, 255]);
|
|
5217
5198
|
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, 1, 1, 0, glContext.RGBA, glContext.UNSIGNED_BYTE, whitePixel);
|
|
5218
5199
|
}
|
|
5219
|
-
|
|
5200
|
+
|
|
5220
5201
|
// use point filtering for pixelated rendering
|
|
5221
5202
|
const filter = tilesPixelated ? glContext.NEAREST : glContext.LINEAR;
|
|
5222
5203
|
glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, filter);
|
|
@@ -5224,6 +5205,18 @@ function glCreateTexture(image)
|
|
|
5224
5205
|
return texture;
|
|
5225
5206
|
}
|
|
5226
5207
|
|
|
5208
|
+
/** Set WebGL texture data from an image
|
|
5209
|
+
* @param {WebGLTexture} texture
|
|
5210
|
+
* @param {HTMLImageElement} image
|
|
5211
|
+
* @memberof WebGL */
|
|
5212
|
+
function glSetTextureData(texture, image)
|
|
5213
|
+
{
|
|
5214
|
+
// build the texture
|
|
5215
|
+
ASSERT(!!image && image.width > 0, 'Invalid image data.');
|
|
5216
|
+
glContext.bindTexture(glContext.TEXTURE_2D, texture);
|
|
5217
|
+
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, image);
|
|
5218
|
+
}
|
|
5219
|
+
|
|
5227
5220
|
/** Draw all sprites and clear out the buffer, called automatically by the system whenever necessary
|
|
5228
5221
|
* @memberof WebGL */
|
|
5229
5222
|
function glFlush()
|
|
@@ -5277,10 +5270,10 @@ function glSetAntialias(antialias=true)
|
|
|
5277
5270
|
* @param {Number} uv0Y
|
|
5278
5271
|
* @param {Number} uv1X
|
|
5279
5272
|
* @param {Number} uv1Y
|
|
5280
|
-
* @param {Number} rgba
|
|
5281
|
-
* @param {Number} [rgbaAdditive=0]
|
|
5273
|
+
* @param {Number} [rgba=-1] - white is -1
|
|
5274
|
+
* @param {Number} [rgbaAdditive=0] - black is 0
|
|
5282
5275
|
* @memberof WebGL */
|
|
5283
|
-
function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdditive=0)
|
|
5276
|
+
function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba=-1, rgbaAdditive=0)
|
|
5284
5277
|
{
|
|
5285
5278
|
ASSERT(typeof rgba == 'number' && typeof rgbaAdditive == 'number', 'invalid color');
|
|
5286
5279
|
|
|
@@ -5333,7 +5326,7 @@ const engineName = 'LittleJS';
|
|
|
5333
5326
|
* @type {string}
|
|
5334
5327
|
* @default
|
|
5335
5328
|
* @memberof Engine */
|
|
5336
|
-
const engineVersion = '1.11.
|
|
5329
|
+
const engineVersion = '1.11.17';
|
|
5337
5330
|
|
|
5338
5331
|
/** Frames per second to update
|
|
5339
5332
|
* @type {number}
|
|
@@ -5421,16 +5414,11 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5421
5414
|
ASSERT(Array.isArray(imageSources), 'pass in images as array');
|
|
5422
5415
|
|
|
5423
5416
|
// allow passing in empty functions
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
gameUpdatePost = ()=>{};
|
|
5430
|
-
if (!gameRender)
|
|
5431
|
-
gameRender = ()=>{};
|
|
5432
|
-
if (!gameRenderPost)
|
|
5433
|
-
gameRenderPost = ()=>{};
|
|
5417
|
+
gameInit ||= ()=>{};
|
|
5418
|
+
gameUpdate ||= ()=>{};
|
|
5419
|
+
gameUpdatePost ||= ()=>{};
|
|
5420
|
+
gameRender ||= ()=>{};
|
|
5421
|
+
gameRenderPost ||= ()=>{};
|
|
5434
5422
|
|
|
5435
5423
|
// Called automatically by engine to setup render system
|
|
5436
5424
|
function enginePreRender()
|
|
@@ -5457,11 +5445,13 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5457
5445
|
const debugSpeedUp = debug && keyIsDown('Equal'); // +
|
|
5458
5446
|
const debugSpeedDown = debug && keyIsDown('Minus'); // -
|
|
5459
5447
|
if (debug) // +/- to speed/slow time
|
|
5460
|
-
frameTimeDeltaMS *= debugSpeedUp ?
|
|
5448
|
+
frameTimeDeltaMS *= debugSpeedUp ? 10 : debugSpeedDown ? .1 : 1;
|
|
5461
5449
|
timeReal += frameTimeDeltaMS / 1e3;
|
|
5462
5450
|
frameTimeBufferMS += paused ? 0 : frameTimeDeltaMS;
|
|
5463
5451
|
if (!debugSpeedUp)
|
|
5464
|
-
frameTimeBufferMS = min(frameTimeBufferMS, 50); // clamp
|
|
5452
|
+
frameTimeBufferMS = min(frameTimeBufferMS, 50); // clamp min framerate
|
|
5453
|
+
if (debug && debugVideoCaptureIsActive())
|
|
5454
|
+
frameTimeBufferMS = 0; // disable time smoothing when capturing video
|
|
5465
5455
|
|
|
5466
5456
|
updateCanvas();
|
|
5467
5457
|
|
|
@@ -5540,6 +5530,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5540
5530
|
}
|
|
5541
5531
|
}
|
|
5542
5532
|
|
|
5533
|
+
debugVideoCaptureUpdate();
|
|
5543
5534
|
requestAnimationFrame(engineUpdate);
|
|
5544
5535
|
}
|
|
5545
5536
|
|
|
@@ -5587,11 +5578,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5587
5578
|
|
|
5588
5579
|
// setup html
|
|
5589
5580
|
const styleRoot =
|
|
5590
|
-
'margin:0;
|
|
5591
|
-
'width:100vw;height:100vh;' + // fill the window
|
|
5592
|
-
'display:flex;' + // use flexbox
|
|
5593
|
-
'align-items:center;' + // horizontal center
|
|
5594
|
-
'justify-content:center;' + // vertical center
|
|
5581
|
+
'margin:0;' + // fill the window
|
|
5595
5582
|
'background:#000;' + // set background color
|
|
5596
5583
|
(canvasPixelated ? 'image-rendering:pixelated;' : '') + // pixel art
|
|
5597
5584
|
'user-select:none;' + // prevent hold to select
|
|
@@ -5614,7 +5601,8 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5614
5601
|
overlayContext = overlayCanvas.getContext('2d');
|
|
5615
5602
|
|
|
5616
5603
|
// set canvas style
|
|
5617
|
-
const styleCanvas = 'position:absolute'
|
|
5604
|
+
const styleCanvas = 'position:absolute;'+ // allow canvases to overlap
|
|
5605
|
+
'top:50%;left:50%;transform:translate(-50%,-50%)'; // center on screen
|
|
5618
5606
|
mainCanvas.style.cssText = overlayCanvas.style.cssText = styleCanvas;
|
|
5619
5607
|
if (glCanvas)
|
|
5620
5608
|
glCanvas.style.cssText = styleCanvas;
|
|
@@ -5625,12 +5613,12 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5625
5613
|
new Promise(resolve =>
|
|
5626
5614
|
{
|
|
5627
5615
|
const image = new Image;
|
|
5628
|
-
image.crossOrigin = 'anonymous';
|
|
5629
5616
|
image.onerror = image.onload = ()=>
|
|
5630
5617
|
{
|
|
5631
5618
|
textureInfos[textureIndex] = new TextureInfo(image);
|
|
5632
5619
|
resolve();
|
|
5633
5620
|
}
|
|
5621
|
+
image.crossOrigin = 'anonymous';
|
|
5634
5622
|
image.src = src;
|
|
5635
5623
|
})
|
|
5636
5624
|
);
|