littlejsengine 1.14.28 → 1.15.3
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 +117 -19
- package/dist/littlejs.esm.js +339 -79
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +338 -79
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +302 -57
- package/examples/electron/index.html +2 -2
- package/examples/index.html +1 -0
- package/examples/shorts/base.html +2 -2
- package/examples/shorts/box2dCar.js +1 -1
- package/examples/shorts/music.js +1 -0
- package/examples/shorts/musicPlayer.js +1 -0
- package/examples/shorts/sequencer.js +2 -0
- package/examples/shorts/song.mp3 +0 -0
- package/examples/shorts/sound.js +1 -0
- package/examples/shorts/timers.js +1 -0
- package/examples/shorts/uiSystem.js +1 -0
- package/examples/shorts/video.webm +0 -0
- package/examples/shorts/videoPlayer.js +34 -0
- package/examples/starter/index.html +3 -3
- package/examples/uiSystem/game.js +1 -0
- package/package.json +1 -1
- package/plugins/pluginExport.js +1 -0
- package/plugins/uiSystem.js +204 -3
- package/src/engine.js +5 -5
- package/src/engineAudio.js +0 -1
- package/src/engineDebug.js +36 -22
- package/src/engineDraw.js +57 -33
- package/src/engineInput.js +1 -1
- package/src/engineObject.js +3 -1
- package/src/engineParticles.js +3 -0
- package/src/engineTileLayer.js +1 -1
- package/src/engineUtilities.js +28 -12
package/examples/shorts/music.js
CHANGED
|
@@ -8,6 +8,7 @@ function gameInit()
|
|
|
8
8
|
uiSystem.defaultSoundPress = new Sound([.5,0,220]);
|
|
9
9
|
uiSystem.defaultSoundClick = new Sound([.5,0,440]);
|
|
10
10
|
uiSystem.defaultCornerRadius = 20;
|
|
11
|
+
uiSystem.defaultShadowColor = BLACK;
|
|
11
12
|
canvasClearColor = hsl(.9,.3,.2);
|
|
12
13
|
|
|
13
14
|
// setup music player UI
|
|
@@ -28,6 +28,7 @@ class UISequencerButton extends UIButton
|
|
|
28
28
|
this.cornerRadius = 0;
|
|
29
29
|
this.dragActivate = true;
|
|
30
30
|
this.isOn = false;
|
|
31
|
+
this.shadowColor = CLEAR_BLACK;
|
|
31
32
|
|
|
32
33
|
// set instrument and color based on track
|
|
33
34
|
const pianoStart = 2;
|
|
@@ -70,6 +71,7 @@ function gameInit()
|
|
|
70
71
|
// initialize UI system
|
|
71
72
|
new UISystemPlugin;
|
|
72
73
|
uiSystem.defaultCornerRadius = 8;
|
|
74
|
+
uiSystem.defaultShadowColor = BLACK;
|
|
73
75
|
canvasClearColor = hsl(0,0,.3);
|
|
74
76
|
|
|
75
77
|
// create sequencer buttons
|
package/examples/shorts/song.mp3
CHANGED
|
Binary file
|
package/examples/shorts/sound.js
CHANGED
|
@@ -5,6 +5,7 @@ function gameInit()
|
|
|
5
5
|
uiSystem.defaultSoundPress = new Sound([.5,0,220]);
|
|
6
6
|
uiSystem.defaultSoundClick = new Sound([.5,0,440]);
|
|
7
7
|
uiSystem.defaultCornerRadius = 8;
|
|
8
|
+
uiSystem.defaultShadowColor = BLACK;
|
|
8
9
|
|
|
9
10
|
// example button that returns to menu
|
|
10
11
|
const buttonBack = new UIButton(mainCanvasSize.scale(.5), vec2(200),
|
|
Binary file
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
function gameInit()
|
|
2
|
+
{
|
|
3
|
+
new UISystemPlugin;
|
|
4
|
+
uiSystem.defaultCornerRadius = 20;
|
|
5
|
+
uiSystem.defaultShadowColor = BLACK;
|
|
6
|
+
canvasClearColor = hsl(0,0,.1);
|
|
7
|
+
|
|
8
|
+
// video player
|
|
9
|
+
const center = mainCanvasSize.scale(.5);
|
|
10
|
+
const videoPos = center.add(vec2(0, -50));
|
|
11
|
+
const videoSize = vec2(480, 270);
|
|
12
|
+
const filename = 'video.webm';
|
|
13
|
+
const autoplay = true;
|
|
14
|
+
videoPlayer = new UIVideo(videoPos, videoSize, filename, autoplay);
|
|
15
|
+
videoPlayer.lineWidth = 5;
|
|
16
|
+
videoPlayer.lineColor = WHITE;
|
|
17
|
+
|
|
18
|
+
// play/pause button
|
|
19
|
+
const buttonSize = vec2(200, 100);
|
|
20
|
+
const playButtonPos = center.add(vec2(-130, 170));
|
|
21
|
+
const playButton = new UIButton(playButtonPos, buttonSize);
|
|
22
|
+
playButton.onClick = ()=> videoPlayer.isPaused() ?
|
|
23
|
+
videoPlayer.play() : videoPlayer.pause();
|
|
24
|
+
playButton.onUpdate = ()=> playButton.text =
|
|
25
|
+
videoPlayer.isPaused() ? 'Play' : 'Pause';
|
|
26
|
+
|
|
27
|
+
// status text
|
|
28
|
+
const statusTextPos = center.add(vec2(130, 170));
|
|
29
|
+
const statusText = new UIText(statusTextPos, vec2(250, 90));
|
|
30
|
+
statusText.textColor = WHITE;
|
|
31
|
+
statusText.onUpdate = ()=> statusText.text =
|
|
32
|
+
videoPlayer.hasEnded() ? 'Ended' :
|
|
33
|
+
videoPlayer.isPlaying() ? 'Playing' : 'Paused';
|
|
34
|
+
}
|
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
</head><body>
|
|
8
8
|
|
|
9
9
|
<!-- LittleJS Engine -->
|
|
10
|
-
<script src=../../dist/littlejs.js?1.15.
|
|
10
|
+
<script src=../../dist/littlejs.js?1.15.3></script>
|
|
11
11
|
|
|
12
12
|
<!-- LittleJS Engine Source
|
|
13
|
+
<script src=../../src/engine.js></script>
|
|
13
14
|
<script src=../../src/engineDebug.js></script>
|
|
14
15
|
<script src=../../src/engineUtilities.js></script>
|
|
15
16
|
<script src=../../src/engineSettings.js></script>
|
|
@@ -21,7 +22,6 @@
|
|
|
21
22
|
<script src=../../src/engineParticles.js></script>
|
|
22
23
|
<script src=../../src/engineMedals.js></script>
|
|
23
24
|
<script src=../../src/engineWebGL.js></script>
|
|
24
|
-
<script src=../../src/engine.js></script>
|
|
25
25
|
<script src=../../plugins/box2d.js></script>
|
|
26
26
|
<script src=../../plugins/box2d.wasm.js></script>
|
|
27
27
|
<script src=../../plugins/drawUtilities.js></script>
|
|
@@ -31,4 +31,4 @@
|
|
|
31
31
|
-->
|
|
32
32
|
|
|
33
33
|
<!-- Add your game scripts here -->
|
|
34
|
-
<script src=game.js?1.15.
|
|
34
|
+
<script src=game.js?1.15.3></script>
|
|
@@ -25,6 +25,7 @@ function createUI()
|
|
|
25
25
|
LJS.uiSystem.defaultSoundClick = new LJS.Sound([.5,0,440]);
|
|
26
26
|
LJS.uiSystem.defaultCornerRadius = 8;
|
|
27
27
|
LJS.uiSystem.defaultGradientColor = LJS.WHITE;
|
|
28
|
+
LJS.uiSystem.defaultShadowColor = LJS.BLACK;
|
|
28
29
|
|
|
29
30
|
// setup root to attach all ui elements to
|
|
30
31
|
uiRoot = new LJS.UIObject;
|
package/package.json
CHANGED
package/plugins/pluginExport.js
CHANGED
package/plugins/uiSystem.js
CHANGED
|
@@ -36,6 +36,7 @@ class UISystemPlugin
|
|
|
36
36
|
ASSERT(!uiSystem, 'UI system already initialized');
|
|
37
37
|
uiSystem = this;
|
|
38
38
|
|
|
39
|
+
// default settings
|
|
39
40
|
/** @property {Color} - Default fill color for UI elements */
|
|
40
41
|
this.defaultColor = WHITE;
|
|
41
42
|
/** @property {Color} - Default outline color for UI elements */
|
|
@@ -64,6 +65,13 @@ class UISystemPlugin
|
|
|
64
65
|
this.defaultSoundRelease = undefined;
|
|
65
66
|
/** @property {Sound} - Default sound when interactive UI element is clicked */
|
|
66
67
|
this.defaultSoundClick = undefined;
|
|
68
|
+
/** @property {Color} - Color for shadow */
|
|
69
|
+
this.defaultShadowColor = CLEAR_BLACK;
|
|
70
|
+
/** @property {number} - Size of shadow blur */
|
|
71
|
+
this.defaultShadowBlur = 5;
|
|
72
|
+
/** @property {Vector2} - Offset of shadow blur */
|
|
73
|
+
this.defaultShadowOffset = vec2(5);
|
|
74
|
+
// system state
|
|
67
75
|
/** @property {Array<UIObject>} - List of all UI elements */
|
|
68
76
|
this.uiObjects = [];
|
|
69
77
|
/** @property {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} - Context to render UI elements to */
|
|
@@ -108,11 +116,16 @@ class UISystemPlugin
|
|
|
108
116
|
// reset hover object at start of update
|
|
109
117
|
uiSystem.lastHoverObject = uiSystem.hoverObject;
|
|
110
118
|
uiSystem.hoverObject = undefined;
|
|
119
|
+
|
|
120
|
+
// update in reverse order so topmost objects get priority
|
|
111
121
|
for (let i = uiSystem.uiObjects.length; i--;)
|
|
112
122
|
{
|
|
113
123
|
const o = uiSystem.uiObjects[i];
|
|
114
124
|
o.parent || updateObject(o)
|
|
115
125
|
}
|
|
126
|
+
|
|
127
|
+
// remove destroyed objects
|
|
128
|
+
uiSystem.uiObjects = uiSystem.uiObjects.filter(o=>!o.destroyed);
|
|
116
129
|
}
|
|
117
130
|
function uiRender()
|
|
118
131
|
{
|
|
@@ -149,8 +162,11 @@ class UISystemPlugin
|
|
|
149
162
|
* @param {number} [lineWidth]
|
|
150
163
|
* @param {Color} [lineColor]
|
|
151
164
|
* @param {number} [cornerRadius]
|
|
152
|
-
* @param {Color} [gradientColor]
|
|
153
|
-
|
|
165
|
+
* @param {Color} [gradientColor]
|
|
166
|
+
* @param {Color} [shadowColor]
|
|
167
|
+
* @param {number} [shadowBlur]
|
|
168
|
+
* @param {Color} [shadowOffset] */
|
|
169
|
+
drawRect(pos, size, color=WHITE, lineWidth=0, lineColor=BLACK, cornerRadius=0, gradientColor, shadowColor=BLACK, shadowBlur=0, shadowOffset=vec2())
|
|
154
170
|
{
|
|
155
171
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
156
172
|
ASSERT(isVector2(size), 'size must be a vec2');
|
|
@@ -172,12 +188,22 @@ class UISystemPlugin
|
|
|
172
188
|
}
|
|
173
189
|
else
|
|
174
190
|
context.fillStyle = color.toString();
|
|
191
|
+
if (shadowBlur || shadowOffset.x || shadowOffset.y)
|
|
192
|
+
if (shadowColor.a > 0)
|
|
193
|
+
{
|
|
194
|
+
// setup shadow
|
|
195
|
+
context.shadowColor = shadowColor.toString();
|
|
196
|
+
context.shadowBlur = shadowBlur;
|
|
197
|
+
context.shadowOffsetX = shadowOffset.x;
|
|
198
|
+
context.shadowOffsetY = shadowOffset.y;
|
|
199
|
+
}
|
|
175
200
|
context.beginPath();
|
|
176
201
|
if (cornerRadius && context['roundRect'])
|
|
177
202
|
context['roundRect'](pos.x-size.x/2, pos.y-size.y/2, size.x, size.y, cornerRadius);
|
|
178
203
|
else
|
|
179
204
|
context.rect(pos.x-size.x/2, pos.y-size.y/2, size.x, size.y);
|
|
180
205
|
context.fill();
|
|
206
|
+
context.shadowColor = '#0000'
|
|
181
207
|
if (lineWidth)
|
|
182
208
|
{
|
|
183
209
|
context.strokeStyle = lineColor.toString();
|
|
@@ -282,6 +308,15 @@ class UISystemPlugin
|
|
|
282
308
|
p.x -= sInv*mainCanvasSize.x/2;
|
|
283
309
|
return p;
|
|
284
310
|
}
|
|
311
|
+
|
|
312
|
+
/** Destroy and remove all objects
|
|
313
|
+
* @memberof Engine */
|
|
314
|
+
destroyObjects()
|
|
315
|
+
{
|
|
316
|
+
for (const o of this.uiObjects)
|
|
317
|
+
o.parent || o.destroy();
|
|
318
|
+
this.uiObjects = this.uiObjects.filter(o=>!o.destroyed);
|
|
319
|
+
}
|
|
285
320
|
}
|
|
286
321
|
|
|
287
322
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -357,6 +392,12 @@ class UIObject
|
|
|
357
392
|
this.dragActivate = false;
|
|
358
393
|
/** @property {boolean} - True if this can be a hover object */
|
|
359
394
|
this.canBeHover = true;
|
|
395
|
+
/** @property {Color} - Color for shadow, undefined if no shadow */
|
|
396
|
+
this.shadowColor = uiSystem.defaultShadowColor?.copy();
|
|
397
|
+
/** @property {number} - Size of shadow blur */
|
|
398
|
+
this.shadowBlur = uiSystem.defaultShadowBlur;
|
|
399
|
+
/** @property {Vector2} - Offset of shadow blur */
|
|
400
|
+
this.shadowOffset = uiSystem.defaultShadowOffset.copy();
|
|
360
401
|
uiSystem.uiObjects.push(this);
|
|
361
402
|
|
|
362
403
|
/** @property {Vector2} - How much to offset the text shadow or undefined */
|
|
@@ -383,6 +424,22 @@ class UIObject
|
|
|
383
424
|
child.parent = undefined;
|
|
384
425
|
}
|
|
385
426
|
|
|
427
|
+
|
|
428
|
+
/** Destroy this object, destroy its children, detach its parent, and mark it for removal */
|
|
429
|
+
destroy()
|
|
430
|
+
{
|
|
431
|
+
if (this.destroyed)
|
|
432
|
+
return;
|
|
433
|
+
|
|
434
|
+
// disconnect from parent and destroy children
|
|
435
|
+
this.destroyed = 1;
|
|
436
|
+
this.parent && this.parent.removeChild(this);
|
|
437
|
+
for (const child of this.children)
|
|
438
|
+
{
|
|
439
|
+
child.parent = 0;
|
|
440
|
+
child.destroy();
|
|
441
|
+
}
|
|
442
|
+
}
|
|
386
443
|
/** Check if the mouse is overlapping a box in screen space
|
|
387
444
|
* @return {boolean} - True if overlapping
|
|
388
445
|
*/
|
|
@@ -458,7 +515,7 @@ class UIObject
|
|
|
458
515
|
|
|
459
516
|
const lineColor = this.interactive && this.isActiveObject() && !this.disabled ? this.color : this.lineColor;
|
|
460
517
|
const color = this.disabled ? this.disabledColor : this.interactive ? this.isActiveObject() ? this.activeColor || this.color : this.isHoverObject() ? this.hoverColor : this.color : this.color;
|
|
461
|
-
uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor, this.cornerRadius, this.gradientColor);
|
|
518
|
+
uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor, this.cornerRadius, this.gradientColor, this.shadowColor, this.shadowBlur, this.shadowOffset);
|
|
462
519
|
}
|
|
463
520
|
|
|
464
521
|
/** Special update when object is not visible */
|
|
@@ -762,4 +819,148 @@ class UIScrollbar extends UIObject
|
|
|
762
819
|
uiSystem.drawText(this.text, this.pos, textSize,
|
|
763
820
|
this.textColor, 0, undefined, this.align, this.font, this.fontStyle, true, this.textShadow);
|
|
764
821
|
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
825
|
+
/**
|
|
826
|
+
* VideoPlayerUIObject - A UI object that plays video
|
|
827
|
+
* @extends UIObject
|
|
828
|
+
* @example
|
|
829
|
+
* // Create a video player UI object
|
|
830
|
+
* const video = new VideoPlayerUIObject(vec2(400, 300), vec2(320, 240), 'cutscene.mp4', true);
|
|
831
|
+
* video.play();
|
|
832
|
+
* @memberof UISystem
|
|
833
|
+
*/
|
|
834
|
+
class UIVideo extends UIObject
|
|
835
|
+
{
|
|
836
|
+
/** Create a video player UI object
|
|
837
|
+
* @param {Vector2} [pos]
|
|
838
|
+
* @param {Vector2} [size]
|
|
839
|
+
* @param {string} src - Video file path or URL
|
|
840
|
+
* @param {boolean} [autoplay=false] - Start playing immediately?
|
|
841
|
+
* @param {boolean} [loop=false] - Loop the video?
|
|
842
|
+
* @param {number} [volume=1] - Volume percent scaled by global volume (0-1)
|
|
843
|
+
*/
|
|
844
|
+
constructor(pos, size, src, autoplay=false, loop=false, volume=1)
|
|
845
|
+
{
|
|
846
|
+
super(pos, size || vec2());
|
|
847
|
+
|
|
848
|
+
ASSERT(isString(src), 'video src must be a string');
|
|
849
|
+
ASSERT(isNumber(volume), 'video volume must be a number');
|
|
850
|
+
|
|
851
|
+
this.color = BLACK; // default to black background
|
|
852
|
+
this.cornerRadius = 0; // default to no corner radius
|
|
853
|
+
|
|
854
|
+
/** @property {float} - The video volume */
|
|
855
|
+
this.volume = volume;
|
|
856
|
+
|
|
857
|
+
// create video element
|
|
858
|
+
/** @property {HTMLVideoElement} - The video player */
|
|
859
|
+
this.video = document.createElement('video');
|
|
860
|
+
this.video.loop = loop;
|
|
861
|
+
this.video.volume = clamp(volume * soundVolume);
|
|
862
|
+
this.video.muted = !soundEnable;
|
|
863
|
+
this.video.style.display = 'none';
|
|
864
|
+
this.video.src = src;
|
|
865
|
+
document.body.appendChild(this.video);
|
|
866
|
+
autoplay && this.play();
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/** Play or resume the video
|
|
870
|
+
* @return {Promise} Promise that resolves when playback starts */
|
|
871
|
+
play()
|
|
872
|
+
{
|
|
873
|
+
// try to play the video, catch any errors (autoplay may be blocked)
|
|
874
|
+
const promise = this.video.play();
|
|
875
|
+
promise?.catch(()=>{});
|
|
876
|
+
return promise;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
/** Pause the video */
|
|
880
|
+
pause() { this.video.pause(); }
|
|
881
|
+
|
|
882
|
+
/** Stop and reset the video */
|
|
883
|
+
stop() { this.video.pause(); this.video.currentTime = 0; }
|
|
884
|
+
|
|
885
|
+
/** Check if video is currently loading
|
|
886
|
+
* @return {boolean} */
|
|
887
|
+
isLoadng()
|
|
888
|
+
{ return this.video.readyState < this.video.HAVE_CURRENT_DATA; }
|
|
889
|
+
|
|
890
|
+
/** Check if video is currently paused
|
|
891
|
+
* @return {boolean} */
|
|
892
|
+
isPaused() { return this.video.paused; }
|
|
893
|
+
|
|
894
|
+
/** Check if video is currently playing
|
|
895
|
+
* @return {boolean} */
|
|
896
|
+
isPlaying()
|
|
897
|
+
{ return !this.isPaused() && !this.hasEnded() && !this.isLoadng(); }
|
|
898
|
+
|
|
899
|
+
/** Check if video has ended playing
|
|
900
|
+
* @return {boolean} */
|
|
901
|
+
hasEnded() { return this.video.ended; }
|
|
902
|
+
|
|
903
|
+
/** Set volume (0-1)
|
|
904
|
+
* @param {number} volume - Volume level (0-1) */
|
|
905
|
+
setVolume(volume)
|
|
906
|
+
{
|
|
907
|
+
this.volume = volume;
|
|
908
|
+
this.video.volume = clamp(volume * soundVolume);
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/** Set playback speed
|
|
912
|
+
* @param {number} rate - Playback rate multiplier */
|
|
913
|
+
setPlaybackRate(rate) { this.video.playbackRate = rate; }
|
|
914
|
+
|
|
915
|
+
/** Get current time in seconds
|
|
916
|
+
* @return {number} Current playback time */
|
|
917
|
+
getCurrentTime() { return this.video.currentTime || 0; }
|
|
918
|
+
|
|
919
|
+
/** Get duration in seconds
|
|
920
|
+
* @return {number} Total video duration */
|
|
921
|
+
getDuration() { return this.video.duration || 0; }
|
|
922
|
+
|
|
923
|
+
/** Get the native video dimensions
|
|
924
|
+
* @return {Vector2} Video dimensions (may be 0,0 if metadata not loaded) */
|
|
925
|
+
getVideoSize()
|
|
926
|
+
{ return vec2(this.video.videoWidth, this.video.videoHeight); }
|
|
927
|
+
|
|
928
|
+
/** Seek to time in seconds
|
|
929
|
+
* @param {number} time - Time in seconds to seek to */
|
|
930
|
+
setTime(time)
|
|
931
|
+
{ this.video.currentTime = clamp(time, 0, this.getDuration()); }
|
|
932
|
+
|
|
933
|
+
update()
|
|
934
|
+
{
|
|
935
|
+
super.update();
|
|
936
|
+
|
|
937
|
+
// update volume based on global sound volume
|
|
938
|
+
this.video.volume = clamp(this.volume * soundVolume);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/** Render video to UI canvas */
|
|
942
|
+
render()
|
|
943
|
+
{
|
|
944
|
+
super.render();
|
|
945
|
+
|
|
946
|
+
if (this.isLoadng())
|
|
947
|
+
return;
|
|
948
|
+
const context = uiSystem.uiContext;
|
|
949
|
+
const s = this.size;
|
|
950
|
+
context.save();
|
|
951
|
+
context.translate(this.pos.x, this.pos.y);
|
|
952
|
+
context.drawImage(this.video, -s.x/2, -s.y/2, s.x, s.y);
|
|
953
|
+
context.restore();
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
/** Clean up video on destroy */
|
|
957
|
+
destroy()
|
|
958
|
+
{
|
|
959
|
+
if (this.destroyed)
|
|
960
|
+
return;
|
|
961
|
+
|
|
962
|
+
this.video.pause();
|
|
963
|
+
this.video.remove();
|
|
964
|
+
super.destroy();
|
|
965
|
+
}
|
|
765
966
|
}
|
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.15.
|
|
33
|
+
const engineVersion = '1.15.3';
|
|
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
|
/**
|
|
@@ -487,10 +487,10 @@ function engineObjectsDestroy()
|
|
|
487
487
|
}
|
|
488
488
|
|
|
489
489
|
/** Collects all object within a given area
|
|
490
|
-
* @param {Vector2} [pos]
|
|
491
|
-
* @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
|
|
492
492
|
* @param {Array<EngineObject>} [objects=engineObjects] - List of objects to check
|
|
493
|
-
* @return {Array<EngineObject>}
|
|
493
|
+
* @return {Array<EngineObject>} - List of collected objects
|
|
494
494
|
* @memberof Engine */
|
|
495
495
|
function engineObjectsCollect(pos, size, objects=engineObjects)
|
|
496
496
|
{
|
package/src/engineAudio.js
CHANGED
package/src/engineDebug.js
CHANGED
|
@@ -70,8 +70,9 @@ function LOG(...output) { console.log(...output); }
|
|
|
70
70
|
* @param {number} [time]
|
|
71
71
|
* @param {number} [angle]
|
|
72
72
|
* @param {boolean} [fill]
|
|
73
|
+
* @param {boolean} [screenSpace]
|
|
73
74
|
* @memberof Debug */
|
|
74
|
-
function debugRect(pos, size=vec2(), color=WHITE, time=0, angle=0, fill=false)
|
|
75
|
+
function debugRect(pos, size=vec2(), color=WHITE, time=0, angle=0, fill=false, screenSpace=false)
|
|
75
76
|
{
|
|
76
77
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
77
78
|
ASSERT(isVector2(size), 'size must be a vec2');
|
|
@@ -86,7 +87,7 @@ function debugRect(pos, size=vec2(), color=WHITE, time=0, angle=0, fill=false)
|
|
|
86
87
|
pos = pos.copy();
|
|
87
88
|
size = size.copy();
|
|
88
89
|
const timer = new Timer(time);
|
|
89
|
-
debugPrimitives.push({pos:pos.copy(), size:size.copy(), color, timer, angle, fill});
|
|
90
|
+
debugPrimitives.push({pos:pos.copy(), size:size.copy(), color, timer, angle, fill, screenSpace});
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
/** Draw a debug poly in world space
|
|
@@ -96,8 +97,9 @@ function debugRect(pos, size=vec2(), color=WHITE, time=0, angle=0, fill=false)
|
|
|
96
97
|
* @param {number} [time]
|
|
97
98
|
* @param {number} [angle]
|
|
98
99
|
* @param {boolean} [fill]
|
|
100
|
+
* @param {boolean} [screenSpace]
|
|
99
101
|
* @memberof Debug */
|
|
100
|
-
function debugPoly(pos, points, color=WHITE, time=0, angle=0, fill=false)
|
|
102
|
+
function debugPoly(pos, points, color=WHITE, time=0, angle=0, fill=false, screenSpace=false)
|
|
101
103
|
{
|
|
102
104
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
103
105
|
ASSERT(isArray(points), 'points must be an array');
|
|
@@ -110,7 +112,7 @@ function debugPoly(pos, points, color=WHITE, time=0, angle=0, fill=false)
|
|
|
110
112
|
pos = pos.copy();
|
|
111
113
|
points = points.map(p=>p.copy());
|
|
112
114
|
const timer = new Timer(time);
|
|
113
|
-
debugPrimitives.push({pos, points, color, timer, angle, fill});
|
|
115
|
+
debugPrimitives.push({pos, points, color, timer, angle, fill, screenSpace});
|
|
114
116
|
}
|
|
115
117
|
|
|
116
118
|
/** Draw a debug circle in world space
|
|
@@ -119,8 +121,9 @@ function debugPoly(pos, points, color=WHITE, time=0, angle=0, fill=false)
|
|
|
119
121
|
* @param {Color|string} [color]
|
|
120
122
|
* @param {number} [time]
|
|
121
123
|
* @param {boolean} [fill]
|
|
124
|
+
* @param {boolean} [screenSpace]
|
|
122
125
|
* @memberof Debug */
|
|
123
|
-
function debugCircle(pos, size=0, color=WHITE, time=0, fill=false)
|
|
126
|
+
function debugCircle(pos, size=0, color=WHITE, time=0, fill=false, screenSpace=false)
|
|
124
127
|
{
|
|
125
128
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
126
129
|
ASSERT(isNumber(size), 'size must be a number');
|
|
@@ -131,7 +134,7 @@ function debugCircle(pos, size=0, color=WHITE, time=0, fill=false)
|
|
|
131
134
|
color = color.toString();
|
|
132
135
|
pos = pos.copy();
|
|
133
136
|
const timer = new Timer(time);
|
|
134
|
-
debugPrimitives.push({pos, size, color, timer, angle:0, fill});
|
|
137
|
+
debugPrimitives.push({pos, size, color, timer, angle:0, fill, screenSpace});
|
|
135
138
|
}
|
|
136
139
|
|
|
137
140
|
/** Draw a debug point in world space
|
|
@@ -139,9 +142,10 @@ function debugCircle(pos, size=0, color=WHITE, time=0, fill=false)
|
|
|
139
142
|
* @param {Color|string} [color]
|
|
140
143
|
* @param {number} [time]
|
|
141
144
|
* @param {number} [angle]
|
|
145
|
+
* @param {boolean} [screenSpace]
|
|
142
146
|
* @memberof Debug */
|
|
143
|
-
function debugPoint(pos, color, time, angle)
|
|
144
|
-
{ debugRect(pos, undefined, color, time, angle); }
|
|
147
|
+
function debugPoint(pos, color, time, angle, screenSpace=false)
|
|
148
|
+
{ debugRect(pos, undefined, color, time, angle, false, screenSpace); }
|
|
145
149
|
|
|
146
150
|
/** Draw a debug line in world space
|
|
147
151
|
* @param {Vector2} posA
|
|
@@ -149,8 +153,9 @@ function debugPoint(pos, color, time, angle)
|
|
|
149
153
|
* @param {Color|string} [color]
|
|
150
154
|
* @param {number} [width]
|
|
151
155
|
* @param {number} [time]
|
|
156
|
+
* @param {boolean} [screenSpace]
|
|
152
157
|
* @memberof Debug */
|
|
153
|
-
function debugLine(posA, posB, color, width=.1, time)
|
|
158
|
+
function debugLine(posA, posB, color, width=.1, time, screenSpace=false)
|
|
154
159
|
{
|
|
155
160
|
ASSERT(isVector2(posA), 'posA must be a vec2');
|
|
156
161
|
ASSERT(isVector2(posB), 'posB must be sa vec2');
|
|
@@ -158,7 +163,7 @@ function debugLine(posA, posB, color, width=.1, time)
|
|
|
158
163
|
|
|
159
164
|
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
160
165
|
const size = vec2(width, halfDelta.length()*2);
|
|
161
|
-
debugRect(posA.add(halfDelta), size, color, time, halfDelta.angle(), true);
|
|
166
|
+
debugRect(posA.add(halfDelta), size, color, time, halfDelta.angle(), true, screenSpace);
|
|
162
167
|
}
|
|
163
168
|
|
|
164
169
|
/** Draw a debug combined axis aligned bounding box in world space
|
|
@@ -167,8 +172,10 @@ function debugLine(posA, posB, color, width=.1, time)
|
|
|
167
172
|
* @param {Vector2} posB
|
|
168
173
|
* @param {Vector2} sizeB
|
|
169
174
|
* @param {Color|string} [color]
|
|
175
|
+
* @param {number} [time]
|
|
176
|
+
* @param {boolean} [screenSpace]
|
|
170
177
|
* @memberof Debug */
|
|
171
|
-
function debugOverlap(posA, sizeA, posB, sizeB, color)
|
|
178
|
+
function debugOverlap(posA, sizeA, posB, sizeB, color, time, screenSpace=false)
|
|
172
179
|
{
|
|
173
180
|
ASSERT(isVector2(posA), 'posA must be a vec2');
|
|
174
181
|
ASSERT(isVector2(posB), 'posB must be a vec2');
|
|
@@ -183,7 +190,7 @@ function debugOverlap(posA, sizeA, posB, sizeB, color)
|
|
|
183
190
|
max(posA.x + sizeA.x/2, posB.x + sizeB.x/2),
|
|
184
191
|
max(posA.y + sizeA.y/2, posB.y + sizeB.y/2)
|
|
185
192
|
);
|
|
186
|
-
debugRect(minPos.lerp(maxPos,.5), maxPos.subtract(minPos), color);
|
|
193
|
+
debugRect(minPos.lerp(maxPos,.5), maxPos.subtract(minPos), color, time, 0, false, screenSpace);
|
|
187
194
|
}
|
|
188
195
|
|
|
189
196
|
/** Draw a debug axis aligned bounding box in world space
|
|
@@ -194,8 +201,9 @@ function debugOverlap(posA, sizeA, posB, sizeB, color)
|
|
|
194
201
|
* @param {number} [time]
|
|
195
202
|
* @param {number} [angle]
|
|
196
203
|
* @param {string} [font]
|
|
204
|
+
* @param {boolean} [screenSpace]
|
|
197
205
|
* @memberof Debug */
|
|
198
|
-
function debugText(text, pos, size=1, color=WHITE, time=0, angle=0, font='monospace')
|
|
206
|
+
function debugText(text, pos, size=1, color=WHITE, time=0, angle=0, font='monospace', screenSpace=false)
|
|
199
207
|
{
|
|
200
208
|
ASSERT(isString(text), 'text must be a string');
|
|
201
209
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
@@ -209,7 +217,7 @@ function debugText(text, pos, size=1, color=WHITE, time=0, angle=0, font='monosp
|
|
|
209
217
|
color = color.toString();
|
|
210
218
|
pos = pos.copy();
|
|
211
219
|
const timer = new Timer(time);
|
|
212
|
-
debugPrimitives.push({text, pos, size, color, timer, angle, font});
|
|
220
|
+
debugPrimitives.push({text, pos, size, color, timer, angle, font, screenSpace});
|
|
213
221
|
}
|
|
214
222
|
|
|
215
223
|
/** Clear all debug primitives in the list
|
|
@@ -419,21 +427,26 @@ function debugRender()
|
|
|
419
427
|
{
|
|
420
428
|
// draw debug primitives
|
|
421
429
|
overlayContext.lineWidth = 2;
|
|
422
|
-
const pointSize = debugPointSize * cameraScale;
|
|
423
430
|
debugPrimitives.forEach(p=>
|
|
424
431
|
{
|
|
425
432
|
overlayContext.save();
|
|
426
433
|
|
|
427
434
|
// create canvas transform from world space to screen space
|
|
428
|
-
|
|
435
|
+
// without scaling because we want consistent pixel sizes
|
|
436
|
+
let pos = p.pos, scale = 1, angle = p.angle;
|
|
437
|
+
if (!p.screenSpace)
|
|
438
|
+
{
|
|
439
|
+
pos = worldToScreen(p.pos);
|
|
440
|
+
scale = cameraScale;
|
|
441
|
+
angle -= cameraAngle;
|
|
442
|
+
}
|
|
429
443
|
overlayContext.translate(pos.x|0, pos.y|0);
|
|
430
|
-
overlayContext.rotate(
|
|
444
|
+
overlayContext.rotate(angle);
|
|
431
445
|
overlayContext.scale(1, p.text ? 1 : -1);
|
|
432
446
|
overlayContext.fillStyle = overlayContext.strokeStyle = p.color;
|
|
433
|
-
|
|
434
447
|
if (p.text !== undefined)
|
|
435
448
|
{
|
|
436
|
-
overlayContext.font = p.size*
|
|
449
|
+
overlayContext.font = p.size*scale + 'px '+ p.font;
|
|
437
450
|
overlayContext.textAlign = 'center';
|
|
438
451
|
overlayContext.textBaseline = 'middle';
|
|
439
452
|
overlayContext.fillText(p.text, 0, 0);
|
|
@@ -444,7 +457,7 @@ function debugRender()
|
|
|
444
457
|
overlayContext.beginPath();
|
|
445
458
|
for (const point of p.points)
|
|
446
459
|
{
|
|
447
|
-
const p2 = point.scale(
|
|
460
|
+
const p2 = point.scale(scale).floor();
|
|
448
461
|
overlayContext.lineTo(p2.x, p2.y);
|
|
449
462
|
}
|
|
450
463
|
overlayContext.closePath();
|
|
@@ -454,13 +467,14 @@ function debugRender()
|
|
|
454
467
|
else if (p.size === 0 || (p.size.x === 0 && p.size.y === 0))
|
|
455
468
|
{
|
|
456
469
|
// point
|
|
470
|
+
const pointSize = debugPointSize * scale;
|
|
457
471
|
overlayContext.fillRect(-pointSize/2, -1, pointSize, 3);
|
|
458
472
|
overlayContext.fillRect(-1, -pointSize/2, 3, pointSize);
|
|
459
473
|
}
|
|
460
474
|
else if (p.size.x !== undefined)
|
|
461
475
|
{
|
|
462
476
|
// rect
|
|
463
|
-
const s = p.size.scale(
|
|
477
|
+
const s = p.size.scale(scale).floor();
|
|
464
478
|
const w = s.x, h = s.y;
|
|
465
479
|
p.fill && overlayContext.fillRect(-w/2|0, -h/2|0, w, h);
|
|
466
480
|
overlayContext.strokeRect(-w/2|0, -h/2|0, w, h);
|
|
@@ -469,7 +483,7 @@ function debugRender()
|
|
|
469
483
|
{
|
|
470
484
|
// circle
|
|
471
485
|
overlayContext.beginPath();
|
|
472
|
-
overlayContext.arc(0, 0, p.size*
|
|
486
|
+
overlayContext.arc(0, 0, p.size*scale/2, 0, 9);
|
|
473
487
|
p.fill && overlayContext.fill();
|
|
474
488
|
overlayContext.stroke();
|
|
475
489
|
}
|