littlejsengine 1.14.23 → 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 +184 -30
- package/dist/littlejs.esm.js +589 -147
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +583 -147
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +546 -147
- package/examples/box2d/gameObjects.js +6 -10
- package/examples/box2d/scenes.js +2 -2
- package/examples/breakout/gameObjects.js +1 -1
- package/examples/electron/index.html +2 -2
- package/examples/index.html +11 -7
- package/examples/platformer/gameCharacter.js +3 -2
- package/examples/platformer/gameObjects.js +3 -8
- package/examples/shorts/base.html +2 -2
- package/examples/shorts/box2d.js +2 -2
- package/examples/shorts/box2dCar.js +18 -7
- package/examples/shorts/box2dPool.js +108 -0
- package/examples/shorts/debugDraw.js +2 -3
- package/examples/shorts/flappyGame.js +1 -0
- package/examples/shorts/fps.js +1 -1
- package/examples/shorts/hillGlideGame.js +0 -2
- package/examples/shorts/input.js +59 -0
- package/examples/shorts/landerGame.js +1 -3
- package/examples/shorts/medals.js +15 -9
- package/examples/shorts/music.js +15 -17
- package/examples/shorts/musicPlayer.js +24 -24
- package/examples/shorts/platformer.js +0 -2
- package/examples/shorts/slidingPuzzle.js +1 -1
- package/examples/shorts/song.mp3 +0 -0
- package/examples/shorts/spaceGame.js +0 -2
- package/examples/shorts/spriteAtlas.js +0 -2
- package/examples/shorts/tiltedView.js +0 -3
- package/examples/shorts/timers.js +1 -2
- package/examples/shorts/topDown.js +0 -2
- 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/box2d.js +170 -50
- package/plugins/pluginExport.js +3 -0
- package/plugins/uiSystem.js +229 -27
- package/src/engine.js +21 -18
- package/src/engineAudio.js +14 -2
- package/src/engineDebug.js +37 -0
- package/src/engineDraw.js +21 -13
- package/src/engineExport.js +3 -0
- package/src/engineInput.js +24 -12
- package/src/engineMedals.js +2 -2
- package/src/engineObject.js +24 -4
- package/src/engineParticles.js +4 -6
- package/src/engineTileLayer.js +2 -0
- package/src/engineUtilities.js +35 -13
package/plugins/uiSystem.js
CHANGED
|
@@ -108,11 +108,16 @@ class UISystemPlugin
|
|
|
108
108
|
// reset hover object at start of update
|
|
109
109
|
uiSystem.lastHoverObject = uiSystem.hoverObject;
|
|
110
110
|
uiSystem.hoverObject = undefined;
|
|
111
|
+
|
|
112
|
+
// update in reverse order so topmost objects get priority
|
|
111
113
|
for (let i = uiSystem.uiObjects.length; i--;)
|
|
112
114
|
{
|
|
113
115
|
const o = uiSystem.uiObjects[i];
|
|
114
116
|
o.parent || updateObject(o)
|
|
115
117
|
}
|
|
118
|
+
|
|
119
|
+
// remove destroyed objects
|
|
120
|
+
uiSystem.uiObjects = uiSystem.uiObjects.filter(o=>!o.destroyed);
|
|
116
121
|
}
|
|
117
122
|
function uiRender()
|
|
118
123
|
{
|
|
@@ -145,12 +150,12 @@ class UISystemPlugin
|
|
|
145
150
|
/** Draw a rectangle to the UI context
|
|
146
151
|
* @param {Vector2} pos
|
|
147
152
|
* @param {Vector2} size
|
|
148
|
-
* @param {Color} [color
|
|
149
|
-
* @param {number} [lineWidth
|
|
150
|
-
* @param {Color} [lineColor
|
|
151
|
-
* @param {number} [cornerRadius
|
|
152
|
-
* @param {Color} [gradientColor
|
|
153
|
-
drawRect(pos, size, color=
|
|
153
|
+
* @param {Color} [color]
|
|
154
|
+
* @param {number} [lineWidth]
|
|
155
|
+
* @param {Color} [lineColor]
|
|
156
|
+
* @param {number} [cornerRadius]
|
|
157
|
+
* @param {Color} [gradientColor] */
|
|
158
|
+
drawRect(pos, size, color=WHITE, lineWidth=0, lineColor=BLACK, cornerRadius=0, gradientColor)
|
|
154
159
|
{
|
|
155
160
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
156
161
|
ASSERT(isVector2(size), 'size must be a vec2');
|
|
@@ -229,10 +234,14 @@ class UISystemPlugin
|
|
|
229
234
|
* @param {string} [align]
|
|
230
235
|
* @param {string} [font=uiSystem.defaultFont]
|
|
231
236
|
* @param {string} [fontStyle]
|
|
232
|
-
* @param {boolean} [applyMaxWidth=true]
|
|
233
|
-
|
|
237
|
+
* @param {boolean} [applyMaxWidth=true]
|
|
238
|
+
* @param {Vector2} [textShadow]
|
|
239
|
+
*/
|
|
240
|
+
drawText(text, pos, size, color=uiSystem.defaultColor, lineWidth=uiSystem.defaultLineWidth, lineColor=uiSystem.defaultLineColor, align='center', font=uiSystem.defaultFont, fontStyle='', applyMaxWidth=true, textShadow=undefined)
|
|
234
241
|
{
|
|
235
|
-
|
|
242
|
+
if (textShadow)
|
|
243
|
+
drawTextScreen(text, pos.add(textShadow), size.y, BLACK, lineWidth, lineColor, align, font, fontStyle, applyMaxWidth ? size.x : undefined, 0, uiSystem.uiContext);
|
|
244
|
+
drawTextScreen(text, pos, size.y, color, lineWidth, lineColor, align, font, fontStyle, applyMaxWidth ? size.x : undefined, 0, uiSystem.uiContext);
|
|
236
245
|
}
|
|
237
246
|
|
|
238
247
|
/**
|
|
@@ -259,6 +268,34 @@ class UISystemPlugin
|
|
|
259
268
|
setCallback(onDragLeave, 'dragleave');
|
|
260
269
|
setCallback(onDragOver, 'dragover');
|
|
261
270
|
}
|
|
271
|
+
|
|
272
|
+
/** Convert a screen space position to native UI position
|
|
273
|
+
* @param {Vector2} pos
|
|
274
|
+
* @return {Vector2}
|
|
275
|
+
*/
|
|
276
|
+
screenToNative(pos)
|
|
277
|
+
{
|
|
278
|
+
if (!uiSystem.nativeHeight)
|
|
279
|
+
return pos;
|
|
280
|
+
|
|
281
|
+
const s = mainCanvasSize.y / uiSystem.nativeHeight;
|
|
282
|
+
const sInv = 1/s;
|
|
283
|
+
const p = pos.copy();
|
|
284
|
+
p.x += s*mainCanvasSize.x/2;
|
|
285
|
+
p.x *= sInv;
|
|
286
|
+
p.y *= sInv;
|
|
287
|
+
p.x -= sInv*mainCanvasSize.x/2;
|
|
288
|
+
return p;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** Destroy and remove all objects
|
|
292
|
+
* @memberof Engine */
|
|
293
|
+
destroyObjects()
|
|
294
|
+
{
|
|
295
|
+
for (const o of this.uiObjects)
|
|
296
|
+
o.parent || o.destroy();
|
|
297
|
+
this.uiObjects = this.uiObjects.filter(o=>!o.destroyed);
|
|
298
|
+
}
|
|
262
299
|
}
|
|
263
300
|
|
|
264
301
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -335,6 +372,9 @@ class UIObject
|
|
|
335
372
|
/** @property {boolean} - True if this can be a hover object */
|
|
336
373
|
this.canBeHover = true;
|
|
337
374
|
uiSystem.uiObjects.push(this);
|
|
375
|
+
|
|
376
|
+
/** @property {Vector2} - How much to offset the text shadow or undefined */
|
|
377
|
+
this.textShadow = undefined;
|
|
338
378
|
}
|
|
339
379
|
|
|
340
380
|
/** Add a child UIObject to this object
|
|
@@ -357,29 +397,41 @@ class UIObject
|
|
|
357
397
|
child.parent = undefined;
|
|
358
398
|
}
|
|
359
399
|
|
|
400
|
+
|
|
401
|
+
/** Destroy this object, destroy its children, detach its parent, and mark it for removal */
|
|
402
|
+
destroy()
|
|
403
|
+
{
|
|
404
|
+
if (this.destroyed)
|
|
405
|
+
return;
|
|
406
|
+
|
|
407
|
+
// disconnect from parent and destroy children
|
|
408
|
+
this.destroyed = 1;
|
|
409
|
+
this.parent && this.parent.removeChild(this);
|
|
410
|
+
for (const child of this.children)
|
|
411
|
+
{
|
|
412
|
+
child.parent = 0;
|
|
413
|
+
child.destroy();
|
|
414
|
+
}
|
|
415
|
+
}
|
|
360
416
|
/** Check if the mouse is overlapping a box in screen space
|
|
361
417
|
* @return {boolean} - True if overlapping
|
|
362
418
|
*/
|
|
363
419
|
isMouseOverlapping()
|
|
364
420
|
{
|
|
421
|
+
if (!mouseInWindow) return false;
|
|
422
|
+
|
|
365
423
|
const size = !isTouchDevice ? this.size :
|
|
366
424
|
this.size.add(vec2(this.extraTouchSize || 0));
|
|
367
|
-
|
|
368
|
-
return isOverlapping(this.pos, size, mousePosScreen);
|
|
369
|
-
|
|
370
|
-
const s = mainCanvasSize.y / uiSystem.nativeHeight;
|
|
371
|
-
const sInv = 1/s;
|
|
372
|
-
let pos = mousePosScreen.copy();
|
|
373
|
-
pos.x += s*mainCanvasSize.x/2;
|
|
374
|
-
pos.x *= sInv;
|
|
375
|
-
pos.y *= sInv;
|
|
376
|
-
pos.x -= sInv*mainCanvasSize.x/2;
|
|
425
|
+
const pos = uiSystem.screenToNative(mousePosScreen);
|
|
377
426
|
return isOverlapping(this.pos, size, pos);
|
|
378
427
|
}
|
|
379
428
|
|
|
380
429
|
/** Update the object, called automatically by plugin once each frame */
|
|
381
430
|
update()
|
|
382
431
|
{
|
|
432
|
+
// call the custom update callback
|
|
433
|
+
this.onUpdate();
|
|
434
|
+
|
|
383
435
|
const wasHover = uiSystem.lastHoverObject === this;
|
|
384
436
|
const isActive = this.isActiveObject();
|
|
385
437
|
const mouseDown = mouseIsDown(0);
|
|
@@ -436,7 +488,7 @@ class UIObject
|
|
|
436
488
|
|
|
437
489
|
const lineColor = this.interactive && this.isActiveObject() && !this.disabled ? this.color : this.lineColor;
|
|
438
490
|
const color = this.disabled ? this.disabledColor : this.interactive ? this.isActiveObject() ? this.activeColor || this.color : this.isHoverObject() ? this.hoverColor : this.color : this.color;
|
|
439
|
-
uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor, this.cornerRadius);
|
|
491
|
+
uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor, this.cornerRadius, this.gradientColor);
|
|
440
492
|
}
|
|
441
493
|
|
|
442
494
|
/** Special update when object is not visible */
|
|
@@ -463,6 +515,9 @@ class UIObject
|
|
|
463
515
|
/** @return {boolean} - Is the mouse held onto this element */
|
|
464
516
|
isActiveObject() { return uiSystem.activeObject === this; }
|
|
465
517
|
|
|
518
|
+
/** Called each frame when object updates */
|
|
519
|
+
onUpdate() {}
|
|
520
|
+
|
|
466
521
|
/** Called when the mouse enters the object */
|
|
467
522
|
onEnter() {}
|
|
468
523
|
|
|
@@ -517,8 +572,9 @@ class UIText extends UIObject
|
|
|
517
572
|
}
|
|
518
573
|
render()
|
|
519
574
|
{
|
|
575
|
+
// only render the text
|
|
520
576
|
const textSize = this.getTextSize();
|
|
521
|
-
uiSystem.drawText(this.text, this.pos, textSize, this.textColor, this.lineWidth, this.lineColor, this.align, this.font, this.fontStyle);
|
|
577
|
+
uiSystem.drawText(this.text, this.pos, textSize, this.textColor, this.lineWidth, this.lineColor, this.align, this.font, this.fontStyle, true, this.textShadow);
|
|
522
578
|
}
|
|
523
579
|
}
|
|
524
580
|
|
|
@@ -594,7 +650,7 @@ class UIButton extends UIObject
|
|
|
594
650
|
// draw the text scaled to fit
|
|
595
651
|
const textSize = this.getTextSize();
|
|
596
652
|
uiSystem.drawText(this.text, this.pos, textSize,
|
|
597
|
-
this.textColor, 0, undefined, this.align, this.font, this.fontStyle);
|
|
653
|
+
this.textColor, 0, undefined, this.align, this.font, this.fontStyle, true, this.textShadow);
|
|
598
654
|
}
|
|
599
655
|
}
|
|
600
656
|
|
|
@@ -648,7 +704,7 @@ class UICheckbox extends UIObject
|
|
|
648
704
|
const textSize = this.getTextSize();
|
|
649
705
|
const pos = this.pos.add(vec2(this.size.x,0));
|
|
650
706
|
uiSystem.drawText(this.text, pos, textSize,
|
|
651
|
-
this.textColor, 0, undefined, 'left', this.font, this.fontStyle, false);
|
|
707
|
+
this.textColor, 0, undefined, 'left', this.font, this.fontStyle, false, this.textShadow);
|
|
652
708
|
}
|
|
653
709
|
}
|
|
654
710
|
|
|
@@ -703,9 +759,11 @@ class UIScrollbar extends UIObject
|
|
|
703
759
|
const p1 = centerPos - handleWidth/2;
|
|
704
760
|
const p2 = centerPos + handleWidth/2;
|
|
705
761
|
const oldValue = this.value;
|
|
762
|
+
|
|
763
|
+
const p = uiSystem.screenToNative(mousePosScreen);
|
|
706
764
|
this.value = isHorizontal ?
|
|
707
|
-
percent(
|
|
708
|
-
percent(
|
|
765
|
+
percent(p.x, p1, p2) :
|
|
766
|
+
percent(p.y, p2, p1);
|
|
709
767
|
this.value === oldValue || this.onChange();
|
|
710
768
|
}
|
|
711
769
|
}
|
|
@@ -727,11 +785,155 @@ class UIScrollbar extends UIObject
|
|
|
727
785
|
vec2(lerp(p1, p2, this.value), this.pos.y) :
|
|
728
786
|
vec2(this.pos.x, lerp(p2, p1, this.value))
|
|
729
787
|
const handleColor = this.disabled ? this.disabledColor : this.handleColor;
|
|
730
|
-
uiSystem.drawRect(handlePos, vec2(handleSize), handleColor, this.lineWidth, this.lineColor, this.cornerRadius);
|
|
788
|
+
uiSystem.drawRect(handlePos, vec2(handleSize), handleColor, this.lineWidth, this.lineColor, this.cornerRadius, this.gradientColor);
|
|
731
789
|
|
|
732
790
|
// draw the text scaled to fit on the scrollbar
|
|
733
791
|
const textSize = this.getTextSize();
|
|
734
792
|
uiSystem.drawText(this.text, this.pos, textSize,
|
|
735
|
-
this.textColor, 0, undefined, this.align, this.font, this.fontStyle);
|
|
793
|
+
this.textColor, 0, undefined, this.align, this.font, this.fontStyle, true, this.textShadow);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
798
|
+
/**
|
|
799
|
+
* VideoPlayerUIObject - A UI object that plays video
|
|
800
|
+
* @extends UIObject
|
|
801
|
+
* @example
|
|
802
|
+
* // Create a video player UI object
|
|
803
|
+
* const video = new VideoPlayerUIObject(vec2(400, 300), vec2(320, 240), 'cutscene.mp4', true);
|
|
804
|
+
* video.play();
|
|
805
|
+
* @memberof UISystem
|
|
806
|
+
*/
|
|
807
|
+
class UIVideo extends UIObject
|
|
808
|
+
{
|
|
809
|
+
/** Create a video player UI object
|
|
810
|
+
* @param {Vector2} [pos]
|
|
811
|
+
* @param {Vector2} [size]
|
|
812
|
+
* @param {string} src - Video file path or URL
|
|
813
|
+
* @param {boolean} [autoplay=false] - Start playing immediately?
|
|
814
|
+
* @param {boolean} [loop=false] - Loop the video?
|
|
815
|
+
* @param {number} [volume=1] - Volume percent scaled by global volume (0-1)
|
|
816
|
+
*/
|
|
817
|
+
constructor(pos, size, src, autoplay=false, loop=false, volume=1)
|
|
818
|
+
{
|
|
819
|
+
super(pos, size || vec2());
|
|
820
|
+
|
|
821
|
+
ASSERT(isString(src), 'video src must be a string');
|
|
822
|
+
ASSERT(isNumber(volume), 'video volume must be a number');
|
|
823
|
+
|
|
824
|
+
this.color = BLACK; // default to black background
|
|
825
|
+
this.cornerRadius = 0; // default to no corner radius
|
|
826
|
+
|
|
827
|
+
/** @property {float} - The video volume */
|
|
828
|
+
this.volume = volume;
|
|
829
|
+
|
|
830
|
+
// create video element
|
|
831
|
+
/** @property {HTMLVideoElement} - The video player */
|
|
832
|
+
this.video = document.createElement('video');
|
|
833
|
+
this.video.loop = loop;
|
|
834
|
+
this.video.volume = clamp(volume * soundVolume);
|
|
835
|
+
this.video.muted = !soundEnable;
|
|
836
|
+
this.video.style.display = 'none';
|
|
837
|
+
this.video.src = src;
|
|
838
|
+
document.body.appendChild(this.video);
|
|
839
|
+
autoplay && this.play();
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/** Play or resume the video
|
|
843
|
+
* @return {Promise} Promise that resolves when playback starts */
|
|
844
|
+
play()
|
|
845
|
+
{
|
|
846
|
+
// try to play the video, catch any errors (autoplay may be blocked)
|
|
847
|
+
const promise = this.video.play();
|
|
848
|
+
promise?.catch(()=>{});
|
|
849
|
+
return promise;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/** Pause the video */
|
|
853
|
+
pause() { this.video.pause(); }
|
|
854
|
+
|
|
855
|
+
/** Stop and reset the video */
|
|
856
|
+
stop() { this.video.pause(); this.video.currentTime = 0; }
|
|
857
|
+
|
|
858
|
+
/** Check if video is currently loading
|
|
859
|
+
* @return {boolean} */
|
|
860
|
+
isLoadng()
|
|
861
|
+
{ return this.video.readyState < this.video.HAVE_CURRENT_DATA; }
|
|
862
|
+
|
|
863
|
+
/** Check if video is currently paused
|
|
864
|
+
* @return {boolean} */
|
|
865
|
+
isPaused() { return this.video.paused; }
|
|
866
|
+
|
|
867
|
+
/** Check if video is currently playing
|
|
868
|
+
* @return {boolean} */
|
|
869
|
+
isPlaying()
|
|
870
|
+
{ return !this.isPaused() && !this.hasEnded() && !this.isLoadng(); }
|
|
871
|
+
|
|
872
|
+
/** Check if video has ended playing
|
|
873
|
+
* @return {boolean} */
|
|
874
|
+
hasEnded() { return this.video.ended; }
|
|
875
|
+
|
|
876
|
+
/** Set volume (0-1)
|
|
877
|
+
* @param {number} volume - Volume level (0-1) */
|
|
878
|
+
setVolume(volume)
|
|
879
|
+
{
|
|
880
|
+
this.volume = volume;
|
|
881
|
+
this.video.volume = clamp(volume * soundVolume);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
/** Set playback speed
|
|
885
|
+
* @param {number} rate - Playback rate multiplier */
|
|
886
|
+
setPlaybackRate(rate) { this.video.playbackRate = rate; }
|
|
887
|
+
|
|
888
|
+
/** Get current time in seconds
|
|
889
|
+
* @return {number} Current playback time */
|
|
890
|
+
getCurrentTime() { return this.video.currentTime || 0; }
|
|
891
|
+
|
|
892
|
+
/** Get duration in seconds
|
|
893
|
+
* @return {number} Total video duration */
|
|
894
|
+
getDuration() { return this.video.duration || 0; }
|
|
895
|
+
|
|
896
|
+
/** Get the native video dimensions
|
|
897
|
+
* @return {Vector2} Video dimensions (may be 0,0 if metadata not loaded) */
|
|
898
|
+
getVideoSize()
|
|
899
|
+
{ return vec2(this.video.videoWidth, this.video.videoHeight); }
|
|
900
|
+
|
|
901
|
+
/** Seek to time in seconds
|
|
902
|
+
* @param {number} time - Time in seconds to seek to */
|
|
903
|
+
setTime(time)
|
|
904
|
+
{ this.video.currentTime = clamp(time, 0, this.getDuration()); }
|
|
905
|
+
|
|
906
|
+
update()
|
|
907
|
+
{
|
|
908
|
+
super.update();
|
|
909
|
+
|
|
910
|
+
// update volume based on global sound volume
|
|
911
|
+
this.video.volume = clamp(this.volume * soundVolume);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
/** Render video to UI canvas */
|
|
915
|
+
render()
|
|
916
|
+
{
|
|
917
|
+
super.render();
|
|
918
|
+
|
|
919
|
+
if (this.isLoadng())
|
|
920
|
+
return;
|
|
921
|
+
const context = uiSystem.uiContext;
|
|
922
|
+
const s = this.size;
|
|
923
|
+
context.save();
|
|
924
|
+
context.translate(this.pos.x, this.pos.y);
|
|
925
|
+
context.drawImage(this.video, -s.x/2, -s.y/2, s.x, s.y);
|
|
926
|
+
context.restore();
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/** Clean up video on destroy */
|
|
930
|
+
destroy()
|
|
931
|
+
{
|
|
932
|
+
if (this.destroyed)
|
|
933
|
+
return;
|
|
934
|
+
|
|
935
|
+
this.video.pause();
|
|
936
|
+
this.video.remove();
|
|
937
|
+
super.destroy();
|
|
736
938
|
}
|
|
737
939
|
}
|
package/src/engine.js
CHANGED
|
@@ -30,7 +30,7 @@ const engineName = 'LittleJS';
|
|
|
30
30
|
* @type {string}
|
|
31
31
|
* @default
|
|
32
32
|
* @memberof Engine */
|
|
33
|
-
const engineVersion = '1.
|
|
33
|
+
const engineVersion = '1.15.2';
|
|
34
34
|
|
|
35
35
|
/** Frames per second to update
|
|
36
36
|
* @type {number}
|
|
@@ -131,7 +131,7 @@ function engineAddPlugin(update, render, glContextLost, glContextRestored)
|
|
|
131
131
|
|
|
132
132
|
/**
|
|
133
133
|
* @callback GameInitCallback - Called after the engine starts, can be async
|
|
134
|
-
* @
|
|
134
|
+
* @return {void|Promise<void>}
|
|
135
135
|
* @memberof Engine
|
|
136
136
|
*/
|
|
137
137
|
/**
|
|
@@ -161,7 +161,7 @@ function engineAddPlugin(update, render, glContextLost, glContextRestored)
|
|
|
161
161
|
async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
|
|
162
162
|
{
|
|
163
163
|
ASSERT(!mainContext, 'engine already initialized');
|
|
164
|
-
ASSERT(
|
|
164
|
+
ASSERT(isArray(imageSources), 'pass in images as array');
|
|
165
165
|
|
|
166
166
|
// allow passing in empty functions
|
|
167
167
|
gameInit ||= ()=>{};
|
|
@@ -453,21 +453,24 @@ function engineObjectsUpdate()
|
|
|
453
453
|
// recursive object update
|
|
454
454
|
function updateObject(o)
|
|
455
455
|
{
|
|
456
|
-
if (
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
456
|
+
if (o.destroyed)
|
|
457
|
+
return;
|
|
458
|
+
|
|
459
|
+
o.update();
|
|
460
|
+
for (const child of o.children)
|
|
461
|
+
updateObject(child);
|
|
462
462
|
}
|
|
463
463
|
for (const o of engineObjects)
|
|
464
464
|
{
|
|
465
|
+
if (o.parent)
|
|
466
|
+
continue;
|
|
467
|
+
|
|
465
468
|
// update top level objects
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
469
|
+
o.update();
|
|
470
|
+
o.updatePhysics();
|
|
471
|
+
for (const child of o.children)
|
|
472
|
+
updateObject(child);
|
|
473
|
+
o.updateTransforms();
|
|
471
474
|
}
|
|
472
475
|
|
|
473
476
|
// remove destroyed objects
|
|
@@ -484,10 +487,10 @@ function engineObjectsDestroy()
|
|
|
484
487
|
}
|
|
485
488
|
|
|
486
489
|
/** Collects all object within a given area
|
|
487
|
-
* @param {Vector2} [pos]
|
|
488
|
-
* @param {Vector2|number} [size]
|
|
490
|
+
* @param {Vector2} [pos] - Center of test area, or undefined for all objects
|
|
491
|
+
* @param {Vector2|number} [size] - Radius of circle if float, rectangle size if Vector2
|
|
489
492
|
* @param {Array<EngineObject>} [objects=engineObjects] - List of objects to check
|
|
490
|
-
* @return {Array<EngineObject>}
|
|
493
|
+
* @return {Array<EngineObject>} - List of collected objects
|
|
491
494
|
* @memberof Engine */
|
|
492
495
|
function engineObjectsCollect(pos, size, objects=engineObjects)
|
|
493
496
|
{
|
|
@@ -500,7 +503,7 @@ function engineObjectsCollect(pos, size, objects=engineObjects)
|
|
|
500
503
|
else if (size instanceof Vector2) // bounding box test
|
|
501
504
|
{
|
|
502
505
|
for (const o of objects)
|
|
503
|
-
isOverlapping(pos, size
|
|
506
|
+
o.isOverlapping(pos, size) && collectedObjects.push(o);
|
|
504
507
|
}
|
|
505
508
|
else // circle test
|
|
506
509
|
{
|
package/src/engineAudio.js
CHANGED
|
@@ -66,6 +66,10 @@ class Sound
|
|
|
66
66
|
{
|
|
67
67
|
if (!soundEnable || headlessMode) return;
|
|
68
68
|
|
|
69
|
+
ASSERT(!zzfxSound || isArray(zzfxSound), 'zzfxSound is invalid');
|
|
70
|
+
ASSERT(isNumber(range), 'range must be a number');
|
|
71
|
+
ASSERT(isNumber(taper), 'taper must be a number');
|
|
72
|
+
|
|
69
73
|
/** @property {number} - World space max range of sound */
|
|
70
74
|
this.range = range;
|
|
71
75
|
/** @property {number} - At what percentage of range should it start tapering */
|
|
@@ -104,6 +108,12 @@ class Sound
|
|
|
104
108
|
*/
|
|
105
109
|
play(pos, volume=1, pitch=1, randomnessScale=1, loop=false, paused=false)
|
|
106
110
|
{
|
|
111
|
+
ASSERT(!pos || isVector2(pos), 'pos must be a vec2');
|
|
112
|
+
ASSERT(isNumber(volume), 'volume must be a number');
|
|
113
|
+
ASSERT(isNumber(pitch), 'pitch must be a number');
|
|
114
|
+
ASSERT(isNumber(randomnessScale), 'randomnessScale must be a number');
|
|
115
|
+
|
|
116
|
+
|
|
107
117
|
if (!soundEnable || headlessMode) return;
|
|
108
118
|
if (!this.sampleChannels) return;
|
|
109
119
|
|
|
@@ -149,6 +159,7 @@ class Sound
|
|
|
149
159
|
*/
|
|
150
160
|
playNote(semitoneOffset=0, pos, volume)
|
|
151
161
|
{
|
|
162
|
+
ASSERT(isNumber(semitoneOffset), 'semitoneOffset must be a number');
|
|
152
163
|
const pitch = getNoteFrequency(semitoneOffset, 1);
|
|
153
164
|
return this.play(pos, volume, pitch, 0);
|
|
154
165
|
}
|
|
@@ -157,7 +168,7 @@ class Sound
|
|
|
157
168
|
* @return {number} - How long the sound is in seconds (undefined if loading)
|
|
158
169
|
*/
|
|
159
170
|
getDuration()
|
|
160
|
-
{ return this.sampleChannels && this.sampleChannels[0].length / this.sampleRate; }
|
|
171
|
+
{ return this.sampleChannels && this.sampleRate ? this.sampleChannels[0].length / this.sampleRate : 0; }
|
|
161
172
|
|
|
162
173
|
/** Check if sound is loaded, for sounds fetched from a url
|
|
163
174
|
* @return {boolean} - True if sound is loaded and ready to play
|
|
@@ -199,6 +210,7 @@ class SoundWave extends Sound
|
|
|
199
210
|
super(undefined, range, taper);
|
|
200
211
|
if (!soundEnable || headlessMode) return;
|
|
201
212
|
ASSERT(!filename || isString(filename), 'filename must be a string');
|
|
213
|
+
ASSERT(isNumber(randomness), 'randomness must be a number');
|
|
202
214
|
|
|
203
215
|
/** @property {SoundLoadCallback} - callback function to call when sound is loaded */
|
|
204
216
|
this.onloadCallback = onloadCallback;
|
|
@@ -415,7 +427,7 @@ class SoundInstance
|
|
|
415
427
|
/** Get the total duration of this sound
|
|
416
428
|
* @return {number} - Total duration in seconds
|
|
417
429
|
*/
|
|
418
|
-
getDuration() { return this.sound.getDuration() / this.rate; }
|
|
430
|
+
getDuration() { return this.rate ? this.sound.getDuration() / this.rate : 0; }
|
|
419
431
|
|
|
420
432
|
/** Get source of this sound instance
|
|
421
433
|
* @return {AudioBufferSourceNode}
|
package/src/engineDebug.js
CHANGED
|
@@ -73,6 +73,12 @@ function LOG(...output) { console.log(...output); }
|
|
|
73
73
|
* @memberof Debug */
|
|
74
74
|
function debugRect(pos, size=vec2(), color=WHITE, time=0, angle=0, fill=false)
|
|
75
75
|
{
|
|
76
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
77
|
+
ASSERT(isVector2(size), 'size must be a vec2');
|
|
78
|
+
ASSERT(isString(color) || isColor(color), 'color is invalid');
|
|
79
|
+
ASSERT(isNumber(time), 'time must be a number');
|
|
80
|
+
ASSERT(isNumber(angle), 'angle must be a number');
|
|
81
|
+
|
|
76
82
|
if (typeof size === 'number')
|
|
77
83
|
size = vec2(size); // allow passing in floats
|
|
78
84
|
if (isColor(color))
|
|
@@ -93,6 +99,12 @@ function debugRect(pos, size=vec2(), color=WHITE, time=0, angle=0, fill=false)
|
|
|
93
99
|
* @memberof Debug */
|
|
94
100
|
function debugPoly(pos, points, color=WHITE, time=0, angle=0, fill=false)
|
|
95
101
|
{
|
|
102
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
103
|
+
ASSERT(isArray(points), 'points must be an array');
|
|
104
|
+
ASSERT(isString(color) || isColor(color), 'color is invalid');
|
|
105
|
+
ASSERT(isNumber(time), 'time must be a number');
|
|
106
|
+
ASSERT(isNumber(angle), 'angle must be a number');
|
|
107
|
+
|
|
96
108
|
if (isColor(color))
|
|
97
109
|
color = color.toString();
|
|
98
110
|
pos = pos.copy();
|
|
@@ -110,6 +122,11 @@ function debugPoly(pos, points, color=WHITE, time=0, angle=0, fill=false)
|
|
|
110
122
|
* @memberof Debug */
|
|
111
123
|
function debugCircle(pos, size=0, color=WHITE, time=0, fill=false)
|
|
112
124
|
{
|
|
125
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
126
|
+
ASSERT(isNumber(size), 'size must be a number');
|
|
127
|
+
ASSERT(isString(color) || isColor(color), 'color is invalid');
|
|
128
|
+
ASSERT(isNumber(time), 'time must be a number');
|
|
129
|
+
|
|
113
130
|
if (isColor(color))
|
|
114
131
|
color = color.toString();
|
|
115
132
|
pos = pos.copy();
|
|
@@ -135,6 +152,10 @@ function debugPoint(pos, color, time, angle)
|
|
|
135
152
|
* @memberof Debug */
|
|
136
153
|
function debugLine(posA, posB, color, width=.1, time)
|
|
137
154
|
{
|
|
155
|
+
ASSERT(isVector2(posA), 'posA must be a vec2');
|
|
156
|
+
ASSERT(isVector2(posB), 'posB must be sa vec2');
|
|
157
|
+
ASSERT(isNumber(width), 'width must be a number');
|
|
158
|
+
|
|
138
159
|
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
139
160
|
const size = vec2(width, halfDelta.length()*2);
|
|
140
161
|
debugRect(posA.add(halfDelta), size, color, time, halfDelta.angle(), true);
|
|
@@ -149,6 +170,11 @@ function debugLine(posA, posB, color, width=.1, time)
|
|
|
149
170
|
* @memberof Debug */
|
|
150
171
|
function debugOverlap(posA, sizeA, posB, sizeB, color)
|
|
151
172
|
{
|
|
173
|
+
ASSERT(isVector2(posA), 'posA must be a vec2');
|
|
174
|
+
ASSERT(isVector2(posB), 'posB must be a vec2');
|
|
175
|
+
ASSERT(isVector2(sizeA), 'sizeA must be a vec2');
|
|
176
|
+
ASSERT(isVector2(sizeB), 'sizeB must be a vec2');
|
|
177
|
+
|
|
152
178
|
const minPos = vec2(
|
|
153
179
|
min(posA.x - sizeA.x/2, posB.x - sizeB.x/2),
|
|
154
180
|
min(posA.y - sizeA.y/2, posB.y - sizeB.y/2)
|
|
@@ -171,6 +197,14 @@ function debugOverlap(posA, sizeA, posB, sizeB, color)
|
|
|
171
197
|
* @memberof Debug */
|
|
172
198
|
function debugText(text, pos, size=1, color=WHITE, time=0, angle=0, font='monospace')
|
|
173
199
|
{
|
|
200
|
+
ASSERT(isString(text), 'text must be a string');
|
|
201
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
202
|
+
ASSERT(isNumber(size), 'size must be a number');
|
|
203
|
+
ASSERT(isString(color) || isColor(color), 'color is invalid');
|
|
204
|
+
ASSERT(isNumber(time), 'time must be a number');
|
|
205
|
+
ASSERT(isNumber(angle), 'angle must be a number');
|
|
206
|
+
ASSERT(isString(font), 'font must be a string');
|
|
207
|
+
|
|
174
208
|
if (isColor(color))
|
|
175
209
|
color = color.toString();
|
|
176
210
|
pos = pos.copy();
|
|
@@ -624,6 +658,9 @@ function debugVideoCaptureUpdate()
|
|
|
624
658
|
debugVideoCaptureIcon.textContent = '● REC ' + formatTime(debugVideoCaptureTimer);
|
|
625
659
|
}
|
|
626
660
|
|
|
661
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
662
|
+
// debug utility functions
|
|
663
|
+
|
|
627
664
|
// make color constants immutable with debug assertions
|
|
628
665
|
function debugProtectConstant(obj)
|
|
629
666
|
{
|