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/dist/littlejs.release.js
CHANGED
|
@@ -33,7 +33,7 @@ const engineName = 'LittleJS';
|
|
|
33
33
|
* @type {string}
|
|
34
34
|
* @default
|
|
35
35
|
* @memberof Engine */
|
|
36
|
-
const engineVersion = '1.
|
|
36
|
+
const engineVersion = '1.15.0';
|
|
37
37
|
|
|
38
38
|
/** Frames per second to update
|
|
39
39
|
* @type {number}
|
|
@@ -164,7 +164,7 @@ function engineAddPlugin(update, render, glContextLost, glContextRestored)
|
|
|
164
164
|
async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
|
|
165
165
|
{
|
|
166
166
|
ASSERT(!mainContext, 'engine already initialized');
|
|
167
|
-
ASSERT(
|
|
167
|
+
ASSERT(isArray(imageSources), 'pass in images as array');
|
|
168
168
|
|
|
169
169
|
// allow passing in empty functions
|
|
170
170
|
gameInit ||= ()=>{};
|
|
@@ -456,21 +456,24 @@ function engineObjectsUpdate()
|
|
|
456
456
|
// recursive object update
|
|
457
457
|
function updateObject(o)
|
|
458
458
|
{
|
|
459
|
-
if (
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
459
|
+
if (o.destroyed)
|
|
460
|
+
return;
|
|
461
|
+
|
|
462
|
+
o.update();
|
|
463
|
+
for (const child of o.children)
|
|
464
|
+
updateObject(child);
|
|
465
465
|
}
|
|
466
466
|
for (const o of engineObjects)
|
|
467
467
|
{
|
|
468
|
+
if (o.parent)
|
|
469
|
+
continue;
|
|
470
|
+
|
|
468
471
|
// update top level objects
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
472
|
+
o.update();
|
|
473
|
+
o.updatePhysics();
|
|
474
|
+
for (const child of o.children)
|
|
475
|
+
updateObject(child);
|
|
476
|
+
o.updateTransforms();
|
|
474
477
|
}
|
|
475
478
|
|
|
476
479
|
// remove destroyed objects
|
|
@@ -503,7 +506,7 @@ function engineObjectsCollect(pos, size, objects=engineObjects)
|
|
|
503
506
|
else if (size instanceof Vector2) // bounding box test
|
|
504
507
|
{
|
|
505
508
|
for (const o of objects)
|
|
506
|
-
isOverlapping(pos, size
|
|
509
|
+
o.isOverlapping(pos, size) && collectedObjects.push(o);
|
|
507
510
|
}
|
|
508
511
|
else // circle test
|
|
509
512
|
{
|
|
@@ -1062,6 +1065,13 @@ function isNumber(n) { return typeof n === 'number' && !isNaN(n); }
|
|
|
1062
1065
|
* @memberof Utilities */
|
|
1063
1066
|
function isString(s) { return s !== undefined && s !== null && typeof s.toString() === 'string'; }
|
|
1064
1067
|
|
|
1068
|
+
/**
|
|
1069
|
+
* Check if object is an array
|
|
1070
|
+
* @param {any} a
|
|
1071
|
+
* @return {boolean}
|
|
1072
|
+
* @memberof Utilities */
|
|
1073
|
+
function isArray(a) { return Array.isArray(a); }
|
|
1074
|
+
|
|
1065
1075
|
/**
|
|
1066
1076
|
* @callback LineTestFunction - Checks if a position is colliding
|
|
1067
1077
|
* @param {Vector2} pos
|
|
@@ -1761,7 +1771,6 @@ class Color
|
|
|
1761
1771
|
* @return {string} */
|
|
1762
1772
|
toString(useAlpha = true)
|
|
1763
1773
|
{
|
|
1764
|
-
ASSERT(typeof useAlpha === 'boolean', 'Use alpha boolean is invalid.', useAlpha);
|
|
1765
1774
|
if (debug && !this.isValid())
|
|
1766
1775
|
return `#000`;
|
|
1767
1776
|
const toHex = (c)=> ((c=clamp(c)*255|0)<16 ? '0' : '') + c.toString(16);
|
|
@@ -2659,11 +2668,10 @@ class EngineObject
|
|
|
2659
2668
|
}
|
|
2660
2669
|
|
|
2661
2670
|
/** Update the object physics, called automatically by engine once each frame */
|
|
2662
|
-
|
|
2671
|
+
updatePhysics()
|
|
2663
2672
|
{
|
|
2664
2673
|
// child objects do not have physics
|
|
2665
|
-
|
|
2666
|
-
return;
|
|
2674
|
+
ASSERT(!this.parent);
|
|
2667
2675
|
|
|
2668
2676
|
if (this.clampSpeed)
|
|
2669
2677
|
{
|
|
@@ -2713,7 +2721,7 @@ class EngineObject
|
|
|
2713
2721
|
continue;
|
|
2714
2722
|
|
|
2715
2723
|
// check collision
|
|
2716
|
-
if (!
|
|
2724
|
+
if (!this.isOverlappingObject(o))
|
|
2717
2725
|
continue;
|
|
2718
2726
|
|
|
2719
2727
|
// notify objects of collision and check if should be resolved
|
|
@@ -2861,6 +2869,9 @@ class EngineObject
|
|
|
2861
2869
|
}
|
|
2862
2870
|
}
|
|
2863
2871
|
|
|
2872
|
+
/** Update the object, called automatically by engine once each frame. Does nothing by default. */
|
|
2873
|
+
update() {}
|
|
2874
|
+
|
|
2864
2875
|
/** Render the object, draws a tile by default, automatically called each frame, sorted by renderOrder */
|
|
2865
2876
|
render()
|
|
2866
2877
|
{
|
|
@@ -2926,6 +2937,10 @@ class EngineObject
|
|
|
2926
2937
|
* @return {number} */
|
|
2927
2938
|
getAliveTime() { return time - this.spawnTime; }
|
|
2928
2939
|
|
|
2940
|
+
/** Get the speed of this object
|
|
2941
|
+
* @return {number} */
|
|
2942
|
+
getSpeed() { return this.velocity.length(); }
|
|
2943
|
+
|
|
2929
2944
|
/** Apply acceleration to this object (adjust velocity, not affected by mass)
|
|
2930
2945
|
* @param {Vector2} acceleration */
|
|
2931
2946
|
applyAcceleration(acceleration)
|
|
@@ -2970,6 +2985,20 @@ class EngineObject
|
|
|
2970
2985
|
child.parent = 0;
|
|
2971
2986
|
}
|
|
2972
2987
|
|
|
2988
|
+
/** Check if overlapping another engine object
|
|
2989
|
+
* Collisions are resoloved to prevent overlaps
|
|
2990
|
+
* @param {EngineObject} object
|
|
2991
|
+
* @return {boolean} */
|
|
2992
|
+
isOverlappingObject(object)
|
|
2993
|
+
{ return this.isOverlapping(object.pos, object.size); }
|
|
2994
|
+
|
|
2995
|
+
/** Check if overlapping a point or aligned bounding box
|
|
2996
|
+
* @param {Vector2} pos - Center of box
|
|
2997
|
+
* @param {Vector2} [size=(0,0)] - Size of box, uses a point if undefined
|
|
2998
|
+
* @return {boolean} */
|
|
2999
|
+
isOverlapping(pos, size=vec2())
|
|
3000
|
+
{ return isOverlapping(this.pos, this.size, pos, size); }
|
|
3001
|
+
|
|
2973
3002
|
/** Set how this object collides
|
|
2974
3003
|
* @param {boolean} [collideSolidObjects] - Does it collide with solid objects?
|
|
2975
3004
|
* @param {boolean} [isSolid] - Does it collide with and block other objects? (expensive in large numbers)
|
|
@@ -3424,7 +3453,7 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
|
|
|
3424
3453
|
* @memberof Draw */
|
|
3425
3454
|
function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0, useWebGL=glEnable, screenSpace, context)
|
|
3426
3455
|
{
|
|
3427
|
-
ASSERT(
|
|
3456
|
+
ASSERT(isArray(points), 'points must be an array');
|
|
3428
3457
|
ASSERT(isNumber(width), 'width must be a number');
|
|
3429
3458
|
ASSERT(isColor(color), 'color is invalid');
|
|
3430
3459
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
@@ -3530,7 +3559,7 @@ function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, l
|
|
|
3530
3559
|
function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(), angle=0, useWebGL=glEnable, screenSpace=false, context=undefined)
|
|
3531
3560
|
{
|
|
3532
3561
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
3533
|
-
ASSERT(
|
|
3562
|
+
ASSERT(isArray(points), 'points must be an array');
|
|
3534
3563
|
ASSERT(isColor(color) && isColor(lineColor), 'color is invalid');
|
|
3535
3564
|
ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
|
|
3536
3565
|
ASSERT(isNumber(angle), 'angle must be a number');
|
|
@@ -3682,11 +3711,12 @@ function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpac
|
|
|
3682
3711
|
* @param {string} [font=fontDefault]
|
|
3683
3712
|
* @param {string} [fontStyle]
|
|
3684
3713
|
* @param {number} [maxWidth]
|
|
3714
|
+
* @param {number} [angle]
|
|
3685
3715
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
3686
3716
|
* @memberof Draw */
|
|
3687
|
-
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth, context=drawContext)
|
|
3717
|
+
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth, angle=0, context=drawContext)
|
|
3688
3718
|
{
|
|
3689
|
-
drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, fontStyle, maxWidth, context);
|
|
3719
|
+
drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, fontStyle, maxWidth, angle, context);
|
|
3690
3720
|
}
|
|
3691
3721
|
|
|
3692
3722
|
/** Draw text on overlay canvas in world space
|
|
@@ -3701,10 +3731,11 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
|
|
|
3701
3731
|
* @param {string} [font=fontDefault]
|
|
3702
3732
|
* @param {string} [fontStyle]
|
|
3703
3733
|
* @param {number} [maxWidth]
|
|
3734
|
+
* @param {number} [angle]
|
|
3704
3735
|
* @memberof Draw */
|
|
3705
|
-
function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth)
|
|
3736
|
+
function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth, angle=0)
|
|
3706
3737
|
{
|
|
3707
|
-
drawText(text, pos, size, color, lineWidth, lineColor, textAlign, font, fontStyle, maxWidth, overlayContext);
|
|
3738
|
+
drawText(text, pos, size, color, lineWidth, lineColor, textAlign, font, fontStyle, maxWidth, angle, overlayContext);
|
|
3708
3739
|
}
|
|
3709
3740
|
|
|
3710
3741
|
/** Draw text on overlay canvas in screen space
|
|
@@ -3719,9 +3750,10 @@ function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textA
|
|
|
3719
3750
|
* @param {string} [font=fontDefault]
|
|
3720
3751
|
* @param {string} [fontStyle]
|
|
3721
3752
|
* @param {number} [maxWidth]
|
|
3753
|
+
* @param {number} [angle]
|
|
3722
3754
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
|
|
3723
3755
|
* @memberof Draw */
|
|
3724
|
-
function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, context=overlayContext)
|
|
3756
|
+
function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, angle=0, context=overlayContext)
|
|
3725
3757
|
{
|
|
3726
3758
|
ASSERT(isString(text), 'text must be a string');
|
|
3727
3759
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
@@ -3732,6 +3764,7 @@ function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=B
|
|
|
3732
3764
|
ASSERT(['left','center','right'].includes(textAlign), 'align must be left, center, or right');
|
|
3733
3765
|
ASSERT(isString(font), 'font must be a string');
|
|
3734
3766
|
ASSERT(isString(fontStyle), 'fontStyle must be a string');
|
|
3767
|
+
ASSERT(isNumber(angle), 'angle must be a number');
|
|
3735
3768
|
|
|
3736
3769
|
context.fillStyle = color.toString();
|
|
3737
3770
|
context.strokeStyle = lineColor.toString();
|
|
@@ -3741,14 +3774,18 @@ function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=B
|
|
|
3741
3774
|
context.textBaseline = 'middle';
|
|
3742
3775
|
|
|
3743
3776
|
const lines = (text+'').split('\n');
|
|
3744
|
-
|
|
3745
|
-
|
|
3777
|
+
const posY = pos.y - (lines.length-1) * size/2; // center vertically
|
|
3778
|
+
context.save();
|
|
3779
|
+
context.translate(pos.x, posY);
|
|
3780
|
+
context.rotate(-angle);
|
|
3781
|
+
let yOffset = 0;
|
|
3746
3782
|
lines.forEach(line=>
|
|
3747
3783
|
{
|
|
3748
|
-
lineWidth && context.strokeText(line,
|
|
3749
|
-
context.fillText(line,
|
|
3750
|
-
|
|
3784
|
+
lineWidth && context.strokeText(line, 0, yOffset, maxWidth);
|
|
3785
|
+
context.fillText(line, 0, yOffset, maxWidth);
|
|
3786
|
+
yOffset += size;
|
|
3751
3787
|
});
|
|
3788
|
+
context.restore();
|
|
3752
3789
|
}
|
|
3753
3790
|
|
|
3754
3791
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -4180,6 +4217,11 @@ let mouseDeltaScreen = vec2();
|
|
|
4180
4217
|
* @memberof Input */
|
|
4181
4218
|
let mouseWheel = 0;
|
|
4182
4219
|
|
|
4220
|
+
/** True if mouse was inside the document window, set to false when mouse leaves
|
|
4221
|
+
* @type {boolean}
|
|
4222
|
+
* @memberof Input */
|
|
4223
|
+
let mouseInWindow = true;
|
|
4224
|
+
|
|
4183
4225
|
/** Returns true if user is using gamepad (has more recently pressed a gamepad button)
|
|
4184
4226
|
* @type {boolean}
|
|
4185
4227
|
* @memberof Input */
|
|
@@ -4274,6 +4316,7 @@ function inputInit()
|
|
|
4274
4316
|
document.addEventListener('mousedown', onMouseDown);
|
|
4275
4317
|
document.addEventListener('mouseup', onMouseUp);
|
|
4276
4318
|
document.addEventListener('mousemove', onMouseMove);
|
|
4319
|
+
document.addEventListener('mouseleave', onMouseLeave);
|
|
4277
4320
|
document.addEventListener('wheel', onMouseWheel);
|
|
4278
4321
|
document.addEventListener('contextmenu', onContextMenu);
|
|
4279
4322
|
document.addEventListener('blur', onBlur);
|
|
@@ -4298,14 +4341,14 @@ function inputInit()
|
|
|
4298
4341
|
if (inputWASDEmulateDirection)
|
|
4299
4342
|
inputData[0][remapKey(e.code)] = 4;
|
|
4300
4343
|
}
|
|
4301
|
-
function remapKey(
|
|
4344
|
+
function remapKey(k)
|
|
4302
4345
|
{
|
|
4303
4346
|
// handle remapping wasd keys to directions
|
|
4304
4347
|
return inputWASDEmulateDirection ?
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4348
|
+
k === 'KeyW' ? 'ArrowUp' :
|
|
4349
|
+
k === 'KeyS' ? 'ArrowDown' :
|
|
4350
|
+
k === 'KeyA' ? 'ArrowLeft' :
|
|
4351
|
+
k === 'KeyD' ? 'ArrowRight' : k : k;
|
|
4309
4352
|
}
|
|
4310
4353
|
function onMouseDown(e)
|
|
4311
4354
|
{
|
|
@@ -4319,7 +4362,7 @@ function inputInit()
|
|
|
4319
4362
|
isUsingGamepad = false;
|
|
4320
4363
|
inputData[0][e.button] = 3;
|
|
4321
4364
|
|
|
4322
|
-
|
|
4365
|
+
const mousePosScreenLast = mousePosScreen;
|
|
4323
4366
|
mousePosScreen = mouseEventToScreen(vec2(e.x,e.y));
|
|
4324
4367
|
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
|
|
4325
4368
|
inputPreventDefault && e.button && e.preventDefault();
|
|
@@ -4332,10 +4375,17 @@ function inputInit()
|
|
|
4332
4375
|
}
|
|
4333
4376
|
function onMouseMove(e)
|
|
4334
4377
|
{
|
|
4335
|
-
|
|
4378
|
+
mouseInWindow = true;
|
|
4379
|
+
const mousePosScreenLast = mousePosScreen;
|
|
4336
4380
|
mousePosScreen = mouseEventToScreen(vec2(e.x,e.y));
|
|
4337
|
-
|
|
4381
|
+
|
|
4382
|
+
// when pointer is locked use movementX/Y for delta
|
|
4383
|
+
const movement = pointerLockIsActive() ?
|
|
4384
|
+
vec2(e.movementX, e.movementY) :
|
|
4385
|
+
mousePosScreen.subtract(mousePosScreenLast);
|
|
4386
|
+
mouseDeltaScreen = mouseDeltaScreen.add(movement);
|
|
4338
4387
|
}
|
|
4388
|
+
function onMouseLeave() { mouseInWindow = false; } // mouse moved off window
|
|
4339
4389
|
function onMouseWheel(e) { mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY); }
|
|
4340
4390
|
function onContextMenu(e) { e.preventDefault(); } // prevent right click menu
|
|
4341
4391
|
function onBlur() { inputClear(); } // reset input when focus is lost
|
|
@@ -4474,7 +4524,7 @@ let touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadStick =
|
|
|
4474
4524
|
function touchGamepadButtonCenter()
|
|
4475
4525
|
{
|
|
4476
4526
|
// draw right face buttons
|
|
4477
|
-
|
|
4527
|
+
const center = vec2(mainCanvasSize.x-touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
4478
4528
|
if (touchGamepadButtonCount <= 2)
|
|
4479
4529
|
center.x += touchGamepadSize/2;
|
|
4480
4530
|
return center;
|
|
@@ -4484,14 +4534,13 @@ function touchGamepadButtonCenter()
|
|
|
4484
4534
|
function touchInputInit()
|
|
4485
4535
|
{
|
|
4486
4536
|
// add non passive touch event listeners
|
|
4487
|
-
let handleTouch = handleTouchDefault;
|
|
4488
4537
|
document.addEventListener('touchstart', (e) => handleTouch(e), { passive: false });
|
|
4489
4538
|
document.addEventListener('touchmove', (e) => handleTouch(e), { passive: false });
|
|
4490
4539
|
document.addEventListener('touchend', (e) => handleTouch(e), { passive: false });
|
|
4491
4540
|
|
|
4492
4541
|
// handle all touch events the same way
|
|
4493
4542
|
let wasTouching;
|
|
4494
|
-
function
|
|
4543
|
+
function handleTouch(e)
|
|
4495
4544
|
{
|
|
4496
4545
|
if (!touchInputEnable)
|
|
4497
4546
|
return;
|
|
@@ -4642,7 +4691,7 @@ function touchGamepadRender()
|
|
|
4642
4691
|
const buttonSize = touchGamepadButtonCount > 1 ? touchGamepadSize/4 : touchGamepadSize/2;
|
|
4643
4692
|
for (let i=0; i<touchGamepadButtonCount; i++)
|
|
4644
4693
|
{
|
|
4645
|
-
|
|
4694
|
+
const j = mod(i-1, 4);
|
|
4646
4695
|
let button = touchGamepadButtonCount > 2 ?
|
|
4647
4696
|
j : min(j, touchGamepadButtonCount-1);
|
|
4648
4697
|
// fix button locations (swap 2 and 3 to match gamepad layout)
|
|
@@ -4744,6 +4793,10 @@ class Sound
|
|
|
4744
4793
|
{
|
|
4745
4794
|
if (!soundEnable || headlessMode) return;
|
|
4746
4795
|
|
|
4796
|
+
ASSERT(!zzfxSound || isArray(zzfxSound), 'zzfxSound is invalid');
|
|
4797
|
+
ASSERT(isNumber(range), 'range must be a number');
|
|
4798
|
+
ASSERT(isNumber(taper), 'taper must be a number');
|
|
4799
|
+
|
|
4747
4800
|
/** @property {number} - World space max range of sound */
|
|
4748
4801
|
this.range = range;
|
|
4749
4802
|
/** @property {number} - At what percentage of range should it start tapering */
|
|
@@ -4782,6 +4835,12 @@ class Sound
|
|
|
4782
4835
|
*/
|
|
4783
4836
|
play(pos, volume=1, pitch=1, randomnessScale=1, loop=false, paused=false)
|
|
4784
4837
|
{
|
|
4838
|
+
ASSERT(!pos || isVector2(pos), 'pos must be a vec2');
|
|
4839
|
+
ASSERT(isNumber(volume), 'volume must be a number');
|
|
4840
|
+
ASSERT(isNumber(pitch), 'pitch must be a number');
|
|
4841
|
+
ASSERT(isNumber(randomnessScale), 'randomnessScale must be a number');
|
|
4842
|
+
|
|
4843
|
+
|
|
4785
4844
|
if (!soundEnable || headlessMode) return;
|
|
4786
4845
|
if (!this.sampleChannels) return;
|
|
4787
4846
|
|
|
@@ -4827,6 +4886,7 @@ class Sound
|
|
|
4827
4886
|
*/
|
|
4828
4887
|
playNote(semitoneOffset=0, pos, volume)
|
|
4829
4888
|
{
|
|
4889
|
+
ASSERT(isNumber(semitoneOffset), 'semitoneOffset must be a number');
|
|
4830
4890
|
const pitch = getNoteFrequency(semitoneOffset, 1);
|
|
4831
4891
|
return this.play(pos, volume, pitch, 0);
|
|
4832
4892
|
}
|
|
@@ -4835,7 +4895,7 @@ class Sound
|
|
|
4835
4895
|
* @return {number} - How long the sound is in seconds (undefined if loading)
|
|
4836
4896
|
*/
|
|
4837
4897
|
getDuration()
|
|
4838
|
-
{ return this.sampleChannels && this.sampleChannels[0].length / this.sampleRate; }
|
|
4898
|
+
{ return this.sampleChannels && this.sampleRate ? this.sampleChannels[0].length / this.sampleRate : 0; }
|
|
4839
4899
|
|
|
4840
4900
|
/** Check if sound is loaded, for sounds fetched from a url
|
|
4841
4901
|
* @return {boolean} - True if sound is loaded and ready to play
|
|
@@ -4877,6 +4937,7 @@ class SoundWave extends Sound
|
|
|
4877
4937
|
super(undefined, range, taper);
|
|
4878
4938
|
if (!soundEnable || headlessMode) return;
|
|
4879
4939
|
ASSERT(!filename || isString(filename), 'filename must be a string');
|
|
4940
|
+
ASSERT(isNumber(randomness), 'randomness must be a number');
|
|
4880
4941
|
|
|
4881
4942
|
/** @property {SoundLoadCallback} - callback function to call when sound is loaded */
|
|
4882
4943
|
this.onloadCallback = onloadCallback;
|
|
@@ -5093,7 +5154,7 @@ class SoundInstance
|
|
|
5093
5154
|
/** Get the total duration of this sound
|
|
5094
5155
|
* @return {number} - Total duration in seconds
|
|
5095
5156
|
*/
|
|
5096
|
-
getDuration() { return this.sound.getDuration() / this.rate; }
|
|
5157
|
+
getDuration() { return this.rate ? this.sound.getDuration() / this.rate : 0; }
|
|
5097
5158
|
|
|
5098
5159
|
/** Get source of this sound instance
|
|
5099
5160
|
* @return {AudioBufferSourceNode}
|
|
@@ -5407,6 +5468,7 @@ function tileCollisionTest(pos, size=vec2(), object, solidOnly=true)
|
|
|
5407
5468
|
}
|
|
5408
5469
|
|
|
5409
5470
|
/** Return the exact position of the boudnary of first tile hit, undefined if nothing was hit.
|
|
5471
|
+
* The point will be inside the colliding tile if it hits (may have a tiny shift)
|
|
5410
5472
|
* @param {Vector2} posStart
|
|
5411
5473
|
* @param {Vector2} posEnd
|
|
5412
5474
|
* @param {EngineObject} [object] - An object or undefined for generic test
|
|
@@ -5980,6 +6042,7 @@ class TileCollisionLayer extends TileLayer
|
|
|
5980
6042
|
}
|
|
5981
6043
|
|
|
5982
6044
|
/** Return the exact position of the boudnary of first tile hit, undefined if nothing was hit.
|
|
6045
|
+
* The point will be inside the colliding tile if it hits (may have a tiny shift)
|
|
5983
6046
|
* @param {Vector2} posStart
|
|
5984
6047
|
* @param {Vector2} posEnd
|
|
5985
6048
|
* @param {EngineObject} [object] - An object or undefined for generic test
|
|
@@ -6175,9 +6238,6 @@ class ParticleEmitter extends EngineObject
|
|
|
6175
6238
|
/** Update the emitter to spawn particles, called automatically by engine once each frame */
|
|
6176
6239
|
update()
|
|
6177
6240
|
{
|
|
6178
|
-
// only do default update to apply parent transforms
|
|
6179
|
-
this.parent && super.update();
|
|
6180
|
-
|
|
6181
6241
|
if (this.velocityInheritance)
|
|
6182
6242
|
{
|
|
6183
6243
|
// pass emitter velocity to particles
|
|
@@ -6194,7 +6254,7 @@ class ParticleEmitter extends EngineObject
|
|
|
6194
6254
|
if (!this.emitTime || this.getAliveTime() <= this.emitTime)
|
|
6195
6255
|
{
|
|
6196
6256
|
// emit particles
|
|
6197
|
-
if (this.emitRate
|
|
6257
|
+
if (this.emitRate && particleEmitRateScale)
|
|
6198
6258
|
{
|
|
6199
6259
|
const rate = 1/this.emitRate/particleEmitRateScale;
|
|
6200
6260
|
for (this.emitTimeBuffer += timeDelta; this.emitTimeBuffer > 0; this.emitTimeBuffer -= rate)
|
|
@@ -6332,8 +6392,6 @@ class Particle extends EngineObject
|
|
|
6332
6392
|
/** Update the object physics, called automatically by engine once each frame */
|
|
6333
6393
|
update()
|
|
6334
6394
|
{
|
|
6335
|
-
super.update();
|
|
6336
|
-
|
|
6337
6395
|
if (this.collideTiles || this.collideSolidObjects)
|
|
6338
6396
|
{
|
|
6339
6397
|
// only apply max circular speed if particle can collide
|
|
@@ -6577,11 +6635,11 @@ class Medal
|
|
|
6577
6635
|
const descriptionSize = height*.3;
|
|
6578
6636
|
const pos = vec2(x + medalDisplayIconSize + 2*gap.x, y + gap.y*2 + nameSize/2);
|
|
6579
6637
|
const textWidth = width - medalDisplayIconSize - 3*gap.x;
|
|
6580
|
-
drawTextScreen(this.name, pos, nameSize, BLACK, 0, undefined, 'left', undefined, textWidth);
|
|
6638
|
+
drawTextScreen(this.name, pos, nameSize, BLACK, 0, undefined, 'left', undefined, undefined, textWidth);
|
|
6581
6639
|
|
|
6582
6640
|
// draw the description
|
|
6583
6641
|
pos.y = y + height - gap.y*2 - descriptionSize/2;
|
|
6584
|
-
drawTextScreen(this.description, pos, descriptionSize, BLACK, 0, undefined, 'left', undefined, textWidth);
|
|
6642
|
+
drawTextScreen(this.description, pos, descriptionSize, BLACK, 0, undefined, 'left', undefined, undefined, textWidth);
|
|
6585
6643
|
context.restore();
|
|
6586
6644
|
}
|
|
6587
6645
|
|
|
@@ -8067,12 +8125,12 @@ class UISystemPlugin
|
|
|
8067
8125
|
/** Draw a rectangle to the UI context
|
|
8068
8126
|
* @param {Vector2} pos
|
|
8069
8127
|
* @param {Vector2} size
|
|
8070
|
-
* @param {Color} [color
|
|
8071
|
-
* @param {number} [lineWidth
|
|
8072
|
-
* @param {Color} [lineColor
|
|
8073
|
-
* @param {number} [cornerRadius
|
|
8074
|
-
* @param {Color} [gradientColor
|
|
8075
|
-
drawRect(pos, size, color=
|
|
8128
|
+
* @param {Color} [color]
|
|
8129
|
+
* @param {number} [lineWidth]
|
|
8130
|
+
* @param {Color} [lineColor]
|
|
8131
|
+
* @param {number} [cornerRadius]
|
|
8132
|
+
* @param {Color} [gradientColor] */
|
|
8133
|
+
drawRect(pos, size, color=WHITE, lineWidth=0, lineColor=BLACK, cornerRadius=0, gradientColor)
|
|
8076
8134
|
{
|
|
8077
8135
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
8078
8136
|
ASSERT(isVector2(size), 'size must be a vec2');
|
|
@@ -8151,10 +8209,14 @@ class UISystemPlugin
|
|
|
8151
8209
|
* @param {string} [align]
|
|
8152
8210
|
* @param {string} [font=uiSystem.defaultFont]
|
|
8153
8211
|
* @param {string} [fontStyle]
|
|
8154
|
-
* @param {boolean} [applyMaxWidth=true]
|
|
8155
|
-
|
|
8212
|
+
* @param {boolean} [applyMaxWidth=true]
|
|
8213
|
+
* @param {Vector2} [textShadow]
|
|
8214
|
+
*/
|
|
8215
|
+
drawText(text, pos, size, color=uiSystem.defaultColor, lineWidth=uiSystem.defaultLineWidth, lineColor=uiSystem.defaultLineColor, align='center', font=uiSystem.defaultFont, fontStyle='', applyMaxWidth=true, textShadow=undefined)
|
|
8156
8216
|
{
|
|
8157
|
-
|
|
8217
|
+
if (textShadow)
|
|
8218
|
+
drawTextScreen(text, pos.add(textShadow), size.y, BLACK, lineWidth, lineColor, align, font, fontStyle, applyMaxWidth ? size.x : undefined, 0, uiSystem.uiContext);
|
|
8219
|
+
drawTextScreen(text, pos, size.y, color, lineWidth, lineColor, align, font, fontStyle, applyMaxWidth ? size.x : undefined, 0, uiSystem.uiContext);
|
|
8158
8220
|
}
|
|
8159
8221
|
|
|
8160
8222
|
/**
|
|
@@ -8181,6 +8243,25 @@ class UISystemPlugin
|
|
|
8181
8243
|
setCallback(onDragLeave, 'dragleave');
|
|
8182
8244
|
setCallback(onDragOver, 'dragover');
|
|
8183
8245
|
}
|
|
8246
|
+
|
|
8247
|
+
/** Convert a screen space position to native UI position
|
|
8248
|
+
* @param {Vector2} pos
|
|
8249
|
+
* @return {Vector2}
|
|
8250
|
+
*/
|
|
8251
|
+
screenToNative(pos)
|
|
8252
|
+
{
|
|
8253
|
+
if (!uiSystem.nativeHeight)
|
|
8254
|
+
return pos;
|
|
8255
|
+
|
|
8256
|
+
const s = mainCanvasSize.y / uiSystem.nativeHeight;
|
|
8257
|
+
const sInv = 1/s;
|
|
8258
|
+
const p = pos.copy();
|
|
8259
|
+
p.x += s*mainCanvasSize.x/2;
|
|
8260
|
+
p.x *= sInv;
|
|
8261
|
+
p.y *= sInv;
|
|
8262
|
+
p.x -= sInv*mainCanvasSize.x/2;
|
|
8263
|
+
return p;
|
|
8264
|
+
}
|
|
8184
8265
|
}
|
|
8185
8266
|
|
|
8186
8267
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -8257,6 +8338,9 @@ class UIObject
|
|
|
8257
8338
|
/** @property {boolean} - True if this can be a hover object */
|
|
8258
8339
|
this.canBeHover = true;
|
|
8259
8340
|
uiSystem.uiObjects.push(this);
|
|
8341
|
+
|
|
8342
|
+
/** @property {Vector2} - How much to offset the text shadow or undefined */
|
|
8343
|
+
this.textShadow = undefined;
|
|
8260
8344
|
}
|
|
8261
8345
|
|
|
8262
8346
|
/** Add a child UIObject to this object
|
|
@@ -8284,24 +8368,20 @@ class UIObject
|
|
|
8284
8368
|
*/
|
|
8285
8369
|
isMouseOverlapping()
|
|
8286
8370
|
{
|
|
8371
|
+
if (!mouseInWindow) return false;
|
|
8372
|
+
|
|
8287
8373
|
const size = !isTouchDevice ? this.size :
|
|
8288
8374
|
this.size.add(vec2(this.extraTouchSize || 0));
|
|
8289
|
-
|
|
8290
|
-
return isOverlapping(this.pos, size, mousePosScreen);
|
|
8291
|
-
|
|
8292
|
-
const s = mainCanvasSize.y / uiSystem.nativeHeight;
|
|
8293
|
-
const sInv = 1/s;
|
|
8294
|
-
let pos = mousePosScreen.copy();
|
|
8295
|
-
pos.x += s*mainCanvasSize.x/2;
|
|
8296
|
-
pos.x *= sInv;
|
|
8297
|
-
pos.y *= sInv;
|
|
8298
|
-
pos.x -= sInv*mainCanvasSize.x/2;
|
|
8375
|
+
const pos = uiSystem.screenToNative(mousePosScreen);
|
|
8299
8376
|
return isOverlapping(this.pos, size, pos);
|
|
8300
8377
|
}
|
|
8301
8378
|
|
|
8302
8379
|
/** Update the object, called automatically by plugin once each frame */
|
|
8303
8380
|
update()
|
|
8304
8381
|
{
|
|
8382
|
+
// call the custom update callback
|
|
8383
|
+
this.onUpdate();
|
|
8384
|
+
|
|
8305
8385
|
const wasHover = uiSystem.lastHoverObject === this;
|
|
8306
8386
|
const isActive = this.isActiveObject();
|
|
8307
8387
|
const mouseDown = mouseIsDown(0);
|
|
@@ -8358,7 +8438,7 @@ class UIObject
|
|
|
8358
8438
|
|
|
8359
8439
|
const lineColor = this.interactive && this.isActiveObject() && !this.disabled ? this.color : this.lineColor;
|
|
8360
8440
|
const color = this.disabled ? this.disabledColor : this.interactive ? this.isActiveObject() ? this.activeColor || this.color : this.isHoverObject() ? this.hoverColor : this.color : this.color;
|
|
8361
|
-
uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor, this.cornerRadius);
|
|
8441
|
+
uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor, this.cornerRadius, this.gradientColor);
|
|
8362
8442
|
}
|
|
8363
8443
|
|
|
8364
8444
|
/** Special update when object is not visible */
|
|
@@ -8385,6 +8465,9 @@ class UIObject
|
|
|
8385
8465
|
/** @return {boolean} - Is the mouse held onto this element */
|
|
8386
8466
|
isActiveObject() { return uiSystem.activeObject === this; }
|
|
8387
8467
|
|
|
8468
|
+
/** Called each frame when object updates */
|
|
8469
|
+
onUpdate() {}
|
|
8470
|
+
|
|
8388
8471
|
/** Called when the mouse enters the object */
|
|
8389
8472
|
onEnter() {}
|
|
8390
8473
|
|
|
@@ -8439,8 +8522,9 @@ class UIText extends UIObject
|
|
|
8439
8522
|
}
|
|
8440
8523
|
render()
|
|
8441
8524
|
{
|
|
8525
|
+
// only render the text
|
|
8442
8526
|
const textSize = this.getTextSize();
|
|
8443
|
-
uiSystem.drawText(this.text, this.pos, textSize, this.textColor, this.lineWidth, this.lineColor, this.align, this.font, this.fontStyle);
|
|
8527
|
+
uiSystem.drawText(this.text, this.pos, textSize, this.textColor, this.lineWidth, this.lineColor, this.align, this.font, this.fontStyle, true, this.textShadow);
|
|
8444
8528
|
}
|
|
8445
8529
|
}
|
|
8446
8530
|
|
|
@@ -8516,7 +8600,7 @@ class UIButton extends UIObject
|
|
|
8516
8600
|
// draw the text scaled to fit
|
|
8517
8601
|
const textSize = this.getTextSize();
|
|
8518
8602
|
uiSystem.drawText(this.text, this.pos, textSize,
|
|
8519
|
-
this.textColor, 0, undefined, this.align, this.font, this.fontStyle);
|
|
8603
|
+
this.textColor, 0, undefined, this.align, this.font, this.fontStyle, true, this.textShadow);
|
|
8520
8604
|
}
|
|
8521
8605
|
}
|
|
8522
8606
|
|
|
@@ -8570,7 +8654,7 @@ class UICheckbox extends UIObject
|
|
|
8570
8654
|
const textSize = this.getTextSize();
|
|
8571
8655
|
const pos = this.pos.add(vec2(this.size.x,0));
|
|
8572
8656
|
uiSystem.drawText(this.text, pos, textSize,
|
|
8573
|
-
this.textColor, 0, undefined, 'left', this.font, this.fontStyle, false);
|
|
8657
|
+
this.textColor, 0, undefined, 'left', this.font, this.fontStyle, false, this.textShadow);
|
|
8574
8658
|
}
|
|
8575
8659
|
}
|
|
8576
8660
|
|
|
@@ -8625,9 +8709,11 @@ class UIScrollbar extends UIObject
|
|
|
8625
8709
|
const p1 = centerPos - handleWidth/2;
|
|
8626
8710
|
const p2 = centerPos + handleWidth/2;
|
|
8627
8711
|
const oldValue = this.value;
|
|
8712
|
+
|
|
8713
|
+
const p = uiSystem.screenToNative(mousePosScreen);
|
|
8628
8714
|
this.value = isHorizontal ?
|
|
8629
|
-
percent(
|
|
8630
|
-
percent(
|
|
8715
|
+
percent(p.x, p1, p2) :
|
|
8716
|
+
percent(p.y, p2, p1);
|
|
8631
8717
|
this.value === oldValue || this.onChange();
|
|
8632
8718
|
}
|
|
8633
8719
|
}
|
|
@@ -8649,20 +8735,20 @@ class UIScrollbar extends UIObject
|
|
|
8649
8735
|
vec2(lerp(p1, p2, this.value), this.pos.y) :
|
|
8650
8736
|
vec2(this.pos.x, lerp(p2, p1, this.value))
|
|
8651
8737
|
const handleColor = this.disabled ? this.disabledColor : this.handleColor;
|
|
8652
|
-
uiSystem.drawRect(handlePos, vec2(handleSize), handleColor, this.lineWidth, this.lineColor, this.cornerRadius);
|
|
8738
|
+
uiSystem.drawRect(handlePos, vec2(handleSize), handleColor, this.lineWidth, this.lineColor, this.cornerRadius, this.gradientColor);
|
|
8653
8739
|
|
|
8654
8740
|
// draw the text scaled to fit on the scrollbar
|
|
8655
8741
|
const textSize = this.getTextSize();
|
|
8656
8742
|
uiSystem.drawText(this.text, this.pos, textSize,
|
|
8657
|
-
this.textColor, 0, undefined, this.align, this.font, this.fontStyle);
|
|
8743
|
+
this.textColor, 0, undefined, this.align, this.font, this.fontStyle, true, this.textShadow);
|
|
8658
8744
|
}
|
|
8659
8745
|
}
|
|
8660
8746
|
/**
|
|
8661
8747
|
* LittleJS Box2D Physics Plugin
|
|
8662
8748
|
* - Box2dObject extends EngineObject with Box2D physics
|
|
8663
|
-
* - Call box2dInit()
|
|
8749
|
+
* - Call box2dInit() to enable
|
|
8664
8750
|
* - You will also need to include box2d.wasm.js
|
|
8665
|
-
* - Uses a super fast web assembly port of Box2D
|
|
8751
|
+
* - Uses a super fast web assembly port of Box2D v2.3.1
|
|
8666
8752
|
* - More info: https://github.com/kripken/box2d.js
|
|
8667
8753
|
* - Functions to create polygon, circle, and edge shapes
|
|
8668
8754
|
* - Contact begin and end callbacks
|
|
@@ -8692,9 +8778,9 @@ function box2dSetDebug(enable) { box2dDebug = enable; }
|
|
|
8692
8778
|
///////////////////////////////////////////////////////////////////////////////
|
|
8693
8779
|
/**
|
|
8694
8780
|
* Box2D Object - extend with your own custom physics objects
|
|
8695
|
-
* - A LittleJS object with Box2D physics
|
|
8696
|
-
* - Each object has a Box2D body which can have multiple fixtures and joints
|
|
8781
|
+
* - A LittleJS object with Box2D physics, dynamic by default
|
|
8697
8782
|
* - Provides interface for Box2D body and fixture functions
|
|
8783
|
+
* - Each object can have multiple fixtures and joints
|
|
8698
8784
|
* @extends EngineObject
|
|
8699
8785
|
* @memberof Box2D
|
|
8700
8786
|
*/
|
|
@@ -8708,7 +8794,7 @@ class Box2dObject extends EngineObject
|
|
|
8708
8794
|
* @param {Color} [color]
|
|
8709
8795
|
* @param {number} [bodyType]
|
|
8710
8796
|
* @param {number} [renderOrder] */
|
|
8711
|
-
constructor(pos, size, tileInfo, angle=0, color, bodyType=box2d.bodyTypeDynamic, renderOrder=0)
|
|
8797
|
+
constructor(pos=vec2(), size=vec2(), tileInfo, angle=0, color, bodyType=box2d.bodyTypeDynamic, renderOrder=0)
|
|
8712
8798
|
{
|
|
8713
8799
|
super(pos, size, tileInfo, angle, color, renderOrder);
|
|
8714
8800
|
|
|
@@ -8720,24 +8806,27 @@ class Box2dObject extends EngineObject
|
|
|
8720
8806
|
this.body = box2d.world.CreateBody(bodyDef);
|
|
8721
8807
|
this.body.object = this;
|
|
8722
8808
|
this.lineColor = BLACK;
|
|
8809
|
+
box2d.objects.push(this);
|
|
8810
|
+
|
|
8811
|
+
// edge lists and loops for drawing
|
|
8812
|
+
this.edgeLists = [];
|
|
8813
|
+
this.edgeLoops = [];
|
|
8723
8814
|
}
|
|
8724
8815
|
|
|
8725
8816
|
/** Destroy this object and its physics body */
|
|
8726
8817
|
destroy()
|
|
8727
8818
|
{
|
|
8819
|
+
if (this.destroyed)
|
|
8820
|
+
return;
|
|
8821
|
+
|
|
8728
8822
|
// destroy physics body, fixtures, and joints
|
|
8729
|
-
this.body
|
|
8730
|
-
this.body
|
|
8823
|
+
ASSERT(this.body, 'Box2dObject has no body to destroy');
|
|
8824
|
+
box2d.world.DestroyBody(this.body);
|
|
8731
8825
|
super.destroy();
|
|
8732
8826
|
}
|
|
8733
8827
|
|
|
8734
|
-
/**
|
|
8735
|
-
|
|
8736
|
-
{
|
|
8737
|
-
// use box2d physics update instead of normal engine update
|
|
8738
|
-
this.pos = box2d.vec2From(this.body.GetPosition());
|
|
8739
|
-
this.angle = -this.body.GetAngle();
|
|
8740
|
-
}
|
|
8828
|
+
/** Box2d objects updated with Box2d world step */
|
|
8829
|
+
updatePhysics() {}
|
|
8741
8830
|
|
|
8742
8831
|
/** Render the object, uses box2d drawing if no tile info exists */
|
|
8743
8832
|
render()
|
|
@@ -8763,10 +8852,23 @@ class Box2dObject extends EngineObject
|
|
|
8763
8852
|
* @param {Color} [lineColor]
|
|
8764
8853
|
* @param {number} [lineWidth]
|
|
8765
8854
|
* @param {CanvasRenderingContext2D} [context] */
|
|
8766
|
-
drawFixtures(color=WHITE, lineColor, lineWidth=.1, context)
|
|
8855
|
+
drawFixtures(color=WHITE, lineColor=BLACK, lineWidth=.1, context)
|
|
8767
8856
|
{
|
|
8768
|
-
|
|
8769
|
-
|
|
8857
|
+
// draw non-edge fixtures
|
|
8858
|
+
this.getFixtureList().forEach((fixture)=>
|
|
8859
|
+
{
|
|
8860
|
+
const shape = box2d.castObjectType(fixture.GetShape());
|
|
8861
|
+
if (shape.GetType() !== box2d.instance.b2Shape.e_edge)
|
|
8862
|
+
{
|
|
8863
|
+
box2d.drawFixture(fixture, this.pos, this.angle, color, lineColor, lineWidth, context);
|
|
8864
|
+
}
|
|
8865
|
+
});
|
|
8866
|
+
|
|
8867
|
+
// draw edges using a single draw line for better connections
|
|
8868
|
+
this.edgeLists.forEach(points=>
|
|
8869
|
+
drawLineList(points, lineWidth, lineColor, false, this.pos, this.angle));
|
|
8870
|
+
this.edgeLoops.forEach(points=>
|
|
8871
|
+
drawLineList(points, lineWidth, lineColor, true, this.pos, this.angle));
|
|
8770
8872
|
}
|
|
8771
8873
|
|
|
8772
8874
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -8791,6 +8893,10 @@ class Box2dObject extends EngineObject
|
|
|
8791
8893
|
* @param {boolean} [isSensor] */
|
|
8792
8894
|
addShape(shape, density=1, friction=.2, restitution=0, isSensor=false)
|
|
8793
8895
|
{
|
|
8896
|
+
ASSERT(isNumber(density), 'density must be a number');
|
|
8897
|
+
ASSERT(isNumber(friction), 'friction must be a number');
|
|
8898
|
+
ASSERT(isNumber(restitution), 'restitution must be a number');
|
|
8899
|
+
|
|
8794
8900
|
const fd = new box2d.instance.b2FixtureDef();
|
|
8795
8901
|
fd.set_shape(shape);
|
|
8796
8902
|
fd.set_density(density);
|
|
@@ -8810,6 +8916,11 @@ class Box2dObject extends EngineObject
|
|
|
8810
8916
|
* @param {boolean} [isSensor] */
|
|
8811
8917
|
addBox(size=vec2(1), offset=vec2(), angle=0, density, friction, restitution, isSensor)
|
|
8812
8918
|
{
|
|
8919
|
+
ASSERT(isVector2(size), 'size must be a Vector2');
|
|
8920
|
+
ASSERT(size.x > 0 && size.y > 0, 'size must be positive');
|
|
8921
|
+
ASSERT(isVector2(offset), 'offset must be a Vector2');
|
|
8922
|
+
ASSERT(isNumber(angle), 'angle must be a number');
|
|
8923
|
+
|
|
8813
8924
|
const shape = new box2d.instance.b2PolygonShape();
|
|
8814
8925
|
shape.SetAsBox(size.x/2, size.y/2, box2d.vec2dTo(offset), angle);
|
|
8815
8926
|
return this.addShape(shape, density, friction, restitution, isSensor);
|
|
@@ -8823,6 +8934,8 @@ class Box2dObject extends EngineObject
|
|
|
8823
8934
|
* @param {boolean} [isSensor] */
|
|
8824
8935
|
addPoly(points, density, friction, restitution, isSensor)
|
|
8825
8936
|
{
|
|
8937
|
+
ASSERT(isArray(points), 'points must be an array');
|
|
8938
|
+
|
|
8826
8939
|
function box2dCreatePolygonShape(points)
|
|
8827
8940
|
{
|
|
8828
8941
|
function box2dCreatePointList(points)
|
|
@@ -8858,6 +8971,9 @@ class Box2dObject extends EngineObject
|
|
|
8858
8971
|
* @param {boolean} [isSensor] */
|
|
8859
8972
|
addRegularPoly(diameter=1, sides=8, density, friction, restitution, isSensor)
|
|
8860
8973
|
{
|
|
8974
|
+
ASSERT(isNumber(diameter) && diameter>0, 'diameter must be a positive number');
|
|
8975
|
+
ASSERT(isNumber(sides) && sides>2, 'sides must be a positive number greater than 2');
|
|
8976
|
+
|
|
8861
8977
|
const points = [];
|
|
8862
8978
|
const radius = diameter/2;
|
|
8863
8979
|
for (let i=sides; i--;)
|
|
@@ -8873,6 +8989,8 @@ class Box2dObject extends EngineObject
|
|
|
8873
8989
|
* @param {boolean} [isSensor] */
|
|
8874
8990
|
addRandomPoly(diameter=1, density, friction, restitution, isSensor)
|
|
8875
8991
|
{
|
|
8992
|
+
ASSERT(isNumber(diameter) && diameter>0, 'diameter must be a positive number');
|
|
8993
|
+
|
|
8876
8994
|
const sides = randInt(3, 9);
|
|
8877
8995
|
const points = [];
|
|
8878
8996
|
const radius = diameter/2;
|
|
@@ -8890,6 +9008,9 @@ class Box2dObject extends EngineObject
|
|
|
8890
9008
|
* @param {boolean} [isSensor] */
|
|
8891
9009
|
addCircle(diameter=1, offset=vec2(), density, friction, restitution, isSensor)
|
|
8892
9010
|
{
|
|
9011
|
+
ASSERT(isNumber(diameter) && diameter>0, 'diameter must be a positive number');
|
|
9012
|
+
ASSERT(isVector2(offset), 'offset must be a Vector2');
|
|
9013
|
+
|
|
8893
9014
|
const shape = new box2d.instance.b2CircleShape();
|
|
8894
9015
|
shape.set_m_p(box2d.vec2dTo(offset));
|
|
8895
9016
|
shape.set_m_radius(diameter/2);
|
|
@@ -8905,53 +9026,63 @@ class Box2dObject extends EngineObject
|
|
|
8905
9026
|
* @param {boolean} [isSensor] */
|
|
8906
9027
|
addEdge(point1, point2, density, friction, restitution, isSensor)
|
|
8907
9028
|
{
|
|
9029
|
+
ASSERT(isVector2(point1), 'point1 must be a Vector2');
|
|
9030
|
+
ASSERT(isVector2(point2), 'point2 must be a Vector2');
|
|
9031
|
+
|
|
8908
9032
|
const shape = new box2d.instance.b2EdgeShape();
|
|
8909
9033
|
shape.Set(box2d.vec2dTo(point1), box2d.vec2dTo(point2));
|
|
8910
9034
|
return this.addShape(shape, density, friction, restitution, isSensor);
|
|
8911
9035
|
}
|
|
8912
9036
|
|
|
8913
|
-
/** Add an edge
|
|
9037
|
+
/** Add an edge list to the body
|
|
8914
9038
|
* @param {Array<Vector2>} points
|
|
8915
9039
|
* @param {number} [density]
|
|
8916
9040
|
* @param {number} [friction]
|
|
8917
9041
|
* @param {number} [restitution]
|
|
8918
9042
|
* @param {boolean} [isSensor] */
|
|
8919
|
-
|
|
9043
|
+
addEdgeList(points, density, friction, restitution, isSensor)
|
|
8920
9044
|
{
|
|
8921
|
-
|
|
8922
|
-
const
|
|
8923
|
-
for (let i=0; i<points.length; ++i)
|
|
9045
|
+
ASSERT(isArray(points), 'points must be an array');
|
|
9046
|
+
const fixtures = [], edgePoints = [];
|
|
9047
|
+
for (let i=0; i<points.length-1; ++i)
|
|
8924
9048
|
{
|
|
8925
9049
|
const shape = new box2d.instance.b2EdgeShape();
|
|
8926
|
-
shape.set_m_vertex0(box2d.vec2dTo(
|
|
8927
|
-
shape.set_m_vertex1(box2d.vec2dTo(
|
|
8928
|
-
shape.set_m_vertex2(box2d.vec2dTo(
|
|
8929
|
-
shape.set_m_vertex3(box2d.vec2dTo(
|
|
9050
|
+
points[i-1] && shape.set_m_vertex0(box2d.vec2dTo(points[i-1]));
|
|
9051
|
+
points[i+0] && shape.set_m_vertex1(box2d.vec2dTo(points[i+0]));
|
|
9052
|
+
points[i+1] && shape.set_m_vertex2(box2d.vec2dTo(points[i+1]));
|
|
9053
|
+
points[i+2] && shape.set_m_vertex3(box2d.vec2dTo(points[i+2]));
|
|
8930
9054
|
const f = this.addShape(shape, density, friction, restitution, isSensor);
|
|
8931
9055
|
fixtures.push(f);
|
|
9056
|
+
edgePoints.push(points[i].copy());
|
|
8932
9057
|
}
|
|
9058
|
+
edgePoints.push(points[points.length-1].copy());
|
|
9059
|
+
this.edgeLists.push(edgePoints);
|
|
8933
9060
|
return fixtures;
|
|
8934
9061
|
}
|
|
8935
9062
|
|
|
8936
|
-
/** Add an edge
|
|
9063
|
+
/** Add an edge loop to the body, an edge loop connects the end points
|
|
8937
9064
|
* @param {Array<Vector2>} points
|
|
8938
9065
|
* @param {number} [density]
|
|
8939
9066
|
* @param {number} [friction]
|
|
8940
9067
|
* @param {number} [restitution]
|
|
8941
9068
|
* @param {boolean} [isSensor] */
|
|
8942
|
-
|
|
9069
|
+
addEdgeLoop(points, density, friction, restitution, isSensor)
|
|
8943
9070
|
{
|
|
8944
|
-
|
|
8945
|
-
|
|
9071
|
+
ASSERT(isArray(points), 'points must be an array');
|
|
9072
|
+
const fixtures = [], edgePoints = [];
|
|
9073
|
+
const getPoint = i=> points[mod(i,points.length)];
|
|
9074
|
+
for (let i=0; i<points.length; ++i)
|
|
8946
9075
|
{
|
|
8947
9076
|
const shape = new box2d.instance.b2EdgeShape();
|
|
8948
|
-
|
|
8949
|
-
|
|
8950
|
-
|
|
8951
|
-
|
|
9077
|
+
shape.set_m_vertex0(box2d.vec2dTo(getPoint(i-1)));
|
|
9078
|
+
shape.set_m_vertex1(box2d.vec2dTo(getPoint(i+0)));
|
|
9079
|
+
shape.set_m_vertex2(box2d.vec2dTo(getPoint(i+1)));
|
|
9080
|
+
shape.set_m_vertex3(box2d.vec2dTo(getPoint(i+2)));
|
|
8952
9081
|
const f = this.addShape(shape, density, friction, restitution, isSensor);
|
|
8953
9082
|
fixtures.push(f);
|
|
9083
|
+
i < points.length && edgePoints.push(points[i].copy());
|
|
8954
9084
|
}
|
|
9085
|
+
this.edgeLoops.push(edgePoints);
|
|
8955
9086
|
return fixtures;
|
|
8956
9087
|
}
|
|
8957
9088
|
|
|
@@ -8986,6 +9117,10 @@ class Box2dObject extends EngineObject
|
|
|
8986
9117
|
* @return {number} */
|
|
8987
9118
|
getBodyType() { return this.body.GetType(); }
|
|
8988
9119
|
|
|
9120
|
+
/** Get the speed of this object
|
|
9121
|
+
* @return {number} */
|
|
9122
|
+
getSpeed() { return this.getLinearVelocity().length(); }
|
|
9123
|
+
|
|
8989
9124
|
///////////////////////////////////////////////////////////////////////////////
|
|
8990
9125
|
// physics set functions
|
|
8991
9126
|
|
|
@@ -9001,33 +9136,40 @@ class Box2dObject extends EngineObject
|
|
|
9001
9136
|
|
|
9002
9137
|
/** Sets the position
|
|
9003
9138
|
* @param {Vector2} pos */
|
|
9004
|
-
setPosition(pos)
|
|
9139
|
+
setPosition(pos)
|
|
9140
|
+
{ this.setTransform(pos, this.body.GetAngle()); }
|
|
9005
9141
|
|
|
9006
9142
|
/** Sets the angle
|
|
9007
9143
|
* @param {number} angle */
|
|
9008
|
-
setAngle(angle)
|
|
9144
|
+
setAngle(angle)
|
|
9145
|
+
{ this.setTransform(box2d.vec2From(this.body.GetPosition()), -angle); }
|
|
9009
9146
|
|
|
9010
9147
|
/** Sets the linear velocity
|
|
9011
9148
|
* @param {Vector2} velocity */
|
|
9012
|
-
setLinearVelocity(velocity)
|
|
9149
|
+
setLinearVelocity(velocity)
|
|
9150
|
+
{ this.body.SetLinearVelocity(box2d.vec2dTo(velocity)); }
|
|
9013
9151
|
|
|
9014
9152
|
/** Sets the angular velocity
|
|
9015
9153
|
* @param {number} angularVelocity */
|
|
9016
|
-
setAngularVelocity(angularVelocity)
|
|
9154
|
+
setAngularVelocity(angularVelocity)
|
|
9155
|
+
{ this.body.SetAngularVelocity(angularVelocity); }
|
|
9017
9156
|
|
|
9018
9157
|
/** Sets the linear damping
|
|
9019
9158
|
* @param {number} damping */
|
|
9020
|
-
setLinearDamping(damping)
|
|
9159
|
+
setLinearDamping(damping)
|
|
9160
|
+
{ this.body.SetLinearDamping(damping); }
|
|
9021
9161
|
|
|
9022
9162
|
/** Sets the angular damping
|
|
9023
9163
|
* @param {number} damping */
|
|
9024
|
-
setAngularDamping(damping)
|
|
9164
|
+
setAngularDamping(damping)
|
|
9165
|
+
{ this.body.SetAngularDamping(damping); }
|
|
9025
9166
|
|
|
9026
9167
|
/** Sets the gravity scale
|
|
9027
9168
|
* @param {number} [scale] */
|
|
9028
|
-
setGravityScale(scale=1)
|
|
9169
|
+
setGravityScale(scale=1)
|
|
9170
|
+
{ this.body.SetGravityScale(this.gravityScale = scale); }
|
|
9029
9171
|
|
|
9030
|
-
/** Should
|
|
9172
|
+
/** Should be like a bullet for continuous collision detection?
|
|
9031
9173
|
* @param {boolean} [isBullet] */
|
|
9032
9174
|
setBullet(isBullet=true) { this.body.SetBullet(isBullet); }
|
|
9033
9175
|
|
|
@@ -9041,11 +9183,13 @@ class Box2dObject extends EngineObject
|
|
|
9041
9183
|
|
|
9042
9184
|
/** Set whether the body is allowed to sleep
|
|
9043
9185
|
* @param {boolean} [isAllowed] */
|
|
9044
|
-
setSleepingAllowed(isAllowed=true)
|
|
9186
|
+
setSleepingAllowed(isAllowed=true)
|
|
9187
|
+
{ this.body.SetSleepingAllowed(isAllowed); }
|
|
9045
9188
|
|
|
9046
9189
|
/** Set whether the body can rotate
|
|
9047
9190
|
* @param {boolean} [isFixed] */
|
|
9048
|
-
setFixedRotation(isFixed=true)
|
|
9191
|
+
setFixedRotation(isFixed=true)
|
|
9192
|
+
{ this.body.SetFixedRotation(isFixed); }
|
|
9049
9193
|
|
|
9050
9194
|
/** Set the center of mass of the body
|
|
9051
9195
|
* @param {Vector2} center */
|
|
@@ -9057,10 +9201,11 @@ class Box2dObject extends EngineObject
|
|
|
9057
9201
|
|
|
9058
9202
|
/** Set the moment of inertia of the body
|
|
9059
9203
|
* @param {number} momentOfInertia */
|
|
9060
|
-
setMomentOfInertia(momentOfInertia)
|
|
9204
|
+
setMomentOfInertia(momentOfInertia)
|
|
9205
|
+
{ this.setMassData(undefined, undefined, momentOfInertia) }
|
|
9061
9206
|
|
|
9062
9207
|
/** Reset the mass, center of mass, and moment */
|
|
9063
|
-
resetMassData()
|
|
9208
|
+
resetMassData() { this.body.ResetMassData(); }
|
|
9064
9209
|
|
|
9065
9210
|
/** Set the mass data of the body
|
|
9066
9211
|
* @param {Vector2} [localCenter]
|
|
@@ -9702,6 +9847,50 @@ class Box2dPrismaticJoint extends Box2dJoint
|
|
|
9702
9847
|
getMotorForce(time) { return this.box2dJoint.GetMotorForce(1/time); }
|
|
9703
9848
|
}
|
|
9704
9849
|
|
|
9850
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
9851
|
+
/**
|
|
9852
|
+
* Box2D Static Object - Box2d with a static physics body
|
|
9853
|
+
* @extends Box2dObject
|
|
9854
|
+
* @memberof Box2D
|
|
9855
|
+
*/
|
|
9856
|
+
class Box2dStaticObject extends Box2dObject
|
|
9857
|
+
{
|
|
9858
|
+
/** Create a LittleJS object with Box2d physics
|
|
9859
|
+
* @param {Vector2} [pos]
|
|
9860
|
+
* @param {Vector2} [size]
|
|
9861
|
+
* @param {TileInfo} [tileInfo]
|
|
9862
|
+
* @param {number} [angle]
|
|
9863
|
+
* @param {Color} [color]
|
|
9864
|
+
* @param {number} [renderOrder] */
|
|
9865
|
+
constructor(pos, size, tileInfo, angle=0, color, renderOrder=0)
|
|
9866
|
+
{
|
|
9867
|
+
const bodyType = box2d.bodyTypeStatic;
|
|
9868
|
+
super(pos, size, tileInfo, angle, color, bodyType, renderOrder);
|
|
9869
|
+
}
|
|
9870
|
+
}
|
|
9871
|
+
|
|
9872
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
9873
|
+
/**
|
|
9874
|
+
* Box2D Kiematic Object - Box2d with a kinematic physics body
|
|
9875
|
+
* @extends Box2dObject
|
|
9876
|
+
* @memberof Box2D
|
|
9877
|
+
*/
|
|
9878
|
+
class Box2dKiematicObject extends Box2dObject
|
|
9879
|
+
{
|
|
9880
|
+
/** Create a LittleJS object with Box2d physics
|
|
9881
|
+
* @param {Vector2} [pos]
|
|
9882
|
+
* @param {Vector2} [size]
|
|
9883
|
+
* @param {TileInfo} [tileInfo]
|
|
9884
|
+
* @param {number} [angle]
|
|
9885
|
+
* @param {Color} [color]
|
|
9886
|
+
* @param {number} [renderOrder] */
|
|
9887
|
+
constructor(pos, size, tileInfo, angle=0, color, renderOrder=0)
|
|
9888
|
+
{
|
|
9889
|
+
const bodyType = box2d.bodyTypeKinematic;
|
|
9890
|
+
super(pos, size, tileInfo, angle, color, bodyType, renderOrder);
|
|
9891
|
+
}
|
|
9892
|
+
}
|
|
9893
|
+
|
|
9705
9894
|
///////////////////////////////////////////////////////////////////////////////
|
|
9706
9895
|
/**
|
|
9707
9896
|
* Box2D Wheel Joint
|
|
@@ -10064,6 +10253,7 @@ class Box2dPlugin
|
|
|
10064
10253
|
box2d = this;
|
|
10065
10254
|
this.instance = instance;
|
|
10066
10255
|
this.world = new box2d.instance.b2World();
|
|
10256
|
+
this.objects = [];
|
|
10067
10257
|
|
|
10068
10258
|
/** @property {number} - Velocity iterations per update*/
|
|
10069
10259
|
this.velocityIterations = 8;
|
|
@@ -10306,9 +10496,10 @@ class Box2dPlugin
|
|
|
10306
10496
|
|
|
10307
10497
|
/** converts a box2d vec2 pointer to a Vector2
|
|
10308
10498
|
* @param {Object} v */
|
|
10309
|
-
vec2FromPointer(
|
|
10499
|
+
vec2FromPointer(vp)
|
|
10310
10500
|
{
|
|
10311
|
-
|
|
10501
|
+
const v = box2d.instance.wrapPointer(vp, box2d.instance.b2Vec2);
|
|
10502
|
+
return box2d.vec2From(v);
|
|
10312
10503
|
}
|
|
10313
10504
|
|
|
10314
10505
|
/** converts a Vector2 to a box2 vec2
|
|
@@ -10382,8 +10573,23 @@ async function box2dInit()
|
|
|
10382
10573
|
// add the box2d plugin to the engine
|
|
10383
10574
|
function box2dUpdate()
|
|
10384
10575
|
{
|
|
10385
|
-
if (
|
|
10386
|
-
|
|
10576
|
+
if (paused)
|
|
10577
|
+
return;
|
|
10578
|
+
|
|
10579
|
+
box2d.step();
|
|
10580
|
+
|
|
10581
|
+
// remove destroyed objects
|
|
10582
|
+
box2d.objects = box2d.objects.filter(o=>!o.destroyed);
|
|
10583
|
+
|
|
10584
|
+
// copy box2d physics results to engine objects
|
|
10585
|
+
for (const o of box2d.objects)
|
|
10586
|
+
{
|
|
10587
|
+
if (o.body)
|
|
10588
|
+
{
|
|
10589
|
+
o.pos = box2d.vec2From(o.body.GetPosition());
|
|
10590
|
+
o.angle = -o.body.GetAngle();
|
|
10591
|
+
}
|
|
10592
|
+
}
|
|
10387
10593
|
}
|
|
10388
10594
|
function box2dRender()
|
|
10389
10595
|
{
|