littlejsengine 1.17.15 → 1.18.1

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.
@@ -35,7 +35,7 @@ const engineName = 'LittleJS';
35
35
  * @type {string}
36
36
  * @default
37
37
  * @memberof Engine */
38
- const engineVersion = '1.17.15';
38
+ const engineVersion = '1.18.1';
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(innerWidth, canvasMaxSize.x);
323
- mainCanvasSize.y = min(innerHeight, canvasMaxSize.y);
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 = innerWidth / innerHeight;
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 w = mainCanvas.width = innerWidth;
560
- const h = mainCanvas.height = innerHeight;
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 = hypot(dx, dy);
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
@@ -2336,6 +2360,12 @@ let touchGamepadSize = 99;
2336
2360
  * @memberof Settings */
2337
2361
  let touchGamepadAlpha = .3;
2338
2362
 
2363
+ /** How long to display the touch gamepad on screen in seconds, set to 0 to always display
2364
+ * @type {number}
2365
+ * @default
2366
+ * @memberof Settings */
2367
+ let touchGamepadDisplayTime = 3;
2368
+
2339
2369
  /** Allow vibration hardware if it exists
2340
2370
  * @type {boolean}
2341
2371
  * @default
@@ -2464,6 +2494,12 @@ function setCanvasPixelated(pixelated)
2464
2494
  * @memberof Settings */
2465
2495
  function setTilesPixelated(pixelated) { tilesPixelated = pixelated; }
2466
2496
 
2497
+ /** Set the canvas pixel ratio.
2498
+ * Pass a number for an explicit ratio, or call with no argument to track devicePixelRatio each frame.
2499
+ * @param {number} [pixelRatio]
2500
+ * @memberof Settings */
2501
+ function setCanvasPixelRatio(pixelRatio) { canvasPixelRatio = pixelRatio; }
2502
+
2467
2503
  /** Set default font used for text rendering
2468
2504
  * @param {string} font
2469
2505
  * @memberof Settings */
@@ -2610,6 +2646,11 @@ function setTouchGamepadSize(size) { touchGamepadSize = size; }
2610
2646
  * @memberof Settings */
2611
2647
  function setTouchGamepadAlpha(alpha) { touchGamepadAlpha = alpha; }
2612
2648
 
2649
+ /** Set how long to display the touch gamepad on screen in seconds, set to 0 to always display
2650
+ * @param {number} time
2651
+ * @memberof Settings */
2652
+ function setTouchGamepadDisplayTime(time) { touchGamepadDisplayTime = time; }
2653
+
2613
2654
  /** Set to allow vibration hardware if it exists
2614
2655
  * @param {boolean} enable
2615
2656
  * @memberof Settings */
@@ -2881,10 +2922,10 @@ class EngineObject
2881
2922
  // if already was touching, try to push away
2882
2923
  const deltaPos = oldPos.subtract(o.pos);
2883
2924
  const length = deltaPos.length();
2884
- const pushAwayAccel = .001; // push away if already overlapping
2885
- const velocity = length < .01 ? randVec2(pushAwayAccel) : deltaPos.scale(pushAwayAccel/length);
2925
+ const pushAwayAccel = .001;
2926
+ const velocity = length < .001 ? vec2(0,1) : deltaPos.scale(pushAwayAccel/length);
2886
2927
  this.velocity = this.velocity.add(velocity);
2887
- if (o.mass) // push away if not fixed
2928
+ if (o.mass) // push away other object if not fixed
2888
2929
  o.velocity = o.velocity.subtract(velocity);
2889
2930
 
2890
2931
  debugPhysics && debugOverlap(this.pos, this.size, o.pos, o.size, '#f00');
