littlejsengine 1.18.0 → 1.18.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/.claude/settings.local.json +7 -0
- package/.github/workflows/test.yml +17 -0
- package/AI.md +10 -0
- package/CLAUDE.md +1 -0
- package/README.md +15 -6
- package/dist/littlejs.d.ts +47 -7
- package/dist/littlejs.esm.js +97 -30
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +91 -28
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +89 -28
- package/examples/box2d/game.js +2 -2
- package/examples/box2d/gameObjects.js +74 -0
- package/examples/box2d/scenes.js +7 -0
- package/examples/shorts/base.html +1 -1
- package/examples/starter/index.html +2 -2
- package/examples/stress/index.html +1 -1
- package/package.json +3 -2
- package/src/engine.js +23 -6
- package/src/engineBuild.mjs +1 -1
- package/src/engineDebug.js +2 -0
- package/src/engineDraw.js +1 -0
- package/src/engineExport.js +6 -2
- package/src/engineInput.js +34 -10
- package/src/engineMath.js +1 -1
- package/src/engineObject.js +4 -3
- package/src/engineSettings.js +26 -8
- package/test/math.test.mjs +774 -0
- package/test/setup.mjs +22 -0
- package/test/smoke.test.mjs +258 -0
- package/test/util.test.mjs +80 -0
package/dist/littlejs.release.js
CHANGED
|
@@ -35,7 +35,7 @@ const engineName = 'LittleJS';
|
|
|
35
35
|
* @type {string}
|
|
36
36
|
* @default
|
|
37
37
|
* @memberof Engine */
|
|
38
|
-
const engineVersion = '1.18.
|
|
38
|
+
const engineVersion = '1.18.2';
|
|
39
39
|
|
|
40
40
|
/** Frames per second to update
|
|
41
41
|
* @type {number}
|
|
@@ -318,12 +318,17 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
318
318
|
}
|
|
319
319
|
else
|
|
320
320
|
{
|
|
321
|
+
// apply device pixel ratio for crisp rendering
|
|
322
|
+
const dpr = canvasPixelRatio ?? (devicePixelRatio || 1);
|
|
323
|
+
const viewWidth = innerWidth * dpr | 0;
|
|
324
|
+
const viewHeight = innerHeight * dpr | 0;
|
|
325
|
+
|
|
321
326
|
// get main canvas size based on window size
|
|
322
|
-
mainCanvasSize.x = min(
|
|
323
|
-
mainCanvasSize.y = min(
|
|
327
|
+
mainCanvasSize.x = min(viewWidth, canvasMaxSize.x);
|
|
328
|
+
mainCanvasSize.y = min(viewHeight, canvasMaxSize.y);
|
|
324
329
|
|
|
325
330
|
// responsive aspect ratio with native resolution
|
|
326
|
-
const innerAspect =
|
|
331
|
+
const innerAspect = viewWidth / viewHeight;
|
|
327
332
|
ASSERT(canvasMinAspect <= canvasMaxAspect);
|
|
328
333
|
if (canvasMaxAspect && innerAspect > canvasMaxAspect)
|
|
329
334
|
{
|
|
@@ -337,6 +342,17 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
337
342
|
const h = mainCanvasSize.x / canvasMinAspect | 0;
|
|
338
343
|
mainCanvasSize.y = min(h, canvasMaxSize.y);
|
|
339
344
|
}
|
|
345
|
+
|
|
346
|
+
// set CSS display size so backing store renders at viewport size
|
|
347
|
+
const cssW = (mainCanvasSize.x / dpr | 0) + 'px';
|
|
348
|
+
const cssH = (mainCanvasSize.y / dpr | 0) + 'px';
|
|
349
|
+
mainCanvas.style.width = cssW;
|
|
350
|
+
mainCanvas.style.height = cssH;
|
|
351
|
+
if (glCanvas)
|
|
352
|
+
{
|
|
353
|
+
glCanvas.style.width = cssW;
|
|
354
|
+
glCanvas.style.height = cssH;
|
|
355
|
+
}
|
|
340
356
|
}
|
|
341
357
|
|
|
342
358
|
// clear main canvas and set size
|
|
@@ -556,8 +572,9 @@ function drawEngineLogo(t)
|
|
|
556
572
|
|
|
557
573
|
// LittleJS Logo and Splash Screen
|
|
558
574
|
const x = mainContext;
|
|
559
|
-
const
|
|
560
|
-
const
|
|
575
|
+
const dpr = canvasPixelRatio ?? (devicePixelRatio || 1);
|
|
576
|
+
const w = mainCanvas.width = innerWidth * dpr;
|
|
577
|
+
const h = mainCanvas.height = innerHeight * dpr;
|
|
561
578
|
{
|
|
562
579
|
// background
|
|
563
580
|
const p3 = percent(t, 1, .8);
|
|
@@ -1054,7 +1071,7 @@ function lineTest(posStart, posEnd, testFunction, normal)
|
|
|
1054
1071
|
// get ray direction and length
|
|
1055
1072
|
const dx = posEnd.x - posStart.x;
|
|
1056
1073
|
const dy = posEnd.y - posStart.y;
|
|
1057
|
-
const totalLength =
|
|
1074
|
+
const totalLength = (dx*dx + dy*dy)**.5;
|
|
1058
1075
|
if (!totalLength) return;
|
|
1059
1076
|
|
|
1060
1077
|
// current integer cell we are in
|
|
@@ -2158,6 +2175,13 @@ let canvasPixelated = false;
|
|
|
2158
2175
|
* @memberof Settings */
|
|
2159
2176
|
let tilesPixelated = true;
|
|
2160
2177
|
|
|
2178
|
+
/** Scale factor applied to the canvas backing store for native-resolution rendering.
|
|
2179
|
+
* Pass 1 for no scaling, a number for an explicit ratio, or undefined to track devicePixelRatio each frame.
|
|
2180
|
+
* @type {number|undefined}
|
|
2181
|
+
* @default
|
|
2182
|
+
* @memberof Settings */
|
|
2183
|
+
let canvasPixelRatio = 1;
|
|
2184
|
+
|
|
2161
2185
|
/** Default font used for text rendering
|
|
2162
2186
|
* @type {string}
|
|
2163
2187
|
* @default
|
|
@@ -2299,18 +2323,22 @@ let touchInputEnable = true;
|
|
|
2299
2323
|
|
|
2300
2324
|
/** True if touch gamepad should appear on mobile devices
|
|
2301
2325
|
* - Supports left analog stick, 4 face buttons and start button (button 9)
|
|
2326
|
+
* - setTouchGamepadButtonCount(1) to use face buttons as right analog stick
|
|
2327
|
+
* - Analog stick buttons 10 and 11 are also activated when virtual sticks are touched
|
|
2328
|
+
|
|
2302
2329
|
* @type {boolean}
|
|
2303
2330
|
* @default
|
|
2304
2331
|
* @memberof Settings */
|
|
2305
2332
|
let touchGamepadEnable = false;
|
|
2306
2333
|
|
|
2307
2334
|
/** True if touch gamepad should have start button in the center
|
|
2335
|
+
* - Prevents activating if overlappng with virtual stick or buttons if they are enabled
|
|
2308
2336
|
* - When the game is paused, any touch will press the button
|
|
2309
|
-
* -
|
|
2310
|
-
* @type {
|
|
2337
|
+
* - Set size to enable the center button
|
|
2338
|
+
* @type {number}
|
|
2311
2339
|
* @default
|
|
2312
2340
|
* @memberof Settings */
|
|
2313
|
-
let
|
|
2341
|
+
let touchGamepadCenterButtonSize = 300;
|
|
2314
2342
|
|
|
2315
2343
|
/** Number of buttons on touch gamepad (0-4), if 1 also acts as right analog stick
|
|
2316
2344
|
* @type {number}
|
|
@@ -2328,7 +2356,7 @@ let touchGamepadAnalog = true;
|
|
|
2328
2356
|
* @type {number}
|
|
2329
2357
|
* @default
|
|
2330
2358
|
* @memberof Settings */
|
|
2331
|
-
let touchGamepadSize =
|
|
2359
|
+
let touchGamepadSize = 100;
|
|
2332
2360
|
|
|
2333
2361
|
/** Transparency of touch gamepad overlay
|
|
2334
2362
|
* @type {number}
|
|
@@ -2470,6 +2498,12 @@ function setCanvasPixelated(pixelated)
|
|
|
2470
2498
|
* @memberof Settings */
|
|
2471
2499
|
function setTilesPixelated(pixelated) { tilesPixelated = pixelated; }
|
|
2472
2500
|
|
|
2501
|
+
/** Set the canvas pixel ratio.
|
|
2502
|
+
* Pass a number for an explicit ratio, or call with no argument to track devicePixelRatio each frame.
|
|
2503
|
+
* @param {number} [pixelRatio]
|
|
2504
|
+
* @memberof Settings */
|
|
2505
|
+
function setCanvasPixelRatio(pixelRatio) { canvasPixelRatio = pixelRatio; }
|
|
2506
|
+
|
|
2473
2507
|
/** Set default font used for text rendering
|
|
2474
2508
|
* @param {string} font
|
|
2475
2509
|
* @memberof Settings */
|
|
@@ -2590,11 +2624,12 @@ function setTouchInputEnable(enable) { touchInputEnable = enable; }
|
|
|
2590
2624
|
* @memberof Settings */
|
|
2591
2625
|
function setTouchGamepadEnable(enable) { touchGamepadEnable = enable; }
|
|
2592
2626
|
|
|
2593
|
-
/**
|
|
2594
|
-
* -
|
|
2595
|
-
*
|
|
2627
|
+
/** Set if touch gamepad should have start button in the center
|
|
2628
|
+
* - Set size to enable the center button
|
|
2629
|
+
* - When the game is paused, any touch will press the button
|
|
2630
|
+
* @param {number} size
|
|
2596
2631
|
* @memberof Settings */
|
|
2597
|
-
function
|
|
2632
|
+
function setTouchGamepadCenterButtonSize(size) { touchGamepadCenterButtonSize = size; }
|
|
2598
2633
|
|
|
2599
2634
|
/** Set number of buttons on touch gamepad (0-4), if 1 also acts as right analog stick
|
|
2600
2635
|
* @param {number} count
|
|
@@ -2892,10 +2927,10 @@ class EngineObject
|
|
|
2892
2927
|
// if already was touching, try to push away
|
|
2893
2928
|
const deltaPos = oldPos.subtract(o.pos);
|
|
2894
2929
|
const length = deltaPos.length();
|
|
2895
|
-
const pushAwayAccel = .001;
|
|
2896
|
-
const velocity = length < .
|
|
2930
|
+
const pushAwayAccel = .001;
|
|
2931
|
+
const velocity = length < .001 ? vec2(0,1) : deltaPos.scale(pushAwayAccel/length);
|
|
2897
2932
|
this.velocity = this.velocity.add(velocity);
|
|
2898
|
-
if (o.mass) // push away if not fixed
|
|
2933
|
+
if (o.mass) // push away other object if not fixed
|
|
2899
2934
|
o.velocity = o.velocity.subtract(velocity);
|
|
2900
2935
|
|
|
2901
2936
|
debugPhysics && debugOverlap(this.pos, this.size, o.pos, o.size, '#f00');
|
|
@@ -3132,6 +3167,7 @@ class EngineObject
|
|
|
3132
3167
|
child.parent = this;
|
|
3133
3168
|
child.localPos = localPos.copy();
|
|
3134
3169
|
child.localAngle = localAngle;
|
|
3170
|
+
child.updateTransforms();
|
|
3135
3171
|
return child;
|
|
3136
3172
|
}
|
|
3137
3173
|
|
|
@@ -3324,6 +3360,7 @@ function tile(index=new Vector2, size=tileDefaultSize, texture=0, padding=tileDe
|
|
|
3324
3360
|
const textureInfo = typeof texture === 'number' ?
|
|
3325
3361
|
textureInfos[texture] : texture;
|
|
3326
3362
|
ASSERT(textureInfo instanceof TextureInfo, 'tile texture is not loaded');
|
|
3363
|
+
ASSERT(textureInfo.size.x > 0, 'tile texture is not loaded');
|
|
3327
3364
|
|
|
3328
3365
|
// get the position of the tile
|
|
3329
3366
|
const sizePaddedX = size.x + padding*2;
|
|
@@ -4686,7 +4723,7 @@ function inputInit()
|
|
|
4686
4723
|
document.addEventListener('mouseup', onMouseUp);
|
|
4687
4724
|
document.addEventListener('mousemove', onMouseMove);
|
|
4688
4725
|
document.addEventListener('mouseleave', onMouseLeave);
|
|
4689
|
-
document.addEventListener('wheel', onMouseWheel);
|
|
4726
|
+
document.addEventListener('wheel', onMouseWheel, { passive: false });
|
|
4690
4727
|
document.addEventListener('contextmenu', onContextMenu);
|
|
4691
4728
|
document.addEventListener('blur', onBlur);
|
|
4692
4729
|
|
|
@@ -4705,7 +4742,7 @@ function inputInit()
|
|
|
4705
4742
|
}
|
|
4706
4743
|
|
|
4707
4744
|
// try to prevent default browser handling of input
|
|
4708
|
-
if (!inputPreventDefault || !
|
|
4745
|
+
if (!inputPreventDefault || !e.cancelable || !document.hasFocus()) return;
|
|
4709
4746
|
|
|
4710
4747
|
// don't break browser shortcuts
|
|
4711
4748
|
if (e.ctrlKey || e.metaKey || e.altKey) return;
|
|
@@ -4764,7 +4801,7 @@ function inputInit()
|
|
|
4764
4801
|
mousePosScreen = mouseEventToScreen(vec2(e.x,e.y));
|
|
4765
4802
|
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
|
|
4766
4803
|
|
|
4767
|
-
if (inputPreventDefault &&
|
|
4804
|
+
if (inputPreventDefault && e.cancelable && document.hasFocus())
|
|
4768
4805
|
e.preventDefault();
|
|
4769
4806
|
}
|
|
4770
4807
|
function onMouseUp(e)
|
|
@@ -4786,7 +4823,12 @@ function inputInit()
|
|
|
4786
4823
|
mouseDeltaScreen = mouseDeltaScreen.add(movement);
|
|
4787
4824
|
}
|
|
4788
4825
|
function onMouseLeave() { mouseInWindow = false; } // mouse moved off window
|
|
4789
|
-
function onMouseWheel(e)
|
|
4826
|
+
function onMouseWheel(e)
|
|
4827
|
+
{
|
|
4828
|
+
mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY);
|
|
4829
|
+
if (inputPreventDefault && e.cancelable && document.hasFocus())
|
|
4830
|
+
e.preventDefault(); // prevent page scrolling
|
|
4831
|
+
}
|
|
4790
4832
|
function onContextMenu(e) { e.preventDefault(); } // prevent right click menu
|
|
4791
4833
|
function onBlur() { inputClear(); } // reset input when focus is lost
|
|
4792
4834
|
|
|
@@ -4836,7 +4878,7 @@ function inputInit()
|
|
|
4836
4878
|
wasTouching = touching;
|
|
4837
4879
|
|
|
4838
4880
|
// prevent default handling like copy, magnifier lens, and scrolling
|
|
4839
|
-
if (inputPreventDefault &&
|
|
4881
|
+
if (inputPreventDefault && e.cancelable && document.hasFocus())
|
|
4840
4882
|
e.preventDefault();
|
|
4841
4883
|
|
|
4842
4884
|
// must return true so the document will get focus
|
|
@@ -4857,7 +4899,7 @@ function inputInit()
|
|
|
4857
4899
|
if (touching)
|
|
4858
4900
|
{
|
|
4859
4901
|
touchGamepadTimer.set();
|
|
4860
|
-
if (
|
|
4902
|
+
if (touchGamepadCenterButtonSize && !wasTouching && paused)
|
|
4861
4903
|
{
|
|
4862
4904
|
// touch anywhere to press start when paused
|
|
4863
4905
|
touchGamepadButtons[9] = 1;
|
|
@@ -4882,6 +4924,7 @@ function inputInit()
|
|
|
4882
4924
|
// virtual analog stick
|
|
4883
4925
|
const delta = touchPos.subtract(stickCenter);
|
|
4884
4926
|
touchGamepadSticks[0] = delta.scale(2/touchGamepadSize).clampLength();
|
|
4927
|
+
touchGamepadButtons[10] = 1; // also press a button when touching stick
|
|
4885
4928
|
}
|
|
4886
4929
|
else if (buttonCenter.distance(touchPos) < touchGamepadSize)
|
|
4887
4930
|
{
|
|
@@ -4890,6 +4933,7 @@ function inputInit()
|
|
|
4890
4933
|
// virtual right analog stick
|
|
4891
4934
|
const delta = touchPos.subtract(buttonCenter);
|
|
4892
4935
|
touchGamepadSticks[1] = delta.scale(2/touchGamepadSize).clampLength();
|
|
4936
|
+
touchGamepadButtons[11] = 1; // also press a button when touching right stick
|
|
4893
4937
|
}
|
|
4894
4938
|
// virtual face buttons
|
|
4895
4939
|
let button = buttonCenter.subtract(touchPos).direction();
|
|
@@ -4906,8 +4950,7 @@ function inputInit()
|
|
|
4906
4950
|
if (button < touchGamepadButtonCount)
|
|
4907
4951
|
touchGamepadButtons[button] = 1;
|
|
4908
4952
|
}
|
|
4909
|
-
else if (
|
|
4910
|
-
startCenter.distance(touchPos) < touchGamepadSize)
|
|
4953
|
+
else if (startCenter.distance(touchPos) < touchGamepadCenterButtonSize)
|
|
4911
4954
|
{
|
|
4912
4955
|
// virtual start button in center
|
|
4913
4956
|
touchGamepadButtons[9] = 1;
|
|
@@ -4956,6 +4999,18 @@ function inputUpdate()
|
|
|
4956
4999
|
// update touch gamepad if enabled
|
|
4957
5000
|
if (touchGamepadEnable && isTouchDevice)
|
|
4958
5001
|
{
|
|
5002
|
+
if (debugGamepads)
|
|
5003
|
+
{
|
|
5004
|
+
const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
5005
|
+
const buttonCenter = touchGamepadButtonCenter();
|
|
5006
|
+
const startCenter = mainCanvasSize.scale(.5);
|
|
5007
|
+
|
|
5008
|
+
debugCircle(stickCenter, 2*touchGamepadSize, 'cyan', 0, false, true);
|
|
5009
|
+
debugCircle(buttonCenter, 2*touchGamepadSize, 'cyan', 0, false, true);
|
|
5010
|
+
if (touchGamepadCenterButtonSize)
|
|
5011
|
+
debugCircle(startCenter, 2*touchGamepadCenterButtonSize, 'cyan', 0, false, true);
|
|
5012
|
+
}
|
|
5013
|
+
|
|
4959
5014
|
if (!touchGamepadTimer.isSet()) return;
|
|
4960
5015
|
|
|
4961
5016
|
// read virtual analog stick
|
|
@@ -4983,7 +5038,7 @@ function inputUpdate()
|
|
|
4983
5038
|
|
|
4984
5039
|
// read virtual gamepad buttons
|
|
4985
5040
|
const data = inputData[1] ?? (inputData[1] = []);
|
|
4986
|
-
for (let i=
|
|
5041
|
+
for (let i=12; i--;)
|
|
4987
5042
|
{
|
|
4988
5043
|
const wasDown = gamepadIsDown(i,0);
|
|
4989
5044
|
data[i] = touchGamepadButtons[i] ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
|
|
@@ -4994,7 +5049,13 @@ function inputUpdate()
|
|
|
4994
5049
|
}
|
|
4995
5050
|
|
|
4996
5051
|
// return if gamepads are disabled or not supported
|
|
4997
|
-
|
|
5052
|
+
try {
|
|
5053
|
+
// protect against getGamepads disallowed security error
|
|
5054
|
+
if (!gamepadsEnable || !navigator?.getGamepads)
|
|
5055
|
+
return;
|
|
5056
|
+
} catch(e) {
|
|
5057
|
+
return;
|
|
5058
|
+
}
|
|
4998
5059
|
|
|
4999
5060
|
// only poll gamepads when focused or in debug mode
|
|
5000
5061
|
if (!debug && !document.hasFocus()) return;
|
package/examples/box2d/game.js
CHANGED
|
@@ -24,7 +24,7 @@ LJS.setTilesPixelated(false);
|
|
|
24
24
|
///////////////////////////////////////////////////////////////////////////////
|
|
25
25
|
// game variables
|
|
26
26
|
|
|
27
|
-
const
|
|
27
|
+
const sceneCount = 12;
|
|
28
28
|
const startScene = 0;
|
|
29
29
|
export let spriteAtlas, groundObject, mouseJoint, repeatSpawnTimer = new LJS.Timer;
|
|
30
30
|
const sound_click = new LJS.Sound([.2,.1,,,,.01,,,,,,,,,,,,,,,-500]);
|
|
@@ -125,7 +125,7 @@ function gameUpdate()
|
|
|
125
125
|
{
|
|
126
126
|
// change scene
|
|
127
127
|
const upPressed = LJS.keyWasPressed('ArrowUp');
|
|
128
|
-
setScene(LJS.mod(Scenes.scene + (upPressed?1:-1),
|
|
128
|
+
setScene(LJS.mod(Scenes.scene + (upPressed?1:-1), sceneCount));
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
|
|
@@ -446,6 +446,80 @@ export class ClothObject extends LJS.Box2dStaticObject
|
|
|
446
446
|
}
|
|
447
447
|
}
|
|
448
448
|
|
|
449
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
450
|
+
|
|
451
|
+
// ragdoll with head, arms, and legs connected to torso by revolute joints
|
|
452
|
+
export class RagdollObject extends LJS.Box2dObject
|
|
453
|
+
{
|
|
454
|
+
constructor(pos, scale=1, color=LJS.randColor())
|
|
455
|
+
{
|
|
456
|
+
const headSize = .8 * scale;
|
|
457
|
+
const torsoWidth = 1.2 * scale;
|
|
458
|
+
const torsoHeight = 2 * scale;
|
|
459
|
+
const limbWidth = .4 * scale;
|
|
460
|
+
const upperArmLen = 1 * scale;
|
|
461
|
+
const lowerArmLen = 1 * scale;
|
|
462
|
+
const upperLegLen = 1.1 * scale;
|
|
463
|
+
const lowerLegLen = 1.1 * scale;
|
|
464
|
+
|
|
465
|
+
// torso is the main body
|
|
466
|
+
super(pos, vec2(torsoWidth, torsoHeight), 0, 0, color);
|
|
467
|
+
this.addBox(vec2(torsoWidth, torsoHeight), vec2(), 0, density, friction, restitution);
|
|
468
|
+
this.limbs = [];
|
|
469
|
+
|
|
470
|
+
const addLimb = (offset, size, isCircle=false) =>
|
|
471
|
+
{
|
|
472
|
+
const o = new LJS.Box2dObject(pos.add(offset), size, 0, 0, color);
|
|
473
|
+
if (isCircle)
|
|
474
|
+
o.addCircle(size.x, vec2(), density, friction, restitution);
|
|
475
|
+
else
|
|
476
|
+
o.addBox(size, vec2(), 0, density, friction, restitution);
|
|
477
|
+
this.limbs.push(o);
|
|
478
|
+
return o;
|
|
479
|
+
};
|
|
480
|
+
const addJoint = (a, b, anchor, lower, upper) =>
|
|
481
|
+
{
|
|
482
|
+
const joint = new LJS.Box2dRevoluteJoint(a, b, anchor);
|
|
483
|
+
joint.enableLimit();
|
|
484
|
+
joint.setLimits(lower, upper);
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
// head
|
|
488
|
+
const head = addLimb(vec2(0, torsoHeight/2 + headSize/2 + .1*scale), vec2(headSize), true);
|
|
489
|
+
addJoint(this, head, pos.add(vec2(0, torsoHeight/2)), -.5, .5);
|
|
490
|
+
|
|
491
|
+
// arms
|
|
492
|
+
const shoulderY = torsoHeight/2 - .2*scale;
|
|
493
|
+
for (const side of [-1, 1])
|
|
494
|
+
{
|
|
495
|
+
const shoulder = pos.add(vec2(side*torsoWidth/2, shoulderY));
|
|
496
|
+
const upperArm = addLimb(vec2(side*(torsoWidth/2 + upperArmLen/2), shoulderY), vec2(upperArmLen, limbWidth));
|
|
497
|
+
addJoint(this, upperArm, shoulder, -2, 2);
|
|
498
|
+
|
|
499
|
+
const elbow = shoulder.add(vec2(side*upperArmLen, 0));
|
|
500
|
+
const lowerArm = addLimb(vec2(side*(torsoWidth/2 + upperArmLen + lowerArmLen/2), shoulderY), vec2(lowerArmLen, limbWidth));
|
|
501
|
+
addJoint(upperArm, lowerArm, elbow, -2.5, 2.5);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// legs
|
|
505
|
+
for (const side of [-1, 1])
|
|
506
|
+
{
|
|
507
|
+
const hip = pos.add(vec2(side*torsoWidth/4, -torsoHeight/2));
|
|
508
|
+
const upperLeg = addLimb(vec2(side*torsoWidth/4, -torsoHeight/2 - upperLegLen/2), vec2(limbWidth, upperLegLen));
|
|
509
|
+
addJoint(this, upperLeg, hip, -.5, 2.5);
|
|
510
|
+
|
|
511
|
+
const knee = hip.add(vec2(0, -upperLegLen));
|
|
512
|
+
const lowerLeg = addLimb(vec2(side*torsoWidth/4, -torsoHeight/2 - upperLegLen - lowerLegLen/2), vec2(limbWidth, lowerLegLen));
|
|
513
|
+
addJoint(upperLeg, lowerLeg, knee, -2.5, 2.5);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
destroy()
|
|
517
|
+
{
|
|
518
|
+
this.limbs.forEach(o => o.destroy());
|
|
519
|
+
super.destroy();
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
449
523
|
///////////////////////////////////////////////////////////////////////////////
|
|
450
524
|
// Effects
|
|
451
525
|
|
package/examples/box2d/scenes.js
CHANGED
|
@@ -184,4 +184,11 @@ export function loadScene(_scene)
|
|
|
184
184
|
for (let i=3;i--;)
|
|
185
185
|
new GameObjects.SoftBodyObject(vec2(20, 3+i*7), vec2(6-i), vec2(9-i), LJS.randColor());
|
|
186
186
|
}
|
|
187
|
+
if (scene == 11)
|
|
188
|
+
{
|
|
189
|
+
sceneName = 'Ragdolls';
|
|
190
|
+
new GameObjects.RagdollObject(vec2(20,5));
|
|
191
|
+
new GameObjects.RagdollObject(vec2(10,8), 2);
|
|
192
|
+
GameObjects.spawnPyramid(vec2(30,0), 6);
|
|
193
|
+
}
|
|
187
194
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
</head><body>
|
|
8
8
|
|
|
9
9
|
<!-- LittleJS Engine -->
|
|
10
|
-
<script src=../../dist/littlejs.js?1.18.
|
|
10
|
+
<script src=../../dist/littlejs.js?1.18.2></script>
|
|
11
11
|
|
|
12
12
|
<!-- LittleJS Engine Source
|
|
13
13
|
<script src=../../src/engine.js></script>
|
|
@@ -32,4 +32,4 @@
|
|
|
32
32
|
-->
|
|
33
33
|
|
|
34
34
|
<!-- Add your game scripts here -->
|
|
35
|
-
<script src=game.js?1.18.
|
|
35
|
+
<script src=game.js?1.18.2></script>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "littlejsengine",
|
|
3
|
-
"version": "1.18.
|
|
3
|
+
"version": "1.18.2",
|
|
4
4
|
"description": "LittleJS - Tiny and Fast HTML5 Game Engine",
|
|
5
5
|
"main": "dist/littlejs.esm.js",
|
|
6
6
|
"types": "dist/littlejs.d.ts",
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
},
|
|
31
31
|
"homepage": "https://github.com/KilledByAPixel/LittleJS",
|
|
32
32
|
"scripts": {
|
|
33
|
-
"build": "node src/engineBuild.mjs"
|
|
33
|
+
"build": "node src/engineBuild.mjs",
|
|
34
|
+
"test": "node --test --import ./test/setup.mjs \"test/**/*.test.mjs\""
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
37
|
"bestzip": "^2.2.1",
|
package/src/engine.js
CHANGED
|
@@ -32,7 +32,7 @@ const engineName = 'LittleJS';
|
|
|
32
32
|
* @type {string}
|
|
33
33
|
* @default
|
|
34
34
|
* @memberof Engine */
|
|
35
|
-
const engineVersion = '1.18.
|
|
35
|
+
const engineVersion = '1.18.2';
|
|
36
36
|
|
|
37
37
|
/** Frames per second to update
|
|
38
38
|
* @type {number}
|
|
@@ -315,12 +315,17 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
315
315
|
}
|
|
316
316
|
else
|
|
317
317
|
{
|
|
318
|
+
// apply device pixel ratio for crisp rendering
|
|
319
|
+
const dpr = canvasPixelRatio ?? (devicePixelRatio || 1);
|
|
320
|
+
const viewWidth = innerWidth * dpr | 0;
|
|
321
|
+
const viewHeight = innerHeight * dpr | 0;
|
|
322
|
+
|
|
318
323
|
// get main canvas size based on window size
|
|
319
|
-
mainCanvasSize.x = min(
|
|
320
|
-
mainCanvasSize.y = min(
|
|
324
|
+
mainCanvasSize.x = min(viewWidth, canvasMaxSize.x);
|
|
325
|
+
mainCanvasSize.y = min(viewHeight, canvasMaxSize.y);
|
|
321
326
|
|
|
322
327
|
// responsive aspect ratio with native resolution
|
|
323
|
-
const innerAspect =
|
|
328
|
+
const innerAspect = viewWidth / viewHeight;
|
|
324
329
|
ASSERT(canvasMinAspect <= canvasMaxAspect);
|
|
325
330
|
if (canvasMaxAspect && innerAspect > canvasMaxAspect)
|
|
326
331
|
{
|
|
@@ -334,6 +339,17 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
334
339
|
const h = mainCanvasSize.x / canvasMinAspect | 0;
|
|
335
340
|
mainCanvasSize.y = min(h, canvasMaxSize.y);
|
|
336
341
|
}
|
|
342
|
+
|
|
343
|
+
// set CSS display size so backing store renders at viewport size
|
|
344
|
+
const cssW = (mainCanvasSize.x / dpr | 0) + 'px';
|
|
345
|
+
const cssH = (mainCanvasSize.y / dpr | 0) + 'px';
|
|
346
|
+
mainCanvas.style.width = cssW;
|
|
347
|
+
mainCanvas.style.height = cssH;
|
|
348
|
+
if (glCanvas)
|
|
349
|
+
{
|
|
350
|
+
glCanvas.style.width = cssW;
|
|
351
|
+
glCanvas.style.height = cssH;
|
|
352
|
+
}
|
|
337
353
|
}
|
|
338
354
|
|
|
339
355
|
// clear main canvas and set size
|
|
@@ -553,8 +569,9 @@ function drawEngineLogo(t)
|
|
|
553
569
|
|
|
554
570
|
// LittleJS Logo and Splash Screen
|
|
555
571
|
const x = mainContext;
|
|
556
|
-
const
|
|
557
|
-
const
|
|
572
|
+
const dpr = canvasPixelRatio ?? (devicePixelRatio || 1);
|
|
573
|
+
const w = mainCanvas.width = innerWidth * dpr;
|
|
574
|
+
const h = mainCanvas.height = innerHeight * dpr;
|
|
558
575
|
{
|
|
559
576
|
// background
|
|
560
577
|
const p3 = percent(t, 1, .8);
|
package/src/engineBuild.mjs
CHANGED
|
@@ -61,7 +61,7 @@ const license = '// LittleJS Engine - MIT License - Copyright 2021 Frank Force\n
|
|
|
61
61
|
'// https://github.com/KilledByAPixel/LittleJS\n\n';
|
|
62
62
|
|
|
63
63
|
console.log(asciiArt);
|
|
64
|
-
console.log('
|
|
64
|
+
console.log('Choo Choo... Building LittleJS Engine!');
|
|
65
65
|
const startTime = Date.now();
|
|
66
66
|
|
|
67
67
|
try
|
package/src/engineDebug.js
CHANGED
package/src/engineDraw.js
CHANGED
|
@@ -110,6 +110,7 @@ function tile(index=new Vector2, size=tileDefaultSize, texture=0, padding=tileDe
|
|
|
110
110
|
const textureInfo = typeof texture === 'number' ?
|
|
111
111
|
textureInfos[texture] : texture;
|
|
112
112
|
ASSERT(textureInfo instanceof TextureInfo, 'tile texture is not loaded');
|
|
113
|
+
ASSERT(textureInfo.size.x > 0, 'tile texture is not loaded');
|
|
113
114
|
|
|
114
115
|
// get the position of the tile
|
|
115
116
|
const sizePaddedX = size.x + padding*2;
|
package/src/engineExport.js
CHANGED
|
@@ -59,6 +59,7 @@ export
|
|
|
59
59
|
canvasFixedSize,
|
|
60
60
|
canvasPixelated,
|
|
61
61
|
tilesPixelated,
|
|
62
|
+
canvasPixelRatio,
|
|
62
63
|
fontDefault,
|
|
63
64
|
showSplashScreen,
|
|
64
65
|
headlessMode,
|
|
@@ -79,7 +80,7 @@ export
|
|
|
79
80
|
gamepadDirectionEmulateStick,
|
|
80
81
|
inputWASDEmulateDirection,
|
|
81
82
|
touchGamepadEnable,
|
|
82
|
-
|
|
83
|
+
touchGamepadCenterButtonSize,
|
|
83
84
|
touchGamepadAnalog,
|
|
84
85
|
touchGamepadSize,
|
|
85
86
|
touchGamepadAlpha,
|
|
@@ -105,6 +106,7 @@ export
|
|
|
105
106
|
setCanvasFixedSize,
|
|
106
107
|
setCanvasPixelated,
|
|
107
108
|
setTilesPixelated,
|
|
109
|
+
setCanvasPixelRatio,
|
|
108
110
|
setFontDefault,
|
|
109
111
|
setShowSplashScreen,
|
|
110
112
|
setHeadlessMode,
|
|
@@ -126,7 +128,7 @@ export
|
|
|
126
128
|
setGamepadDirectionEmulateStick,
|
|
127
129
|
setInputWASDEmulateDirection,
|
|
128
130
|
setTouchGamepadEnable,
|
|
129
|
-
|
|
131
|
+
setTouchGamepadCenterButtonSize,
|
|
130
132
|
setTouchGamepadButtonCount,
|
|
131
133
|
setTouchGamepadAnalog,
|
|
132
134
|
setTouchGamepadSize,
|
|
@@ -166,10 +168,12 @@ export
|
|
|
166
168
|
distanceAngle,
|
|
167
169
|
lerpAngle,
|
|
168
170
|
lerp,
|
|
171
|
+
percentLerp,
|
|
169
172
|
smoothStep,
|
|
170
173
|
nearestPowerOfTwo,
|
|
171
174
|
isOverlapping,
|
|
172
175
|
isIntersecting,
|
|
176
|
+
lineTest,
|
|
173
177
|
oscillate,
|
|
174
178
|
|
|
175
179
|
// Utilities
|