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/src/engineDraw.js
CHANGED
|
@@ -294,7 +294,7 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
|
|
|
294
294
|
{
|
|
295
295
|
// normal canvas 2D rendering method (slower)
|
|
296
296
|
++drawCount;
|
|
297
|
-
size = new Vector2(size.x, -size.y); //
|
|
297
|
+
size = new Vector2(size.x, -size.y); // flip upside down sprites
|
|
298
298
|
drawCanvas2D(pos, size, angle, mirror, (context)=>
|
|
299
299
|
{
|
|
300
300
|
if (textureInfo)
|
|
@@ -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
|
|
package/src/engineObject.js
CHANGED
|
@@ -135,11 +135,10 @@ class EngineObject
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
/** Update the object physics, called automatically by engine once each frame */
|
|
138
|
-
|
|
138
|
+
updatePhysics()
|
|
139
139
|
{
|
|
140
140
|
// child objects do not have physics
|
|
141
|
-
|
|
142
|
-
return;
|
|
141
|
+
ASSERT(!this.parent);
|
|
143
142
|
|
|
144
143
|
if (this.clampSpeed)
|
|
145
144
|
{
|
|
@@ -189,7 +188,7 @@ class EngineObject
|
|
|
189
188
|
continue;
|
|
190
189
|
|
|
191
190
|
// check collision
|
|
192
|
-
if (!
|
|
191
|
+
if (!this.isOverlappingObject(o))
|
|
193
192
|
continue;
|
|
194
193
|
|
|
195
194
|
// notify objects of collision and check if should be resolved
|
|
@@ -337,6 +336,9 @@ class EngineObject
|
|
|
337
336
|
}
|
|
338
337
|
}
|
|
339
338
|
|
|
339
|
+
/** Update the object, called automatically by engine once each frame. Does nothing by default. */
|
|
340
|
+
update() {}
|
|
341
|
+
|
|
340
342
|
/** Render the object, draws a tile by default, automatically called each frame, sorted by renderOrder */
|
|
341
343
|
render()
|
|
342
344
|
{
|
|
@@ -402,6 +404,10 @@ class EngineObject
|
|
|
402
404
|
* @return {number} */
|
|
403
405
|
getAliveTime() { return time - this.spawnTime; }
|
|
404
406
|
|
|
407
|
+
/** Get the speed of this object
|
|
408
|
+
* @return {number} */
|
|
409
|
+
getSpeed() { return this.velocity.length(); }
|
|
410
|
+
|
|
405
411
|
/** Apply acceleration to this object (adjust velocity, not affected by mass)
|
|
406
412
|
* @param {Vector2} acceleration */
|
|
407
413
|
applyAcceleration(acceleration)
|
|
@@ -446,6 +452,20 @@ class EngineObject
|
|
|
446
452
|
child.parent = 0;
|
|
447
453
|
}
|
|
448
454
|
|
|
455
|
+
/** Check if overlapping another engine object
|
|
456
|
+
* Collisions are resoloved to prevent overlaps
|
|
457
|
+
* @param {EngineObject} object
|
|
458
|
+
* @return {boolean} */
|
|
459
|
+
isOverlappingObject(object)
|
|
460
|
+
{ return this.isOverlapping(object.pos, object.size); }
|
|
461
|
+
|
|
462
|
+
/** Check if overlapping a point or aligned bounding box
|
|
463
|
+
* @param {Vector2} pos - Center of box
|
|
464
|
+
* @param {Vector2} [size=(0,0)] - Size of box, uses a point if undefined
|
|
465
|
+
* @return {boolean} */
|
|
466
|
+
isOverlapping(pos, size=vec2())
|
|
467
|
+
{ return isOverlapping(this.pos, this.size, pos, size); }
|
|
468
|
+
|
|
449
469
|
/** Set how this object collides
|
|
450
470
|
* @param {boolean} [collideSolidObjects] - Does it collide with solid objects?
|
|
451
471
|
* @param {boolean} [isSolid] - Does it collide with and block other objects? (expensive in large numbers)
|
package/src/engineParticles.js
CHANGED
|
@@ -160,12 +160,12 @@ class ParticleEmitter extends EngineObject
|
|
|
160
160
|
this.previousPos = this.pos.copy();
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
/** Emitters do not have physics */
|
|
164
|
+
updatePhysics() {}
|
|
165
|
+
|
|
163
166
|
/** Update the emitter to spawn particles, called automatically by engine once each frame */
|
|
164
167
|
update()
|
|
165
168
|
{
|
|
166
|
-
// only do default update to apply parent transforms
|
|
167
|
-
this.parent && super.update();
|
|
168
|
-
|
|
169
169
|
if (this.velocityInheritance)
|
|
170
170
|
{
|
|
171
171
|
// pass emitter velocity to particles
|
|
@@ -182,7 +182,7 @@ class ParticleEmitter extends EngineObject
|
|
|
182
182
|
if (!this.emitTime || this.getAliveTime() <= this.emitTime)
|
|
183
183
|
{
|
|
184
184
|
// emit particles
|
|
185
|
-
if (this.emitRate
|
|
185
|
+
if (this.emitRate && particleEmitRateScale)
|
|
186
186
|
{
|
|
187
187
|
const rate = 1/this.emitRate/particleEmitRateScale;
|
|
188
188
|
for (this.emitTimeBuffer += timeDelta; this.emitTimeBuffer > 0; this.emitTimeBuffer -= rate)
|
|
@@ -320,8 +320,6 @@ class Particle extends EngineObject
|
|
|
320
320
|
/** Update the object physics, called automatically by engine once each frame */
|
|
321
321
|
update()
|
|
322
322
|
{
|
|
323
|
-
super.update();
|
|
324
|
-
|
|
325
323
|
if (this.collideTiles || this.collideSolidObjects)
|
|
326
324
|
{
|
|
327
325
|
// only apply max circular speed if particle can collide
|
package/src/engineTileLayer.js
CHANGED
|
@@ -48,6 +48,7 @@ function tileCollisionTest(pos, size=vec2(), object, solidOnly=true)
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/** Return the exact position of the boudnary of first tile hit, undefined if nothing was hit.
|
|
51
|
+
* The point will be inside the colliding tile if it hits (may have a tiny shift)
|
|
51
52
|
* @param {Vector2} posStart
|
|
52
53
|
* @param {Vector2} posEnd
|
|
53
54
|
* @param {EngineObject} [object] - An object or undefined for generic test
|
|
@@ -621,6 +622,7 @@ class TileCollisionLayer extends TileLayer
|
|
|
621
622
|
}
|
|
622
623
|
|
|
623
624
|
/** Return the exact position of the boudnary of first tile hit, undefined if nothing was hit.
|
|
625
|
+
* The point will be inside the colliding tile if it hits (may have a tiny shift)
|
|
624
626
|
* @param {Vector2} posStart
|
|
625
627
|
* @param {Vector2} posEnd
|
|
626
628
|
* @param {EngineObject} [object] - An object or undefined for generic test
|
package/src/engineUtilities.js
CHANGED
|
@@ -148,7 +148,7 @@ function percentLerp(value, percentA, percentB, lerpA, lerpB)
|
|
|
148
148
|
* @param {number} valueA
|
|
149
149
|
* @param {number} valueB
|
|
150
150
|
* @param {number} [wrapSize]
|
|
151
|
-
* @
|
|
151
|
+
* @return {number}
|
|
152
152
|
* @memberof Utilities */
|
|
153
153
|
function distanceWrap(valueA, valueB, wrapSize=1)
|
|
154
154
|
{ const d = (valueA - valueB) % wrapSize; return d*2 % wrapSize - d; }
|
|
@@ -158,7 +158,7 @@ function distanceWrap(valueA, valueB, wrapSize=1)
|
|
|
158
158
|
* @param {number} valueB
|
|
159
159
|
* @param {number} percent
|
|
160
160
|
* @param {number} [wrapSize]
|
|
161
|
-
* @
|
|
161
|
+
* @return {number}
|
|
162
162
|
* @memberof Utilities */
|
|
163
163
|
function lerpWrap(valueA, valueB, percent, wrapSize=1)
|
|
164
164
|
{
|
|
@@ -170,7 +170,7 @@ function lerpWrap(valueA, valueB, percent, wrapSize=1)
|
|
|
170
170
|
/** Returns signed wrapped distance between the two angles passed in
|
|
171
171
|
* @param {number} angleA
|
|
172
172
|
* @param {number} angleB
|
|
173
|
-
* @
|
|
173
|
+
* @return {number}
|
|
174
174
|
* @memberof Utilities */
|
|
175
175
|
function distanceAngle(angleA, angleB) { return distanceWrap(angleA, angleB, 2*PI); }
|
|
176
176
|
|
|
@@ -178,7 +178,7 @@ function distanceAngle(angleA, angleB) { return distanceWrap(angleA, angleB, 2*P
|
|
|
178
178
|
* @param {number} angleA
|
|
179
179
|
* @param {number} angleB
|
|
180
180
|
* @param {number} percent
|
|
181
|
-
* @
|
|
181
|
+
* @return {number}
|
|
182
182
|
* @memberof Utilities */
|
|
183
183
|
function lerpAngle(angleA, angleB, percent) { return lerpWrap(angleA, angleB, percent, 2*PI); }
|
|
184
184
|
|
|
@@ -305,6 +305,13 @@ function isNumber(n) { return typeof n === 'number' && !isNaN(n); }
|
|
|
305
305
|
* @memberof Utilities */
|
|
306
306
|
function isString(s) { return s !== undefined && s !== null && typeof s.toString() === 'string'; }
|
|
307
307
|
|
|
308
|
+
/**
|
|
309
|
+
* Check if object is an array
|
|
310
|
+
* @param {any} a
|
|
311
|
+
* @return {boolean}
|
|
312
|
+
* @memberof Utilities */
|
|
313
|
+
function isArray(a) { return Array.isArray(a); }
|
|
314
|
+
|
|
308
315
|
/**
|
|
309
316
|
* @callback LineTestFunction - Checks if a position is colliding
|
|
310
317
|
* @param {Vector2} pos
|
|
@@ -1004,7 +1011,6 @@ class Color
|
|
|
1004
1011
|
* @return {string} */
|
|
1005
1012
|
toString(useAlpha = true)
|
|
1006
1013
|
{
|
|
1007
|
-
ASSERT(typeof useAlpha === 'boolean', 'Use alpha boolean is invalid.', useAlpha);
|
|
1008
1014
|
if (debug && !this.isValid())
|
|
1009
1015
|
return `#000`;
|
|
1010
1016
|
const toHex = (c)=> ((c=clamp(c)*255|0)<16 ? '0' : '') + c.toString(16);
|
|
@@ -1141,11 +1147,14 @@ const MAGENTA = debugProtectConstant(rgb(1,0,1));
|
|
|
1141
1147
|
class Timer
|
|
1142
1148
|
{
|
|
1143
1149
|
/** Create a timer object set time passed in
|
|
1144
|
-
* @param {number} [timeLeft] - How much time left before the timer
|
|
1145
|
-
|
|
1150
|
+
* @param {number} [timeLeft] - How much time left before the timer
|
|
1151
|
+
* @param {boolean} [useRealTime] - Should the timer keep running even when the game is paused? (useful for UI) */
|
|
1152
|
+
constructor(timeLeft, useRealTime=false)
|
|
1146
1153
|
{
|
|
1147
1154
|
ASSERT(timeLeft === undefined || isNumber(timeLeft), 'Constructed Timer is invalid.', timeLeft);
|
|
1148
|
-
this.
|
|
1155
|
+
this.useRealTime = useRealTime;
|
|
1156
|
+
const globalTime = this.getGlobalTime();
|
|
1157
|
+
this.time = timeLeft === undefined ? undefined : globalTime + timeLeft;
|
|
1149
1158
|
this.setTime = timeLeft;
|
|
1150
1159
|
}
|
|
1151
1160
|
|
|
@@ -1154,10 +1163,19 @@ class Timer
|
|
|
1154
1163
|
set(timeLeft=0)
|
|
1155
1164
|
{
|
|
1156
1165
|
ASSERT(isNumber(timeLeft), 'Timer is invalid.', timeLeft);
|
|
1157
|
-
|
|
1166
|
+
const globalTime = this.getGlobalTime();
|
|
1167
|
+
this.time = globalTime + timeLeft;
|
|
1158
1168
|
this.setTime = timeLeft;
|
|
1159
1169
|
}
|
|
1160
1170
|
|
|
1171
|
+
/** Set if the timer should keep running even when the game is paused
|
|
1172
|
+
* @param {boolean} [useRealTime] */
|
|
1173
|
+
setUseRealTime(useRealTime=true)
|
|
1174
|
+
{
|
|
1175
|
+
ASSERT(!this.isSet(), 'Cannot change global time setting while timer is set.');
|
|
1176
|
+
this.useRealTime = useRealTime;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1161
1179
|
/** Unset the timer */
|
|
1162
1180
|
unset() { this.time = undefined; }
|
|
1163
1181
|
|
|
@@ -1167,24 +1185,28 @@ class Timer
|
|
|
1167
1185
|
|
|
1168
1186
|
/** Returns true if set and has not elapsed
|
|
1169
1187
|
* @return {boolean} */
|
|
1170
|
-
active() { return
|
|
1188
|
+
active() { return this.getGlobalTime() < this.time; }
|
|
1171
1189
|
|
|
1172
1190
|
/** Returns true if set and elapsed
|
|
1173
1191
|
* @return {boolean} */
|
|
1174
|
-
elapsed() { return
|
|
1192
|
+
elapsed() { return this.getGlobalTime() >= this.time; }
|
|
1175
1193
|
|
|
1176
1194
|
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
1177
1195
|
* @return {number} */
|
|
1178
|
-
get() { return this.isSet()?
|
|
1196
|
+
get() { return this.isSet()? this.getGlobalTime() - this.time : 0; }
|
|
1179
1197
|
|
|
1180
1198
|
/** Get percentage elapsed based on time it was set to, returns 0 if not set
|
|
1181
1199
|
* @return {number} */
|
|
1182
|
-
getPercent() { return this.isSet()? 1-percent(this.time -
|
|
1200
|
+
getPercent() { return this.isSet()? 1-percent(this.time - this.getGlobalTime(), 0, this.setTime) : 0; }
|
|
1183
1201
|
|
|
1184
1202
|
/** Get the time this timer was set to, returns 0 if not set
|
|
1185
1203
|
* @return {number} */
|
|
1186
1204
|
getSetTime() { return this.isSet() ? this.setTime : 0; }
|
|
1187
1205
|
|
|
1206
|
+
/** Get the current global time this timer is based on
|
|
1207
|
+
* @return {number} */
|
|
1208
|
+
getGlobalTime() { return this.useRealTime ? timeReal : time; }
|
|
1209
|
+
|
|
1188
1210
|
/** Returns this timer expressed as a string
|
|
1189
1211
|
* @return {string} */
|
|
1190
1212
|
toString() { return this.isSet() ? abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ) : 'unset'; }
|