@@ -3313,6 +3354,7 @@ function tile(index=new Vector2, size=tileDefaultSize, texture=0, padding=tileDe
3313
3354
  const textureInfo = typeof texture === 'number' ?
3314
3355
  textureInfos[texture] : texture;
3315
3356
  ASSERT(textureInfo instanceof TextureInfo, 'tile texture is not loaded');
3357
+ ASSERT(textureInfo.size.x > 0, 'tile texture is not loaded');
3316
3358
 
3317
3359
  // get the position of the tile
3318
3360
  const sizePaddedX = size.x + padding*2;
@@ -4675,7 +4717,7 @@ function inputInit()
4675
4717
  document.addEventListener('mouseup', onMouseUp);
4676
4718
  document.addEventListener('mousemove', onMouseMove);
4677
4719
  document.addEventListener('mouseleave', onMouseLeave);
4678
- document.addEventListener('wheel', onMouseWheel);
4720
+ document.addEventListener('wheel', onMouseWheel, { passive: false });
4679
4721
  document.addEventListener('contextmenu', onContextMenu);
4680
4722
  document.addEventListener('blur', onBlur);
4681
4723
 
@@ -4694,7 +4736,7 @@ function inputInit()
4694
4736
  }
4695
4737
 
4696
4738
  // try to prevent default browser handling of input
4697
- if (!inputPreventDefault || !document.hasFocus() || !e.cancelable) return;
4739
+ if (!inputPreventDefault || !e.cancelable || !document.hasFocus()) return;
4698
4740
 
4699
4741
  // don't break browser shortcuts
4700
4742
  if (e.ctrlKey || e.metaKey || e.altKey) return;
@@ -4753,7 +4795,7 @@ function inputInit()
4753
4795
  mousePosScreen = mouseEventToScreen(vec2(e.x,e.y));
4754
4796
  mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
4755
4797
 
4756
- if (inputPreventDefault && document.hasFocus() && e.cancelable)
4798
+ if (inputPreventDefault && e.cancelable && document.hasFocus())
4757
4799
  e.preventDefault();
4758
4800
  }
4759
4801
  function onMouseUp(e)
@@ -4775,7 +4817,12 @@ function inputInit()
4775
4817
  mouseDeltaScreen = mouseDeltaScreen.add(movement);
4776
4818
  }
4777
4819
  function onMouseLeave() { mouseInWindow = false; } // mouse moved off window
4778
- function onMouseWheel(e) { mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY); }
4820
+ function onMouseWheel(e)
4821
+ {
4822
+ mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY);
4823
+ if (inputPreventDefault && e.cancelable && document.hasFocus())
4824
+ e.preventDefault(); // prevent page scrolling
4825
+ }
4779
4826
  function onContextMenu(e) { e.preventDefault(); } // prevent right click menu
4780
4827
  function onBlur() { inputClear(); } // reset input when focus is lost
4781
4828
 
@@ -4825,7 +4872,7 @@ function inputInit()
4825
4872
  wasTouching = touching;
4826
4873
 
4827
4874
  // prevent default handling like copy, magnifier lens, and scrolling
4828
- if (inputPreventDefault && document.hasFocus() && e.cancelable)
4875
+ if (inputPreventDefault && e.cancelable && document.hasFocus())
4829
4876
  e.preventDefault();
4830
4877
 
4831
4878
  // must return true so the document will get focus
@@ -4983,7 +5030,13 @@ function inputUpdate()
4983
5030
  }
4984
5031
 
4985
5032
  // return if gamepads are disabled or not supported
4986
- if (!gamepadsEnable || !navigator || !navigator.getGamepads) return;
5033
+ try {
5034
+ // protect against getGamepads disallowed security error
5035
+ if (!gamepadsEnable || !navigator?.getGamepads)
5036
+ return;
5037
+ } catch(e) {
5038
+ return;
5039
+ }
4987
5040
 
4988
5041
  // only poll gamepads when focused or in debug mode
4989
5042
  if (!debug && !document.hasFocus()) return;
@@ -5081,10 +5134,10 @@ function inputRender()
5081
5134
  function touchGamepadRender()
