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.d.ts
CHANGED
|
@@ -105,7 +105,7 @@ declare module "littlejsengine" {
|
|
|
105
105
|
export function setPaused(isPaused?: boolean): void;
|
|
106
106
|
/**
|
|
107
107
|
* @callback GameInitCallback - Called after the engine starts, can be async
|
|
108
|
-
* @
|
|
108
|
+
* @return {void|Promise<void>}
|
|
109
109
|
* @memberof Engine
|
|
110
110
|
*/
|
|
111
111
|
/**
|
|
@@ -139,10 +139,10 @@ declare module "littlejsengine" {
|
|
|
139
139
|
* @memberof Engine */
|
|
140
140
|
export function engineObjectsDestroy(): void;
|
|
141
141
|
/** Collects all object within a given area
|
|
142
|
-
* @param {Vector2} [pos]
|
|
143
|
-
* @param {Vector2|number} [size]
|
|
142
|
+
* @param {Vector2} [pos] - Center of test area, or undefined for all objects
|
|
143
|
+
* @param {Vector2|number} [size] - Radius of circle if float, rectangle size if Vector2
|
|
144
144
|
* @param {Array<EngineObject>} [objects=engineObjects] - List of objects to check
|
|
145
|
-
* @return {Array<EngineObject>}
|
|
145
|
+
* @return {Array<EngineObject>} - List of collected objects
|
|
146
146
|
* @memberof Engine */
|
|
147
147
|
export function engineObjectsCollect(pos?: Vector2, size?: Vector2 | number, objects?: Array<EngineObject>): Array<EngineObject>;
|
|
148
148
|
/**
|
|
@@ -813,7 +813,7 @@ declare module "littlejsengine" {
|
|
|
813
813
|
* @param {number} valueA
|
|
814
814
|
* @param {number} valueB
|
|
815
815
|
* @param {number} [wrapSize]
|
|
816
|
-
* @
|
|
816
|
+
* @return {number}
|
|
817
817
|
* @memberof Utilities */
|
|
818
818
|
export function distanceWrap(valueA: number, valueB: number, wrapSize?: number): number;
|
|
819
819
|
/** Linearly interpolates between values passed in with wrapping
|
|
@@ -821,20 +821,20 @@ declare module "littlejsengine" {
|
|
|
821
821
|
* @param {number} valueB
|
|
822
822
|
* @param {number} percent
|
|
823
823
|
* @param {number} [wrapSize]
|
|
824
|
-
* @
|
|
824
|
+
* @return {number}
|
|
825
825
|
* @memberof Utilities */
|
|
826
826
|
export function lerpWrap(valueA: number, valueB: number, percent: number, wrapSize?: number): number;
|
|
827
827
|
/** Returns signed wrapped distance between the two angles passed in
|
|
828
828
|
* @param {number} angleA
|
|
829
829
|
* @param {number} angleB
|
|
830
|
-
* @
|
|
830
|
+
* @return {number}
|
|
831
831
|
* @memberof Utilities */
|
|
832
832
|
export function distanceAngle(angleA: number, angleB: number): number;
|
|
833
833
|
/** Linearly interpolates between the angles passed in with wrapping
|
|
834
834
|
* @param {number} angleA
|
|
835
835
|
* @param {number} angleB
|
|
836
836
|
* @param {number} percent
|
|
837
|
-
* @
|
|
837
|
+
* @return {number}
|
|
838
838
|
* @memberof Utilities */
|
|
839
839
|
export function lerpAngle(angleA: number, angleB: number, percent: number): number;
|
|
840
840
|
/** Linearly interpolates between values passed in using percent
|
|
@@ -1229,13 +1229,18 @@ declare module "littlejsengine" {
|
|
|
1229
1229
|
*/
|
|
1230
1230
|
export class Timer {
|
|
1231
1231
|
/** Create a timer object set time passed in
|
|
1232
|
-
* @param {number} [timeLeft] - How much time left before the timer
|
|
1233
|
-
|
|
1232
|
+
* @param {number} [timeLeft] - How much time left before the timer
|
|
1233
|
+
* @param {boolean} [useRealTime] - Should the timer keep running even when the game is paused? (useful for UI) */
|
|
1234
|
+
constructor(timeLeft?: number, useRealTime?: boolean);
|
|
1235
|
+
useRealTime: boolean;
|
|
1234
1236
|
time: number;
|
|
1235
1237
|
setTime: number;
|
|
1236
1238
|
/** Set the timer with seconds passed in
|
|
1237
1239
|
* @param {number} [timeLeft] - How much time left before the timer is elapsed in seconds */
|
|
1238
1240
|
set(timeLeft?: number): void;
|
|
1241
|
+
/** Set if the timer should keep running even when the game is paused
|
|
1242
|
+
* @param {boolean} [useRealTime] */
|
|
1243
|
+
setUseRealTime(useRealTime?: boolean): void;
|
|
1239
1244
|
/** Unset the timer */
|
|
1240
1245
|
unset(): void;
|
|
1241
1246
|
/** Returns true if set
|
|
@@ -1256,6 +1261,9 @@ declare module "littlejsengine" {
|
|
|
1256
1261
|
/** Get the time this timer was set to, returns 0 if not set
|
|
1257
1262
|
* @return {number} */
|
|
1258
1263
|
getSetTime(): number;
|
|
1264
|
+
/** Get the current global time this timer is based on
|
|
1265
|
+
* @return {number} */
|
|
1266
|
+
getGlobalTime(): number;
|
|
1259
1267
|
/** Returns this timer expressed as a string
|
|
1260
1268
|
* @return {string} */
|
|
1261
1269
|
toString(): string;
|
|
@@ -3204,6 +3212,9 @@ declare module "littlejsengine" {
|
|
|
3204
3212
|
* @return {Vector2}
|
|
3205
3213
|
*/
|
|
3206
3214
|
screenToNative(pos: Vector2): Vector2;
|
|
3215
|
+
/** Destroy and remove all objects
|
|
3216
|
+
* @memberof Engine */
|
|
3217
|
+
destroyObjects(): void;
|
|
3207
3218
|
}
|
|
3208
3219
|
/**
|
|
3209
3220
|
* UI Object - Base level object for all UI elements
|
|
@@ -3282,6 +3293,9 @@ declare module "littlejsengine" {
|
|
|
3282
3293
|
* @param {UIObject} child
|
|
3283
3294
|
*/
|
|
3284
3295
|
removeChild(child: UIObject): void;
|
|
3296
|
+
/** Destroy this object, destroy its children, detach its parent, and mark it for removal */
|
|
3297
|
+
destroy(): void;
|
|
3298
|
+
destroyed: number;
|
|
3285
3299
|
/** Check if the mouse is overlapping a box in screen space
|
|
3286
3300
|
* @return {boolean} - True if overlapping
|
|
3287
3301
|
*/
|
|
@@ -3408,6 +3422,67 @@ declare module "littlejsengine" {
|
|
|
3408
3422
|
handleColor: Color;
|
|
3409
3423
|
text: string;
|
|
3410
3424
|
}
|
|
3425
|
+
/**
|
|
3426
|
+
* VideoPlayerUIObject - A UI object that plays video
|
|
3427
|
+
* @extends UIObject
|
|
3428
|
+
* @example
|
|
3429
|
+
* // Create a video player UI object
|
|
3430
|
+
* const video = new VideoPlayerUIObject(vec2(400, 300), vec2(320, 240), 'cutscene.mp4', true);
|
|
3431
|
+
* video.play();
|
|
3432
|
+
* @memberof UISystem
|
|
3433
|
+
*/
|
|
3434
|
+
export class UIVideo extends UIObject {
|
|
3435
|
+
/** Create a video player UI object
|
|
3436
|
+
* @param {Vector2} [pos]
|
|
3437
|
+
* @param {Vector2} [size]
|
|
3438
|
+
* @param {string} src - Video file path or URL
|
|
3439
|
+
* @param {boolean} [autoplay=false] - Start playing immediately?
|
|
3440
|
+
* @param {boolean} [loop=false] - Loop the video?
|
|
3441
|
+
* @param {number} [volume=1] - Volume percent scaled by global volume (0-1)
|
|
3442
|
+
*/
|
|
3443
|
+
constructor(pos?: Vector2, size?: Vector2, src: string, autoplay?: boolean, loop?: boolean, volume?: number);
|
|
3444
|
+
/** @property {float} - The video volume */
|
|
3445
|
+
volume: number;
|
|
3446
|
+
/** @property {HTMLVideoElement} - The video player */
|
|
3447
|
+
video: HTMLVideoElement;
|
|
3448
|
+
/** Play or resume the video
|
|
3449
|
+
* @return {Promise} Promise that resolves when playback starts */
|
|
3450
|
+
play(): Promise<any>;
|
|
3451
|
+
/** Pause the video */
|
|
3452
|
+
pause(): void;
|
|
3453
|
+
/** Stop and reset the video */
|
|
3454
|
+
stop(): void;
|
|
3455
|
+
/** Check if video is currently loading
|
|
3456
|
+
* @return {boolean} */
|
|
3457
|
+
isLoadng(): boolean;
|
|
3458
|
+
/** Check if video is currently paused
|
|
3459
|
+
* @return {boolean} */
|
|
3460
|
+
isPaused(): boolean;
|
|
3461
|
+
/** Check if video is currently playing
|
|
3462
|
+
* @return {boolean} */
|
|
3463
|
+
isPlaying(): boolean;
|
|
3464
|
+
/** Check if video has ended playing
|
|
3465
|
+
* @return {boolean} */
|
|
3466
|
+
hasEnded(): boolean;
|
|
3467
|
+
/** Set volume (0-1)
|
|
3468
|
+
* @param {number} volume - Volume level (0-1) */
|
|
3469
|
+
setVolume(volume: number): void;
|
|
3470
|
+
/** Set playback speed
|
|
3471
|
+
* @param {number} rate - Playback rate multiplier */
|
|
3472
|
+
setPlaybackRate(rate: number): void;
|
|
3473
|
+
/** Get current time in seconds
|
|
3474
|
+
* @return {number} Current playback time */
|
|
3475
|
+
getCurrentTime(): number;
|
|
3476
|
+
/** Get duration in seconds
|
|
3477
|
+
* @return {number} Total video duration */
|
|
3478
|
+
getDuration(): number;
|
|
3479
|
+
/** Get the native video dimensions
|
|
3480
|
+
* @return {Vector2} Video dimensions (may be 0,0 if metadata not loaded) */
|
|
3481
|
+
getVideoSize(): Vector2;
|
|
3482
|
+
/** Seek to time in seconds
|
|
3483
|
+
* @param {number} time - Time in seconds to seek to */
|
|
3484
|
+
setTime(time: number): void;
|
|
3485
|
+
}
|
|
3411
3486
|
/**
|
|
3412
3487
|
* LittleJS Box2D Physics Plugin
|
|
3413
3488
|
* - Box2dObject extends EngineObject with Box2D physics
|
package/dist/littlejs.esm.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
|
|
@@ -11819,6 +12012,7 @@ export
|
|
|
11819
12012
|
UIButton,
|
|
11820
12013
|
UICheckbox,
|
|
11821
12014
|
UIScrollbar,
|
|
12015
|
+
UIVideo,
|
|
11822
12016
|
|
|
11823
12017
|
// Box2D Physics
|
|
11824
12018
|
box2d,
|