littlejsengine 1.14.23 → 1.14.28
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 +99 -20
- package/dist/littlejs.esm.js +378 -130
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +373 -130
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +336 -130
- 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 +10 -7
- package/examples/platformer/gameCharacter.js +3 -2
- package/examples/platformer/gameObjects.js +3 -8
- package/examples/shorts/base.html +1 -1
- 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/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/starter/index.html +2 -2
- package/package.json +1 -1
- package/plugins/box2d.js +170 -50
- package/plugins/pluginExport.js +2 -0
- package/plugins/uiSystem.js +55 -27
- package/src/engine.js +17 -14
- package/src/engineAudio.js +14 -2
- package/src/engineDebug.js +37 -0
- package/src/engineDraw.js +20 -12
- 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 +1 -6
- package/src/engineTileLayer.js +2 -0
- package/src/engineUtilities.js +7 -1
package/plugins/uiSystem.js
CHANGED
|
@@ -145,12 +145,12 @@ class UISystemPlugin
|
|
|
145
145
|
/** Draw a rectangle to the UI context
|
|
146
146
|
* @param {Vector2} pos
|
|
147
147
|
* @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=
|
|
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=WHITE, lineWidth=0, lineColor=BLACK, cornerRadius=0, gradientColor)
|
|
154
154
|
{
|
|
155
155
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
156
156
|
ASSERT(isVector2(size), 'size must be a vec2');
|
|
@@ -229,10 +229,14 @@ class UISystemPlugin
|
|
|
229
229
|
* @param {string} [align]
|
|
230
230
|
* @param {string} [font=uiSystem.defaultFont]
|
|
231
231
|
* @param {string} [fontStyle]
|
|
232
|
-
* @param {boolean} [applyMaxWidth=true]
|
|
233
|
-
|
|
232
|
+
* @param {boolean} [applyMaxWidth=true]
|
|
233
|
+
* @param {Vector2} [textShadow]
|
|
234
|
+
*/
|
|
235
|
+
drawText(text, pos, size, color=uiSystem.defaultColor, lineWidth=uiSystem.defaultLineWidth, lineColor=uiSystem.defaultLineColor, align='center', font=uiSystem.defaultFont, fontStyle='', applyMaxWidth=true, textShadow=undefined)
|
|
234
236
|
{
|
|
235
|
-
|
|
237
|
+
if (textShadow)
|
|
238
|
+
drawTextScreen(text, pos.add(textShadow), size.y, BLACK, lineWidth, lineColor, align, font, fontStyle, applyMaxWidth ? size.x : undefined, 0, uiSystem.uiContext);
|
|
239
|
+
drawTextScreen(text, pos, size.y, color, lineWidth, lineColor, align, font, fontStyle, applyMaxWidth ? size.x : undefined, 0, uiSystem.uiContext);
|
|
236
240
|
}
|
|
237
241
|
|
|
238
242
|
/**
|
|
@@ -259,6 +263,25 @@ class UISystemPlugin
|
|
|
259
263
|
setCallback(onDragLeave, 'dragleave');
|
|
260
264
|
setCallback(onDragOver, 'dragover');
|
|
261
265
|
}
|
|
266
|
+
|
|
267
|
+
/** Convert a screen space position to native UI position
|
|
268
|
+
* @param {Vector2} pos
|
|
269
|
+
* @return {Vector2}
|
|
270
|
+
*/
|
|
271
|
+
screenToNative(pos)
|
|
272
|
+
{
|
|
273
|
+
if (!uiSystem.nativeHeight)
|
|
274
|
+
return pos;
|
|
275
|
+
|
|
276
|
+
const s = mainCanvasSize.y / uiSystem.nativeHeight;
|
|
277
|
+
const sInv = 1/s;
|
|
278
|
+
const p = pos.copy();
|
|
279
|
+
p.x += s*mainCanvasSize.x/2;
|
|
280
|
+
p.x *= sInv;
|
|
281
|
+
p.y *= sInv;
|
|
282
|
+
p.x -= sInv*mainCanvasSize.x/2;
|
|
283
|
+
return p;
|
|
284
|
+
}
|
|
262
285
|
}
|
|
263
286
|
|
|
264
287
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -335,6 +358,9 @@ class UIObject
|
|
|
335
358
|
/** @property {boolean} - True if this can be a hover object */
|
|
336
359
|
this.canBeHover = true;
|
|
337
360
|
uiSystem.uiObjects.push(this);
|
|
361
|
+
|
|
362
|
+
/** @property {Vector2} - How much to offset the text shadow or undefined */
|
|
363
|
+
this.textShadow = undefined;
|
|
338
364
|
}
|
|
339
365
|
|
|
340
366
|
/** Add a child UIObject to this object
|
|
@@ -362,24 +388,20 @@ class UIObject
|
|
|
362
388
|
*/
|
|
363
389
|
isMouseOverlapping()
|
|
364
390
|
{
|
|
391
|
+
if (!mouseInWindow) return false;
|
|
392
|
+
|
|
365
393
|
const size = !isTouchDevice ? this.size :
|
|
366
394
|
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;
|
|
395
|
+
const pos = uiSystem.screenToNative(mousePosScreen);
|
|
377
396
|
return isOverlapping(this.pos, size, pos);
|
|
378
397
|
}
|
|
379
398
|
|
|
380
399
|
/** Update the object, called automatically by plugin once each frame */
|
|
381
400
|
update()
|
|
382
401
|
{
|
|
402
|
+
// call the custom update callback
|
|
403
|
+
this.onUpdate();
|
|
404
|
+
|
|
383
405
|
const wasHover = uiSystem.lastHoverObject === this;
|
|
384
406
|
const isActive = this.isActiveObject();
|
|
385
407
|
const mouseDown = mouseIsDown(0);
|
|
@@ -436,7 +458,7 @@ class UIObject
|
|
|
436
458
|
|
|
437
459
|
const lineColor = this.interactive && this.isActiveObject() && !this.disabled ? this.color : this.lineColor;
|
|
438
460
|
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);
|
|
461
|
+
uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor, this.cornerRadius, this.gradientColor);
|
|
440
462
|
}
|
|
441
463
|
|
|
442
464
|
/** Special update when object is not visible */
|
|
@@ -463,6 +485,9 @@ class UIObject
|
|
|
463
485
|
/** @return {boolean} - Is the mouse held onto this element */
|
|
464
486
|
isActiveObject() { return uiSystem.activeObject === this; }
|
|
465
487
|
|
|
488
|
+
/** Called each frame when object updates */
|
|
489
|
+
onUpdate() {}
|
|
490
|
+
|
|
466
491
|
/** Called when the mouse enters the object */
|
|
467
492
|
onEnter() {}
|
|
468
493
|
|
|
@@ -517,8 +542,9 @@ class UIText extends UIObject
|
|
|
517
542
|
}
|
|
518
543
|
render()
|
|
519
544
|
{
|
|
545
|
+
// only render the text
|
|
520
546
|
const textSize = this.getTextSize();
|
|
521
|
-
uiSystem.drawText(this.text, this.pos, textSize, this.textColor, this.lineWidth, this.lineColor, this.align, this.font, this.fontStyle);
|
|
547
|
+
uiSystem.drawText(this.text, this.pos, textSize, this.textColor, this.lineWidth, this.lineColor, this.align, this.font, this.fontStyle, true, this.textShadow);
|
|
522
548
|
}
|
|
523
549
|
}
|
|
524
550
|
|
|
@@ -594,7 +620,7 @@ class UIButton extends UIObject
|
|
|
594
620
|
// draw the text scaled to fit
|
|
595
621
|
const textSize = this.getTextSize();
|
|
596
622
|
uiSystem.drawText(this.text, this.pos, textSize,
|
|
597
|
-
this.textColor, 0, undefined, this.align, this.font, this.fontStyle);
|
|
623
|
+
this.textColor, 0, undefined, this.align, this.font, this.fontStyle, true, this.textShadow);
|
|
598
624
|
}
|
|
599
625
|
}
|
|
600
626
|
|
|
@@ -648,7 +674,7 @@ class UICheckbox extends UIObject
|
|
|
648
674
|
const textSize = this.getTextSize();
|
|
649
675
|
const pos = this.pos.add(vec2(this.size.x,0));
|
|
650
676
|
uiSystem.drawText(this.text, pos, textSize,
|
|
651
|
-
this.textColor, 0, undefined, 'left', this.font, this.fontStyle, false);
|
|
677
|
+
this.textColor, 0, undefined, 'left', this.font, this.fontStyle, false, this.textShadow);
|
|
652
678
|
}
|
|
653
679
|
}
|
|
654
680
|
|
|
@@ -703,9 +729,11 @@ class UIScrollbar extends UIObject
|
|
|
703
729
|
const p1 = centerPos - handleWidth/2;
|
|
704
730
|
const p2 = centerPos + handleWidth/2;
|
|
705
731
|
const oldValue = this.value;
|
|
732
|
+
|
|
733
|
+
const p = uiSystem.screenToNative(mousePosScreen);
|
|
706
734
|
this.value = isHorizontal ?
|
|
707
|
-
percent(
|
|
708
|
-
percent(
|
|
735
|
+
percent(p.x, p1, p2) :
|
|
736
|
+
percent(p.y, p2, p1);
|
|
709
737
|
this.value === oldValue || this.onChange();
|
|
710
738
|
}
|
|
711
739
|
}
|
|
@@ -727,11 +755,11 @@ class UIScrollbar extends UIObject
|
|
|
727
755
|
vec2(lerp(p1, p2, this.value), this.pos.y) :
|
|
728
756
|
vec2(this.pos.x, lerp(p2, p1, this.value))
|
|
729
757
|
const handleColor = this.disabled ? this.disabledColor : this.handleColor;
|
|
730
|
-
uiSystem.drawRect(handlePos, vec2(handleSize), handleColor, this.lineWidth, this.lineColor, this.cornerRadius);
|
|
758
|
+
uiSystem.drawRect(handlePos, vec2(handleSize), handleColor, this.lineWidth, this.lineColor, this.cornerRadius, this.gradientColor);
|
|
731
759
|
|
|
732
760
|
// draw the text scaled to fit on the scrollbar
|
|
733
761
|
const textSize = this.getTextSize();
|
|
734
762
|
uiSystem.drawText(this.text, this.pos, textSize,
|
|
735
|
-
this.textColor, 0, undefined, this.align, this.font, this.fontStyle);
|
|
763
|
+
this.textColor, 0, undefined, this.align, this.font, this.fontStyle, true, this.textShadow);
|
|
736
764
|
}
|
|
737
765
|
}
|
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.0';
|
|
34
34
|
|
|
35
35
|
/** Frames per second to update
|
|
36
36
|
* @type {number}
|
|
@@ -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
|
|
@@ -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
|
{
|
package/src/engineDraw.js
CHANGED
|
@@ -403,7 +403,7 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
|
|
|
403
403
|
* @memberof Draw */
|
|
404
404
|
function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0, useWebGL=glEnable, screenSpace, context)
|
|
405
405
|
{
|
|
406
|
-
ASSERT(
|
|
406
|
+
ASSERT(isArray(points), 'points must be an array');
|
|
407
407
|
ASSERT(isNumber(width), 'width must be a number');
|
|
408
408
|
ASSERT(isColor(color), 'color is invalid');
|
|
409
409
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
@@ -509,7 +509,7 @@ function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, l
|
|
|
509
509
|
function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(), angle=0, useWebGL=glEnable, screenSpace=false, context=undefined)
|
|
510
510
|
{
|
|
511
511
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
512
|
-
ASSERT(
|
|
512
|
+
ASSERT(isArray(points), 'points must be an array');
|
|
513
513
|
ASSERT(isColor(color) && isColor(lineColor), 'color is invalid');
|
|
514
514
|
ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
|
|
515
515
|
ASSERT(isNumber(angle), 'angle must be a number');
|
|
@@ -661,11 +661,12 @@ function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpac
|
|
|
661
661
|
* @param {string} [font=fontDefault]
|
|
662
662
|
* @param {string} [fontStyle]
|
|
663
663
|
* @param {number} [maxWidth]
|
|
664
|
+
* @param {number} [angle]
|
|
664
665
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
665
666
|
* @memberof Draw */
|
|
666
|
-
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth, context=drawContext)
|
|
667
|
+
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth, angle=0, context=drawContext)
|
|
667
668
|
{
|
|
668
|
-
drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, fontStyle, maxWidth, context);
|
|
669
|
+
drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, fontStyle, maxWidth, angle, context);
|
|
669
670
|
}
|
|
670
671
|
|
|
671
672
|
/** Draw text on overlay canvas in world space
|
|
@@ -680,10 +681,11 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
|
|
|
680
681
|
* @param {string} [font=fontDefault]
|
|
681
682
|
* @param {string} [fontStyle]
|
|
682
683
|
* @param {number} [maxWidth]
|
|
684
|
+
* @param {number} [angle]
|
|
683
685
|
* @memberof Draw */
|
|
684
|
-
function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth)
|
|
686
|
+
function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth, angle=0)
|
|
685
687
|
{
|
|
686
|
-
drawText(text, pos, size, color, lineWidth, lineColor, textAlign, font, fontStyle, maxWidth, overlayContext);
|
|
688
|
+
drawText(text, pos, size, color, lineWidth, lineColor, textAlign, font, fontStyle, maxWidth, angle, overlayContext);
|
|
687
689
|
}
|
|
688
690
|
|
|
689
691
|
/** Draw text on overlay canvas in screen space
|
|
@@ -698,9 +700,10 @@ function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textA
|
|
|
698
700
|
* @param {string} [font=fontDefault]
|
|
699
701
|
* @param {string} [fontStyle]
|
|
700
702
|
* @param {number} [maxWidth]
|
|
703
|
+
* @param {number} [angle]
|
|
701
704
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
|
|
702
705
|
* @memberof Draw */
|
|
703
|
-
function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, context=overlayContext)
|
|
706
|
+
function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, angle=0, context=overlayContext)
|
|
704
707
|
{
|
|
705
708
|
ASSERT(isString(text), 'text must be a string');
|
|
706
709
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
@@ -711,6 +714,7 @@ function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=B
|
|
|
711
714
|
ASSERT(['left','center','right'].includes(textAlign), 'align must be left, center, or right');
|
|
712
715
|
ASSERT(isString(font), 'font must be a string');
|
|
713
716
|
ASSERT(isString(fontStyle), 'fontStyle must be a string');
|
|
717
|
+
ASSERT(isNumber(angle), 'angle must be a number');
|
|
714
718
|
|
|
715
719
|
context.fillStyle = color.toString();
|
|
716
720
|
context.strokeStyle = lineColor.toString();
|
|
@@ -720,14 +724,18 @@ function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=B
|
|
|
720
724
|
context.textBaseline = 'middle';
|
|
721
725
|
|
|
722
726
|
const lines = (text+'').split('\n');
|
|
723
|
-
|
|
724
|
-
|
|
727
|
+
const posY = pos.y - (lines.length-1) * size/2; // center vertically
|
|
728
|
+
context.save();
|
|
729
|
+
context.translate(pos.x, posY);
|
|
730
|
+
context.rotate(-angle);
|
|
731
|
+
let yOffset = 0;
|
|
725
732
|
lines.forEach(line=>
|
|
726
733
|
{
|
|
727
|
-
lineWidth && context.strokeText(line,
|
|
728
|
-
context.fillText(line,
|
|
729
|
-
|
|
734
|
+
lineWidth && context.strokeText(line, 0, yOffset, maxWidth);
|
|
735
|
+
context.fillText(line, 0, yOffset, maxWidth);
|
|
736
|
+
yOffset += size;
|
|
730
737
|
});
|
|
738
|
+
context.restore();
|
|
731
739
|
}
|
|
732
740
|
|
|
733
741
|
///////////////////////////////////////////////////////////////////////////////
|
package/src/engineExport.js
CHANGED
|
@@ -173,6 +173,7 @@ export
|
|
|
173
173
|
// Random
|
|
174
174
|
rand,
|
|
175
175
|
randInt,
|
|
176
|
+
randBool,
|
|
176
177
|
randSign,
|
|
177
178
|
randInCircle,
|
|
178
179
|
randVec2,
|
|
@@ -190,6 +191,7 @@ export
|
|
|
190
191
|
isVector2,
|
|
191
192
|
isNumber,
|
|
192
193
|
isString,
|
|
194
|
+
isArray,
|
|
193
195
|
|
|
194
196
|
// Default Colors
|
|
195
197
|
WHITE,
|
|
@@ -290,6 +292,7 @@ export
|
|
|
290
292
|
mouseDelta,
|
|
291
293
|
mouseDeltaScreen,
|
|
292
294
|
mouseWheel,
|
|
295
|
+
mouseInWindow,
|
|
293
296
|
isUsingGamepad,
|
|
294
297
|
inputPreventDefault,
|
|
295
298
|
setInputPreventDefault,
|
package/src/engineInput.js
CHANGED
|
@@ -119,6 +119,11 @@ let mouseDeltaScreen = vec2();
|
|
|
119
119
|
* @memberof Input */
|
|
120
120
|
let mouseWheel = 0;
|
|
121
121
|
|
|
122
|
+
/** True if mouse was inside the document window, set to false when mouse leaves
|
|
123
|
+
* @type {boolean}
|
|
124
|
+
* @memberof Input */
|
|
125
|
+
let mouseInWindow = true;
|
|
126
|
+
|
|
122
127
|
/** Returns true if user is using gamepad (has more recently pressed a gamepad button)
|
|
123
128
|
* @type {boolean}
|
|
124
129
|
* @memberof Input */
|
|
@@ -213,6 +218,7 @@ function inputInit()
|
|
|
213
218
|
document.addEventListener('mousedown', onMouseDown);
|
|
214
219
|
document.addEventListener('mouseup', onMouseUp);
|
|
215
220
|
document.addEventListener('mousemove', onMouseMove);
|
|
221
|
+
document.addEventListener('mouseleave', onMouseLeave);
|
|
216
222
|
document.addEventListener('wheel', onMouseWheel);
|
|
217
223
|
document.addEventListener('contextmenu', onContextMenu);
|
|
218
224
|
document.addEventListener('blur', onBlur);
|
|
@@ -237,14 +243,14 @@ function inputInit()
|
|
|
237
243
|
if (inputWASDEmulateDirection)
|
|
238
244
|
inputData[0][remapKey(e.code)] = 4;
|
|
239
245
|
}
|
|
240
|
-
function remapKey(
|
|
246
|
+
function remapKey(k)
|
|
241
247
|
{
|
|
242
248
|
// handle remapping wasd keys to directions
|
|
243
249
|
return inputWASDEmulateDirection ?
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
250
|
+
k === 'KeyW' ? 'ArrowUp' :
|
|
251
|
+
k === 'KeyS' ? 'ArrowDown' :
|
|
252
|
+
k === 'KeyA' ? 'ArrowLeft' :
|
|
253
|
+
k === 'KeyD' ? 'ArrowRight' : k : k;
|
|
248
254
|
}
|
|
249
255
|
function onMouseDown(e)
|
|
250
256
|
{
|
|
@@ -258,7 +264,7 @@ function inputInit()
|
|
|
258
264
|
isUsingGamepad = false;
|
|
259
265
|
inputData[0][e.button] = 3;
|
|
260
266
|
|
|
261
|
-
|
|
267
|
+
const mousePosScreenLast = mousePosScreen;
|
|
262
268
|
mousePosScreen = mouseEventToScreen(vec2(e.x,e.y));
|
|
263
269
|
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
|
|
264
270
|
inputPreventDefault && e.button && e.preventDefault();
|
|
@@ -271,10 +277,17 @@ function inputInit()
|
|
|
271
277
|
}
|
|
272
278
|
function onMouseMove(e)
|
|
273
279
|
{
|
|
274
|
-
|
|
280
|
+
mouseInWindow = true;
|
|
281
|
+
const mousePosScreenLast = mousePosScreen;
|
|
275
282
|
mousePosScreen = mouseEventToScreen(vec2(e.x,e.y));
|
|
276
|
-
|
|
283
|
+
|
|
284
|
+
// when pointer is locked use movementX/Y for delta
|
|
285
|
+
const movement = pointerLockIsActive() ?
|
|
286
|
+
vec2(e.movementX, e.movementY) :
|
|
287
|
+
mousePosScreen.subtract(mousePosScreenLast);
|
|
288
|
+
mouseDeltaScreen = mouseDeltaScreen.add(movement);
|
|
277
289
|
}
|
|
290
|
+
function onMouseLeave() { mouseInWindow = false; } // mouse moved off window
|
|
278
291
|
function onMouseWheel(e) { mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY); }
|
|
279
292
|
function onContextMenu(e) { e.preventDefault(); } // prevent right click menu
|
|
280
293
|
function onBlur() { inputClear(); } // reset input when focus is lost
|
|
@@ -413,7 +426,7 @@ let touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadStick =
|
|
|
413
426
|
function touchGamepadButtonCenter()
|
|
414
427
|
{
|
|
415
428
|
// draw right face buttons
|
|
416
|
-
|
|
429
|
+
const center = vec2(mainCanvasSize.x-touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
417
430
|
if (touchGamepadButtonCount <= 2)
|
|
418
431
|
center.x += touchGamepadSize/2;
|
|
419
432
|
return center;
|
|
@@ -423,14 +436,13 @@ function touchGamepadButtonCenter()
|
|
|
423
436
|
function touchInputInit()
|
|
424
437
|
{
|
|
425
438
|
// add non passive touch event listeners
|
|
426
|
-
let handleTouch = handleTouchDefault;
|
|
427
439
|
document.addEventListener('touchstart', (e) => handleTouch(e), { passive: false });
|
|
428
440
|
document.addEventListener('touchmove', (e) => handleTouch(e), { passive: false });
|
|
429
441
|
document.addEventListener('touchend', (e) => handleTouch(e), { passive: false });
|
|
430
442
|
|
|
431
443
|
// handle all touch events the same way
|
|
432
444
|
let wasTouching;
|
|
433
|
-
function
|
|
445
|
+
function handleTouch(e)
|
|
434
446
|
{
|
|
435
447
|
if (!touchInputEnable)
|
|
436
448
|
return;
|
|
@@ -581,7 +593,7 @@ function touchGamepadRender()
|
|
|
581
593
|
const buttonSize = touchGamepadButtonCount > 1 ? touchGamepadSize/4 : touchGamepadSize/2;
|
|
582
594
|
for (let i=0; i<touchGamepadButtonCount; i++)
|
|
583
595
|
{
|
|
584
|
-
|
|
596
|
+
const j = mod(i-1, 4);
|
|
585
597
|
let button = touchGamepadButtonCount > 2 ?
|
|
586
598
|
j : min(j, touchGamepadButtonCount-1);
|
|
587
599
|
// fix button locations (swap 2 and 3 to match gamepad layout)
|
package/src/engineMedals.js
CHANGED
|
@@ -167,11 +167,11 @@ class Medal
|
|
|
167
167
|
const descriptionSize = height*.3;
|
|
168
168
|
const pos = vec2(x + medalDisplayIconSize + 2*gap.x, y + gap.y*2 + nameSize/2);
|
|
169
169
|
const textWidth = width - medalDisplayIconSize - 3*gap.x;
|
|
170
|
-
drawTextScreen(this.name, pos, nameSize, BLACK, 0, undefined, 'left', undefined, textWidth);
|
|
170
|
+
drawTextScreen(this.name, pos, nameSize, BLACK, 0, undefined, 'left', undefined, undefined, textWidth);
|
|
171
171
|
|
|
172
172
|
// draw the description
|
|
173
173
|
pos.y = y + height - gap.y*2 - descriptionSize/2;
|
|
174
|
-
drawTextScreen(this.description, pos, descriptionSize, BLACK, 0, undefined, 'left', undefined, textWidth);
|
|
174
|
+
drawTextScreen(this.description, pos, descriptionSize, BLACK, 0, undefined, 'left', undefined, undefined, textWidth);
|
|
175
175
|
context.restore();
|
|
176
176
|
}
|
|
177
177
|
|