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.
@@ -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
- * @returns {void|Promise<void>}
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] - Center of test area, or undefined for all objects
143
- * @param {Vector2|number} [size] - Radius of circle if float, rectangle size if Vector2
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>} - List of collected objects
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
- * @returns {number}
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
- * @returns {number}
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
- * @returns {number}
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
- * @returns {number}
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 elapses in seconds */
1233
- constructor(timeLeft?: number);
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
@@ -33,7 +33,7 @@ const engineName = 'LittleJS';
33
33
  * @type {string}
34
34
  * @default
35
35
  * @memberof Engine */
36
- const engineVersion = '1.15.0';
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
- * @returns {void|Promise<void>}
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] - Center of test area, or undefined for all objects
494
- * @param {Vector2|number} [size] - Radius of circle if float, rectangle size if Vector2
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>} - List of collected objects
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
- * @returns {number}
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
- * @returns {number}
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
- * @returns {number}
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
- * @returns {number}
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 elapses in seconds */
2558
- constructor(timeLeft)
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.time = timeLeft === undefined ? undefined : time + timeLeft;
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
- this.time = time + timeLeft;
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 time < this.time; }
2595
+ active() { return this.getGlobalTime() < this.time; }
2584
2596
 
2585
2597
  /** Returns true if set and elapsed
2586
2598
  * @return {boolean} */
2587
- elapsed() { return time >= this.time; }
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()? time - this.time : 0; }
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 - time, 0, this.setTime) : 0; }
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); // fix upside down sprites
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,