5082
5135
  {
5083
5136
  if (!touchInputEnable || !isTouchDevice || headlessMode) return;
5084
- if (!touchGamepadEnable || !touchGamepadTimer.isSet()) return;
5137
+ if (!touchGamepadEnable || !touchGamepadTimer.isSet() && touchGamepadDisplayTime) return;
5085
5138
 
5086
5139
  // fade off when not touching or paused
5087
- const alpha = percent(touchGamepadTimer.get(), 4, 3);
5140
+ const alpha = touchGamepadDisplayTime ? percent(touchGamepadTimer.get(), touchGamepadDisplayTime+1, touchGamepadDisplayTime) : 1;
5088
5141
  if (!alpha || paused) return;
5089
5142
 
5090
5143
  // setup the canvas
@@ -8655,7 +8708,7 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
8655
8708
  * - Buttons
8656
8709
  * - Checkboxes
8657
8710
  * - Images
8658
- * - Scrollbars
8711
+ * - Sliders
8659
8712
  * - Video
8660
8713
  * @namespace UISystem
8661
8714
  */
@@ -9852,7 +9905,7 @@ class UICheckbox extends UIObject
9852
9905
  ASSERT(isString(text), 'ui checkbox must be a string');
9853
9906
  ASSERT(isColor(color), 'ui checkbox color must be a color');
9854
9907
 
9855
- /** @property {boolean} - Current percentage value of this scrollbar 0-1 */
9908
+ /** @property {boolean} - Current percentage value of this slider 0-1 */
9856
9909
  this.checked = checked;
9857
9910
  // set properties
9858
9911
  this.text = text;
@@ -9887,13 +9940,13 @@ class UICheckbox extends UIObject
9887
9940
 
9888
9941
  ///////////////////////////////////////////////////////////////////////////////
9889
9942
  /**
9890
- * UIScrollbar - A UI object that acts as a scrollbar
9943
+ * UISlider - A UI object that acts as a slider or scrollbar
9891
9944
  * @extends UIObject
9892
9945
  * @memberof UISystem
9893
9946
  */
9894
- class UIScrollbar extends UIObject
9947
+ class UISlider extends UIObject
9895
9948
  {
9896
- /** Create a UIScrollbar object
9949
+ /** Create a UISlider object
9897
9950
  * @param {Vector2} [pos]
9898
9951
  * @param {Vector2} [size]
9899
9952
  * @param {number} [value]
@@ -9905,14 +9958,14 @@ class UIScrollbar extends UIObject
9905
9958
  {
9906
9959
  super(pos, size);
9907
9960
 
9908
- ASSERT(isNumber(value), 'ui scrollbar value must be a number');
9909
- ASSERT(isString(text), 'ui scrollbar must be a string');
9910
- ASSERT(isColor(color), 'ui scrollbar color must be a color');
9911
- ASSERT(isColor(handleColor), 'ui scrollbar handleColor must be a color');
9961
+ ASSERT(isNumber(value), 'ui slider value must be a number');
9962
+ ASSERT(isString(text), 'ui slider must be a string');
9963
+ ASSERT(isColor(color), 'ui slider color must be a color');
9964
+ ASSERT(isColor(handleColor), 'ui slider handleColor must be a color');
9912
9965
 
9913
- /** @property {number} - Current percentage value of this scrollbar 0-1 */
9966
+ /** @property {number} - Current percentage value of this slider 0-1 */
9914
9967
  this.value = value;
9915
- /** @property {Color} - Color for the handle part of the scrollbar */
9968
+ /** @property {Color} - Color for the handle part of the slider */
9916
9969
  this.handleColor = handleColor.copy();
9917
9970
  /** @property {boolean} - Should it fill up like a progress bar? */
9918
9971
  this.fillMode = false;
@@ -9931,7 +9984,7 @@ class UIScrollbar extends UIObject
9931
9984
  const oldValue = this.value;
9932
9985
  if (this.isActiveObject())
9933
9986
  {
9934
- // handle horizontal or vertical scrollbar
9987
+ // handle horizontal or vertical slider
9935
9988
  const isHorizontal = this.size.x > this.size.y;
9936
9989
  const handleSize = isHorizontal ? this.size.y : this.size.x;
9937
9990
  const barSize = isHorizontal ? this.size.x : this.size.y;
@@ -9959,7 +10012,7 @@ class UIScrollbar extends UIObject
9959
10012
  {
9960
10013
  super.render();
9961
10014
 
9962
- // handle horizontal or vertical scrollbar
10015
+ // handle horizontal or vertical slider
9963
10016
  const isHorizontal = this.size.x > this.size.y;
9964
10017
  const barWidth = isHorizontal ? this.size.x : this.size.y;
9965
10018
  const handleWidth = isHorizontal ? this.size.y : this.size.x;
@@ -9977,7 +10030,7 @@ class UIScrollbar extends UIObject
9977
10030
  }
9978
10031
  else
9979
10032
  {
9980
- // draw the scrollbar handle
10033
+ // draw the slider handle
9981
10034
  const value = clamp(isHorizontal ? this.value : 1 - this.value);
9982
10035
  const p = (barWidth - handleWidth) * (value - .5);
9983
10036
  const pos = this.pos.add(isHorizontal ? vec2(p, 0) : vec2(0, p));
@@ -9986,7 +10039,7 @@ class UIScrollbar extends UIObject
9986
10039
  uiSystem.drawRect(pos, drawSize, color, this.lineWidth, this.lineColor, this.cornerRadius, this.gradientColor);
9987
10040
  }
9988
10041
 
9989
- // draw the text scaled to fit on the scrollbar
10042
+ // draw the text scaled to fit on the slider
9990
10043
  const textSize = this.getTextSize();
9991
10044
  uiSystem.drawText(this.text, this.pos, textSize,
9992
10045
  this.textColor, this.textLineWidth, this.textLineColor, this.align, this.font, this.fontStyle, true, this.textShadow);
@@ -10261,7 +10314,7 @@ class Box2dObject extends EngineObject
10261
10314
  // draw non-edge fixtures
10262
10315
  this.getFixtureList().forEach((fixture)=>
10263
10316
  {
10264
- const shape = box2d.castObjectType(fixture.GetShape());
10317
+ const shape = box2d.castShapeObject(fixture.GetShape());
10265
10318
  if (shape.GetType() !== box2d.instance.b2Shape.e_edge)
10266
10319
  {
10267
10320
  box2d.drawFixture(fixture, this.pos, this.angle, color, lineColor, lineWidth, useWebGL, context);
@@ -10901,7 +10954,7 @@ class Box2dJoint
10901
10954
  constructor(jointDef)
10902
10955
  {
10903
10956
  /** @property {Object} - The Box2d joint */
10904
- this.box2dJoint = box2d.castObjectType(box2d.world.CreateJoint(jointDef));
10957
+ this.box2dJoint = box2d.castJointObject(box2d.world.CreateJoint(jointDef));
10905
10958
  }
10906
10959
 
10907
10960
  /** Destroy this joint */
@@ -11186,7 +11239,7 @@ class Box2dRevoluteJoint extends Box2dJoint
11186
11239
 
11187
11240
  /** Enable/disable the joint limit
11188
11241
  * @param {boolean} [enable] */
11189
- enableLimit(enable=true) { return this.box2dJoint.enableLimit(enable); }
11242
+ enableLimit(enable=true) { return this.box2dJoint.EnableLimit(enable); }
11190
11243
 
11191
11244
  /** Get the lower joint limit
11192
11245
  * @return {number} */
@@ -11344,7 +11397,7 @@ class Box2dPrismaticJoint extends Box2dJoint
11344
11397
 
11345
11398
  /** Enable/disable the joint limit
11346
11399
  * @param {boolean} [enable] */
11347
- enableLimit(enable=true) { return this.box2dJoint.enableLimit(enable); }
11400
+ enableLimit(enable=true) { return this.box2dJoint.EnableLimit(enable); }
11348
11401
 
11349
11402
  /** Get the lower joint limit
11350
11403
  * @return {number} */
@@ -11958,7 +12011,7 @@ class Box2dPlugin
11958
12011
  * @param {CanvasRenderingContext2D} [context] */
11959
12012
  drawFixture(fixture, pos, angle, color=WHITE, lineColor=BLACK, lineWidth=.1, useWebgl, context)
11960
12013
  {
11961
- const shape = box2d.castObjectType(fixture.GetShape());
12014
+ const shape = box2d.castShapeObject(fixture.GetShape());
11962
12015
  switch (shape.GetType())
11963
12016
  {
11964
12017
  case box2d.instance.b2Shape.e_polygon:
@@ -12016,9 +12069,9 @@ class Box2dPlugin
12016
12069
  * @param {Object} o */
12017
12070
  isNull(o) { return !box2d.instance.getPointer(o); }
12018
12071
 
12019
- /** casts a box2d object to its correct type
12072
+ /** casts a box2d object to a shape type
12020
12073
  * @param {Object} o */
12021
- castObjectType(o)
12074
+ castShapeObject(o)
12022
12075
  {
12023
12076
  switch (o.GetType())
12024
12077
  {
@@ -12030,6 +12083,17 @@ class Box2dPlugin
12030
12083
  return box2d.instance.castObject(o, box2d.instance.b2PolygonShape);
12031
12084
  case box2d.instance.b2Shape.e_chain:
12032
12085
  return box2d.instance.castObject(o, box2d.instance.b2ChainShape);
12086
+ }
12087
+
12088
+ ASSERT(false, 'Unknown box2d object type');
12089
+ }
12090
+
12091
+ /** casts a box2d object to a joint type
12092
+ * @param {Object} o */
12093
+ castJointObject(o)
12094
+ {
12095
+ switch (o.GetType())
12096
+ {
12033
12097
  case box2d.instance.e_revoluteJoint:
12034
12098
  return box2d.instance.castObject(o, box2d.instance.b2RevoluteJoint);
12035
12099
  case box2d.instance.e_prismaticJoint:
@@ -24,7 +24,7 @@ LJS.setTilesPixelated(false);
24
24
  ///////////////////////////////////////////////////////////////////////////////
25
25
  // game variables
26
26
 
27
- const maxScenes = 11;
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), maxScenes));
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
 
@@ -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
  }
@@ -1,5 +1,5 @@
1
1
  <!DOCTYPE html><meta charset=utf-8><body>
2
- <script src=../../dist/littlejs.js?1.17.15></script>
2
+ <script src=../../dist/littlejs.js?1.18.1></script>
3
3
  <script src=../../dist/box2d.wasm.js></script>
4
4
 
5
5
  <!-- LittleJS Engine Source
@@ -20,7 +20,7 @@ function gameInit()
20
20
  musicPlayer.addChild(infoText);
21
21
 
22
22
  // volume slider
23
- const volumeSlider = new UIScrollbar(vec2(0, -20), vec2(400, 30),
23
+ const volumeSlider = new UISlider(vec2(0, -20), vec2(400, 30),
24
24
  musicVolume, 'Music Volume');
25
25
  volumeSlider.fillMode = true;
26
26
  musicPlayer.addChild(volumeSlider);
@@ -26,7 +26,7 @@ function gameInit()
26
26
  musicPlayer.addChild(dropZoneText);
27
27
 
28
28
  // volume slider
29
- const volumeSlider = new UIScrollbar(vec2(0, -20), vec2(400, 30),
29
+ const volumeSlider = new UISlider(vec2(0, -20), vec2(400, 30),
30
30
  musicVolume, 'Music Volume');
31
31
  volumeSlider.fillMode = true;
32
32
  musicPlayer.addChild(volumeSlider);
@@ -58,8 +58,8 @@ function gameInit()
58
58
  stopButton.onClick = ()=> musicInstance?.stop();
59
59
  musicPlayer.addChild(stopButton);
60
60
 
61
- // progress bar and scrollbar for seeking
62
- progressBar = new UIScrollbar(vec2(0, 120), vec2(400, 30), 0);
61
+ // progress bar and slider for seeking
62
+ progressBar = new UISlider(vec2(0, 120), vec2(400, 30), 0);
63
63
  progressBar.disabledColor = RED;
64
64
  progressBar.onChange = ()=>
65
65
  {
@@ -91,7 +91,7 @@ function gameInit()
91
91
  // create tempo slider
92
92
  const minTempo = 120, maxTempo = 480;
93
93
  const tempoPercent = percent(tempo, minTempo, maxTempo);
94
- const tempoSlider = new UIScrollbar(vec2(380,500), vec2(340,40), tempoPercent);
94
+ const tempoSlider = new UISlider(vec2(380,500), vec2(340,40), tempoPercent);
95
95
  tempoSlider.onChange = ()=>
96
96
  {
97
97
  tempo = lerp(minTempo, maxTempo, tempoSlider.value);
@@ -30,7 +30,7 @@ function gameInit()
30
30
  }
31
31
 
32
32
  // create non-interactive slider to display timer
33
- timerSlider = new UIScrollbar(vec2(0, 100), vec2(400, 50));
33
+ timerSlider = new UISlider(vec2(0, 100), vec2(400, 50));
34
34
  timerSlider.interactive = false;
35
35
  timerSlider.update = ()=>
36
36
  {
@@ -33,12 +33,12 @@ function gameInit()
33
33
  uiMenu.addChild(textInput);
34
34
  textInput.onChange = ()=> canvasClearColor = randColor();
35
35
 
36
- // example scrollbar
37
- const scrollbar = new UIScrollbar(vec2(0,90), vec2(400, 50),
36
+ // example slider
37
+ const slider = new UISlider(vec2(0,90), vec2(400, 50),
38
38
  soundVolume, 'Volume');
39
- scrollbar.navigationIndex = ++navigationIndex;
40
- uiMenu.addChild(scrollbar);
41
- scrollbar.onChange = ()=> setSoundVolume(scrollbar.value);
39
+ slider.navigationIndex = ++navigationIndex;
40
+ uiMenu.addChild(slider);
41
+ slider.onChange = ()=> setSoundVolume(slider.value);
42
42
 
43
43
  // exit button
44
44
  const button1 = new UIButton(vec2(0,170), vec2(200, 50), 'Exit Menu');
@@ -7,7 +7,7 @@
7
7
  </head><body>
8
8
 
9
9
  <!-- LittleJS Engine -->
10
- <script src=../../dist/littlejs.js?1.17.15></script>
10
+ <script src=../../dist/littlejs.js?1.18.1></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.17.15></script>
35
+ <script src=game.js?1.18.1></script>
@@ -69,7 +69,7 @@ function gameUpdate()
69
69
 
70
70
  // right click = drop test object
71
71
  if (LJS.mouseIsDown(2))
72
- new TestObject(LJS.mousePos);
72
+ new TestObject(LJS.mousePos.add(LJS.randInCircle()));
73
73
 
74
74
  // mouse hold = add objects
75
75
  if (LJS.mouseIsDown(0))
@@ -67,12 +67,12 @@ function createUI()
67
67
  checkbox.navigationIndex = ++navigationIndex;
68
68
  checkbox.text = 'Test Checkbox';
69
69
 
70
- // example scrollbar
71
- const scrollbar = new LJS.UIScrollbar(vec2(0,60), vec2(350, 50));
72
- uiMenu.addChild(scrollbar);
73
- scrollbar.onChange = ()=> scrollbar.text = scrollbar.value.toFixed(2)
74
- scrollbar.onChange();
75
- scrollbar.navigationIndex = ++navigationIndex;
70
+ // example slider
71
+ const slider = new LJS.UISlider(vec2(0,60), vec2(350, 50));
72
+ uiMenu.addChild(slider);
73
+ slider.onChange = ()=> slider.text = slider.value.toFixed(2)
74
+ slider.onChange();
75
+ slider.navigationIndex = ++navigationIndex;
76
76
 
77
77
  // example button
78
78
  const button1 = new LJS.UIButton(vec2(0,140), vec2(350, 50), 'Test Button');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "littlejsengine",
3
- "version": "1.17.15",
3
+ "version": "1.18.1",
4
4
  "description": "LittleJS - Tiny and Fast HTML5 Game Engine",
5
5
  "main": "dist/littlejs.esm.js",
6
6
  "types": "dist/littlejs.d.ts",