littlejsengine 1.14.28 → 1.15.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.
- package/dist/littlejs.d.ts +85 -10
- package/dist/littlejs.esm.js +212 -18
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +211 -18
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +211 -18
- package/examples/electron/index.html +2 -2
- package/examples/index.html +1 -0
- package/examples/shorts/base.html +2 -2
- package/examples/shorts/song.mp3 +0 -0
- package/examples/shorts/video.webm +0 -0
- package/examples/shorts/videoPlayer.js +33 -0
- package/examples/starter/index.html +3 -3
- package/package.json +1 -1
- package/plugins/pluginExport.js +1 -0
- package/plugins/uiSystem.js +174 -0
- package/src/engine.js +5 -5
- package/src/engineDraw.js +1 -1
- package/src/engineParticles.js +3 -0
- package/src/engineUtilities.js +28 -12
package/dist/littlejs.js
CHANGED
|
@@ -33,7 +33,7 @@ const engineName = 'LittleJS';
|
|
|
33
33
|
* @type {string}
|
|
34
34
|
* @default
|
|
35
35
|
* @memberof Engine */
|
|
36
|
-
const engineVersion = '1.15.
|
|
36
|
+
const engineVersion = '1.15.2';
|
|
37
37
|
|
|
38
38
|
/** Frames per second to update
|
|
39
39
|
* @type {number}
|
|
@@ -134,7 +134,7 @@ function engineAddPlugin(update, render, glContextLost, glContextRestored)
|
|
|
134
134
|
|
|
135
135
|
/**
|
|
136
136
|
* @callback GameInitCallback - Called after the engine starts, can be async
|
|
137
|
-
* @
|
|
137
|
+
* @return {void|Promise<void>}
|
|
138
138
|
* @memberof Engine
|
|
139
139
|
*/
|
|
140
140
|
/**
|
|
@@ -490,10 +490,10 @@ function engineObjectsDestroy()
|
|
|
490
490
|
}
|
|
491
491
|
|
|
492
492
|
/** Collects all object within a given area
|
|
493
|
-
* @param {Vector2} [pos]
|
|
494
|
-
* @param {Vector2|number} [size]
|
|
493
|
+
* @param {Vector2} [pos] - Center of test area, or undefined for all objects
|
|
494
|
+
* @param {Vector2|number} [size] - Radius of circle if float, rectangle size if Vector2
|
|
495
495
|
* @param {Array<EngineObject>} [objects=engineObjects] - List of objects to check
|
|
496
|
-
* @return {Array<EngineObject>}
|
|
496
|
+
* @return {Array<EngineObject>} - List of collected objects
|
|
497
497
|
* @memberof Engine */
|
|
498
498
|
function engineObjectsCollect(pos, size, objects=engineObjects)
|
|
499
499
|
{
|
|
@@ -1555,7 +1555,7 @@ function percentLerp(value, percentA, percentB, lerpA, lerpB)
|
|
|
1555
1555
|
* @param {number} valueA
|
|
1556
1556
|
* @param {number} valueB
|
|
1557
1557
|
* @param {number} [wrapSize]
|
|
1558
|
-
* @
|
|
1558
|
+
* @return {number}
|
|
1559
1559
|
* @memberof Utilities */
|
|
1560
1560
|
function distanceWrap(valueA, valueB, wrapSize=1)
|
|
1561
1561
|
{ const d = (valueA - valueB) % wrapSize; return d*2 % wrapSize - d; }
|
|
@@ -1565,7 +1565,7 @@ function distanceWrap(valueA, valueB, wrapSize=1)
|
|
|
1565
1565
|
* @param {number} valueB
|
|
1566
1566
|
* @param {number} percent
|
|
1567
1567
|
* @param {number} [wrapSize]
|
|
1568
|
-
* @
|
|
1568
|
+
* @return {number}
|
|
1569
1569
|
* @memberof Utilities */
|
|
1570
1570
|
function lerpWrap(valueA, valueB, percent, wrapSize=1)
|
|
1571
1571
|
{
|
|
@@ -1577,7 +1577,7 @@ function lerpWrap(valueA, valueB, percent, wrapSize=1)
|
|
|
1577
1577
|
/** Returns signed wrapped distance between the two angles passed in
|
|
1578
1578
|
* @param {number} angleA
|
|
1579
1579
|
* @param {number} angleB
|
|
1580
|
-
* @
|
|
1580
|
+
* @return {number}
|
|
1581
1581
|
* @memberof Utilities */
|
|
1582
1582
|
function distanceAngle(angleA, angleB) { return distanceWrap(angleA, angleB, 2*PI); }
|
|
1583
1583
|
|
|
@@ -1585,7 +1585,7 @@ function distanceAngle(angleA, angleB) { return distanceWrap(angleA, angleB, 2*P
|
|
|
1585
1585
|
* @param {number} angleA
|
|
1586
1586
|
* @param {number} angleB
|
|
1587
1587
|
* @param {number} percent
|
|
1588
|
-
* @
|
|
1588
|
+
* @return {number}
|
|
1589
1589
|
* @memberof Utilities */
|
|
1590
1590
|
function lerpAngle(angleA, angleB, percent) { return lerpWrap(angleA, angleB, percent, 2*PI); }
|
|
1591
1591
|
|
|
@@ -2554,11 +2554,14 @@ const MAGENTA = debugProtectConstant(rgb(1,0,1));
|
|
|
2554
2554
|
class Timer
|
|
2555
2555
|
{
|
|
2556
2556
|
/** Create a timer object set time passed in
|
|
2557
|
-
* @param {number} [timeLeft] - How much time left before the timer
|
|
2558
|
-
|
|
2557
|
+
* @param {number} [timeLeft] - How much time left before the timer
|
|
2558
|
+
* @param {boolean} [useRealTime] - Should the timer keep running even when the game is paused? (useful for UI) */
|
|
2559
|
+
constructor(timeLeft, useRealTime=false)
|
|
2559
2560
|
{
|
|
2560
2561
|
ASSERT(timeLeft === undefined || isNumber(timeLeft), 'Constructed Timer is invalid.', timeLeft);
|
|
2561
|
-
this.
|
|
2562
|
+
this.useRealTime = useRealTime;
|
|
2563
|
+
const globalTime = this.getGlobalTime();
|
|
2564
|
+
this.time = timeLeft === undefined ? undefined : globalTime + timeLeft;
|
|
2562
2565
|
this.setTime = timeLeft;
|
|
2563
2566
|
}
|
|
2564
2567
|
|
|
@@ -2567,10 +2570,19 @@ class Timer
|
|
|
2567
2570
|
set(timeLeft=0)
|
|
2568
2571
|
{
|
|
2569
2572
|
ASSERT(isNumber(timeLeft), 'Timer is invalid.', timeLeft);
|
|
2570
|
-
|
|
2573
|
+
const globalTime = this.getGlobalTime();
|
|
2574
|
+
this.time = globalTime + timeLeft;
|
|
2571
2575
|
this.setTime = timeLeft;
|
|
2572
2576
|
}
|
|
2573
2577
|
|
|
2578
|
+
/** Set if the timer should keep running even when the game is paused
|
|
2579
|
+
* @param {boolean} [useRealTime] */
|
|
2580
|
+
setUseRealTime(useRealTime=true)
|
|
2581
|
+
{
|
|
2582
|
+
ASSERT(!this.isSet(), 'Cannot change global time setting while timer is set.');
|
|
2583
|
+
this.useRealTime = useRealTime;
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2574
2586
|
/** Unset the timer */
|
|
2575
2587
|
unset() { this.time = undefined; }
|
|
2576
2588
|
|
|
@@ -2580,24 +2592,28 @@ class Timer
|
|
|
2580
2592
|
|
|
2581
2593
|
/** Returns true if set and has not elapsed
|
|
2582
2594
|
* @return {boolean} */
|
|
2583
|
-
active() { return
|
|
2595
|
+
active() { return this.getGlobalTime() < this.time; }
|
|
2584
2596
|
|
|
2585
2597
|
/** Returns true if set and elapsed
|
|
2586
2598
|
* @return {boolean} */
|
|
2587
|
-
elapsed() { return
|
|
2599
|
+
elapsed() { return this.getGlobalTime() >= this.time; }
|
|
2588
2600
|
|
|
2589
2601
|
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
2590
2602
|
* @return {number} */
|
|
2591
|
-
get() { return this.isSet()?
|
|
2603
|
+
get() { return this.isSet()? this.getGlobalTime() - this.time : 0; }
|
|
2592
2604
|
|
|
2593
2605
|
/** Get percentage elapsed based on time it was set to, returns 0 if not set
|
|
2594
2606
|
* @return {number} */
|
|
2595
|
-
getPercent() { return this.isSet()? 1-percent(this.time -
|
|
2607
|
+
getPercent() { return this.isSet()? 1-percent(this.time - this.getGlobalTime(), 0, this.setTime) : 0; }
|
|
2596
2608
|
|
|
2597
2609
|
/** Get the time this timer was set to, returns 0 if not set
|
|
2598
2610
|
* @return {number} */
|
|
2599
2611
|
getSetTime() { return this.isSet() ? this.setTime : 0; }
|
|
2600
2612
|
|
|
2613
|
+
/** Get the current global time this timer is based on
|
|
2614
|
+
* @return {number} */
|
|
2615
|
+
getGlobalTime() { return this.useRealTime ? timeReal : time; }
|
|
2616
|
+
|
|
2601
2617
|
/** Returns this timer expressed as a string
|
|
2602
2618
|
* @return {string} */
|
|
2603
2619
|
toString() { return this.isSet() ? abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ) : 'unset'; }
|
|
@@ -3991,7 +4007,7 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
|
|
|
3991
4007
|
{
|
|
3992
4008
|
// normal canvas 2D rendering method (slower)
|
|
3993
4009
|
++drawCount;
|
|
3994
|
-
size = new Vector2(size.x, -size.y); //
|
|
4010
|
+
size = new Vector2(size.x, -size.y); // flip upside down sprites
|
|
3995
4011
|
drawCanvas2D(pos, size, angle, mirror, (context)=>
|
|
3996
4012
|
{
|
|
3997
4013
|
if (textureInfo)
|
|
@@ -6882,6 +6898,9 @@ class ParticleEmitter extends EngineObject
|
|
|
6882
6898
|
this.previousPos = this.pos.copy();
|
|
6883
6899
|
}
|
|
6884
6900
|
|
|
6901
|
+
/** Emitters do not have physics */
|
|
6902
|
+
updatePhysics() {}
|
|
6903
|
+
|
|
6885
6904
|
/** Update the emitter to spawn particles, called automatically by engine once each frame */
|
|
6886
6905
|
update()
|
|
6887
6906
|
{
|
|
@@ -8735,11 +8754,16 @@ class UISystemPlugin
|
|
|
8735
8754
|
// reset hover object at start of update
|
|
8736
8755
|
uiSystem.lastHoverObject = uiSystem.hoverObject;
|
|
8737
8756
|
uiSystem.hoverObject = undefined;
|
|
8757
|
+
|
|
8758
|
+
// update in reverse order so topmost objects get priority
|
|
8738
8759
|
for (let i = uiSystem.uiObjects.length; i--;)
|
|
8739
8760
|
{
|
|
8740
8761
|
const o = uiSystem.uiObjects[i];
|
|
8741
8762
|
o.parent || updateObject(o)
|
|
8742
8763
|
}
|
|
8764
|
+
|
|
8765
|
+
// remove destroyed objects
|
|
8766
|
+
uiSystem.uiObjects = uiSystem.uiObjects.filter(o=>!o.destroyed);
|
|
8743
8767
|
}
|
|
8744
8768
|
function uiRender()
|
|
8745
8769
|
{
|
|
@@ -8909,6 +8933,15 @@ class UISystemPlugin
|
|
|
8909
8933
|
p.x -= sInv*mainCanvasSize.x/2;
|
|
8910
8934
|
return p;
|
|
8911
8935
|
}
|
|
8936
|
+
|
|
8937
|
+
/** Destroy and remove all objects
|
|
8938
|
+
* @memberof Engine */
|
|
8939
|
+
destroyObjects()
|
|
8940
|
+
{
|
|
8941
|
+
for (const o of this.uiObjects)
|
|
8942
|
+
o.parent || o.destroy();
|
|
8943
|
+
this.uiObjects = this.uiObjects.filter(o=>!o.destroyed);
|
|
8944
|
+
}
|
|
8912
8945
|
}
|
|
8913
8946
|
|
|
8914
8947
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -9010,6 +9043,22 @@ class UIObject
|
|
|
9010
9043
|
child.parent = undefined;
|
|
9011
9044
|
}
|
|
9012
9045
|
|
|
9046
|
+
|
|
9047
|
+
/** Destroy this object, destroy its children, detach its parent, and mark it for removal */
|
|
9048
|
+
destroy()
|
|
9049
|
+
{
|
|
9050
|
+
if (this.destroyed)
|
|
9051
|
+
return;
|
|
9052
|
+
|
|
9053
|
+
// disconnect from parent and destroy children
|
|
9054
|
+
this.destroyed = 1;
|
|
9055
|
+
this.parent && this.parent.removeChild(this);
|
|
9056
|
+
for (const child of this.children)
|
|
9057
|
+
{
|
|
9058
|
+
child.parent = 0;
|
|
9059
|
+
child.destroy();
|
|
9060
|
+
}
|
|
9061
|
+
}
|
|
9013
9062
|
/** Check if the mouse is overlapping a box in screen space
|
|
9014
9063
|
* @return {boolean} - True if overlapping
|
|
9015
9064
|
*/
|
|
@@ -9389,6 +9438,150 @@ class UIScrollbar extends UIObject
|
|
|
9389
9438
|
uiSystem.drawText(this.text, this.pos, textSize,
|
|
9390
9439
|
this.textColor, 0, undefined, this.align, this.font, this.fontStyle, true, this.textShadow);
|
|
9391
9440
|
}
|
|
9441
|
+
}
|
|
9442
|
+
|
|
9443
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
9444
|
+
/**
|
|
9445
|
+
* VideoPlayerUIObject - A UI object that plays video
|
|
9446
|
+
* @extends UIObject
|
|
9447
|
+
* @example
|
|
9448
|
+
* // Create a video player UI object
|
|
9449
|
+
* const video = new VideoPlayerUIObject(vec2(400, 300), vec2(320, 240), 'cutscene.mp4', true);
|
|
9450
|
+
* video.play();
|
|
9451
|
+
* @memberof UISystem
|
|
9452
|
+
*/
|
|
9453
|
+
class UIVideo extends UIObject
|
|
9454
|
+
{
|
|
9455
|
+
/** Create a video player UI object
|
|
9456
|
+
* @param {Vector2} [pos]
|
|
9457
|
+
* @param {Vector2} [size]
|
|
9458
|
+
* @param {string} src - Video file path or URL
|
|
9459
|
+
* @param {boolean} [autoplay=false] - Start playing immediately?
|
|
9460
|
+
* @param {boolean} [loop=false] - Loop the video?
|
|
9461
|
+
* @param {number} [volume=1] - Volume percent scaled by global volume (0-1)
|
|
9462
|
+
*/
|
|
9463
|
+
constructor(pos, size, src, autoplay=false, loop=false, volume=1)
|
|
9464
|
+
{
|
|
9465
|
+
super(pos, size || vec2());
|
|
9466
|
+
|
|
9467
|
+
ASSERT(isString(src), 'video src must be a string');
|
|
9468
|
+
ASSERT(isNumber(volume), 'video volume must be a number');
|
|
9469
|
+
|
|
9470
|
+
this.color = BLACK; // default to black background
|
|
9471
|
+
this.cornerRadius = 0; // default to no corner radius
|
|
9472
|
+
|
|
9473
|
+
/** @property {float} - The video volume */
|
|
9474
|
+
this.volume = volume;
|
|
9475
|
+
|
|
9476
|
+
// create video element
|
|
9477
|
+
/** @property {HTMLVideoElement} - The video player */
|
|
9478
|
+
this.video = document.createElement('video');
|
|
9479
|
+
this.video.loop = loop;
|
|
9480
|
+
this.video.volume = clamp(volume * soundVolume);
|
|
9481
|
+
this.video.muted = !soundEnable;
|
|
9482
|
+
this.video.style.display = 'none';
|
|
9483
|
+
this.video.src = src;
|
|
9484
|
+
document.body.appendChild(this.video);
|
|
9485
|
+
autoplay && this.play();
|
|
9486
|
+
}
|
|
9487
|
+
|
|
9488
|
+
/** Play or resume the video
|
|
9489
|
+
* @return {Promise} Promise that resolves when playback starts */
|
|
9490
|
+
play()
|
|
9491
|
+
{
|
|
9492
|
+
// try to play the video, catch any errors (autoplay may be blocked)
|
|
9493
|
+
const promise = this.video.play();
|
|
9494
|
+
promise?.catch(()=>{});
|
|
9495
|
+
return promise;
|
|
9496
|
+
}
|
|
9497
|
+
|
|
9498
|
+
/** Pause the video */
|
|
9499
|
+
pause() { this.video.pause(); }
|
|
9500
|
+
|
|
9501
|
+
/** Stop and reset the video */
|
|
9502
|
+
stop() { this.video.pause(); this.video.currentTime = 0; }
|
|
9503
|
+
|
|
9504
|
+
/** Check if video is currently loading
|
|
9505
|
+
* @return {boolean} */
|
|
9506
|
+
isLoadng()
|
|
9507
|
+
{ return this.video.readyState < this.video.HAVE_CURRENT_DATA; }
|
|
9508
|
+
|
|
9509
|
+
/** Check if video is currently paused
|
|
9510
|
+
* @return {boolean} */
|
|
9511
|
+
isPaused() { return this.video.paused; }
|
|
9512
|
+
|
|
9513
|
+
/** Check if video is currently playing
|
|
9514
|
+
* @return {boolean} */
|
|
9515
|
+
isPlaying()
|
|
9516
|
+
{ return !this.isPaused() && !this.hasEnded() && !this.isLoadng(); }
|
|
9517
|
+
|
|
9518
|
+
/** Check if video has ended playing
|
|
9519
|
+
* @return {boolean} */
|
|
9520
|
+
hasEnded() { return this.video.ended; }
|
|
9521
|
+
|
|
9522
|
+
/** Set volume (0-1)
|
|
9523
|
+
* @param {number} volume - Volume level (0-1) */
|
|
9524
|
+
setVolume(volume)
|
|
9525
|
+
{
|
|
9526
|
+
this.volume = volume;
|
|
9527
|
+
this.video.volume = clamp(volume * soundVolume);
|
|
9528
|
+
}
|
|
9529
|
+
|
|
9530
|
+
/** Set playback speed
|
|
9531
|
+
* @param {number} rate - Playback rate multiplier */
|
|
9532
|
+
setPlaybackRate(rate) { this.video.playbackRate = rate; }
|
|
9533
|
+
|
|
9534
|
+
/** Get current time in seconds
|
|
9535
|
+
* @return {number} Current playback time */
|
|
9536
|
+
getCurrentTime() { return this.video.currentTime || 0; }
|
|
9537
|
+
|
|
9538
|
+
/** Get duration in seconds
|
|
9539
|
+
* @return {number} Total video duration */
|
|
9540
|
+
getDuration() { return this.video.duration || 0; }
|
|
9541
|
+
|
|
9542
|
+
/** Get the native video dimensions
|
|
9543
|
+
* @return {Vector2} Video dimensions (may be 0,0 if metadata not loaded) */
|
|
9544
|
+
getVideoSize()
|
|
9545
|
+
{ return vec2(this.video.videoWidth, this.video.videoHeight); }
|
|
9546
|
+
|
|
9547
|
+
/** Seek to time in seconds
|
|
9548
|
+
* @param {number} time - Time in seconds to seek to */
|
|
9549
|
+
setTime(time)
|
|
9550
|
+
{ this.video.currentTime = clamp(time, 0, this.getDuration()); }
|
|
9551
|
+
|
|
9552
|
+
update()
|
|
9553
|
+
{
|
|
9554
|
+
super.update();
|
|
9555
|
+
|
|
9556
|
+
// update volume based on global sound volume
|
|
9557
|
+
this.video.volume = clamp(this.volume * soundVolume);
|
|
9558
|
+
}
|
|
9559
|
+
|
|
9560
|
+
/** Render video to UI canvas */
|
|
9561
|
+
render()
|
|
9562
|
+
{
|
|
9563
|
+
super.render();
|
|
9564
|
+
|
|
9565
|
+
if (this.isLoadng())
|
|
9566
|
+
return;
|
|
9567
|
+
const context = uiSystem.uiContext;
|
|
9568
|
+
const s = this.size;
|
|
9569
|
+
context.save();
|
|
9570
|
+
context.translate(this.pos.x, this.pos.y);
|
|
9571
|
+
context.drawImage(this.video, -s.x/2, -s.y/2, s.x, s.y);
|
|
9572
|
+
context.restore();
|
|
9573
|
+
}
|
|
9574
|
+
|
|
9575
|
+
/** Clean up video on destroy */
|
|
9576
|
+
destroy()
|
|
9577
|
+
{
|
|
9578
|
+
if (this.destroyed)
|
|
9579
|
+
return;
|
|
9580
|
+
|
|
9581
|
+
this.video.pause();
|
|
9582
|
+
this.video.remove();
|
|
9583
|
+
super.destroy();
|
|
9584
|
+
}
|
|
9392
9585
|
}
|
|
9393
9586
|
/**
|
|
9394
9587
|
* LittleJS Box2D Physics Plugin
|