littlejsengine 1.14.7 → 1.14.8

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.js CHANGED
@@ -33,7 +33,7 @@ const engineName = 'LittleJS';
33
33
  * @type {string}
34
34
  * @default
35
35
  * @memberof Engine */
36
- const engineVersion = '1.14.7';
36
+ const engineVersion = '1.14.8';
37
37
 
38
38
  /** Frames per second to update
39
39
  * @type {number}
@@ -1418,8 +1418,11 @@ function nearestPowerOfTwo(value) { return 2**Math.ceil(Math.log2(value)); }
1418
1418
  * @memberof Utilities */
1419
1419
  function isOverlapping(posA, sizeA, posB, sizeB=vec2())
1420
1420
  {
1421
- return abs(posA.x - posB.x)*2 < sizeA.x + sizeB.x
1422
- && abs(posA.y - posB.y)*2 < sizeA.y + sizeB.y;
1421
+ const dx = (posA.x - posB.x)*2;
1422
+ const dy = (posA.y - posB.y)*2;
1423
+ const sx = sizeA.x + sizeB.x;
1424
+ const sy = sizeA.y + sizeB.y;
1425
+ return dx >= -sx && dx < sx && dy >= -sy && dy < sy;
1423
1426
  }
1424
1427
 
1425
1428
  /** Returns true if a line segment is intersecting an axis aligned box
@@ -2650,7 +2653,7 @@ function setGLEnable(enable)
2650
2653
  glCanvas.style.visibility = enable ? 'visible' : 'hidden';
2651
2654
  }
2652
2655
 
2653
- /** Set how many sided polygons to use when drawing circles and elipses with WebGL
2656
+ /** Set how many sided polygons to use when drawing circles and ellipses with WebGL
2654
2657
  * @param {number} sides
2655
2658
  * @memberof Settings */
2656
2659
  function setGLCircleSides(sides) { glCircleSides = sides; }
@@ -2861,7 +2864,7 @@ class EngineObject
2861
2864
  /** @property {Vector2} - World space position of the object */
2862
2865
  this.pos = pos.copy();
2863
2866
  /** @property {Vector2} - World space width and height of the object */
2864
- this.size = size;
2867
+ this.size = size.copy();
2865
2868
  /** @property {Vector2} - Size of object used for drawing, uses size if not set */
2866
2869
  this.drawSize = undefined;
2867
2870
  /** @property {TileInfo} - Tile info to render object (undefined is untextured) */
@@ -2869,7 +2872,7 @@ class EngineObject
2869
2872
  /** @property {number} - Angle to rotate the object */
2870
2873
  this.angle = angle;
2871
2874
  /** @property {Color} - Color to apply when rendered */
2872
- this.color = color;
2875
+ this.color = color.copy();
2873
2876
  /** @property {Color} - Additive color to apply when rendered */
2874
2877
  this.additiveColor = undefined;
2875
2878
  /** @property {boolean} - Should it flip along y axis when rendered */
@@ -2993,7 +2996,7 @@ class EngineObject
2993
2996
  for (const o of engineObjectsCollide)
2994
2997
  {
2995
2998
  // non solid objects don't collide with each other
2996
- if (!this.isSolid && !o.isSolid || o.destroyed || o.parent || o === this)
2999
+ if ((!this.isSolid && !o.isSolid) || o.destroyed || o.parent || o === this)
2997
3000
  continue;
2998
3001
 
2999
3002
  // check collision
@@ -3032,7 +3035,7 @@ class EngineObject
3032
3035
  {
3033
3036
  // push outside object collision
3034
3037
  this.pos.y = o.pos.y + (sizeBoth.y/2 + epsilon) * sign(oldPos.y - o.pos.y);
3035
- if (o.groundObject && wasMovingDown || !o.mass)
3038
+ if ((o.groundObject && wasMovingDown) || !o.mass)
3036
3039
  {
3037
3040
  // set ground object if landed on something
3038
3041
  if (wasMovingDown)
@@ -4183,7 +4186,7 @@ class FontImage
4183
4186
  * @param {Vector2} pos
4184
4187
  * @param {number} [scale=.25]
4185
4188
  * @param {boolean} [center]
4186
- * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D}[context=drawContext]
4189
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
4187
4190
  */
4188
4191
  drawText(text, pos, scale=1, center, context=drawContext)
4189
4192
  {
@@ -4450,6 +4453,7 @@ function inputInit()
4450
4453
  document.addEventListener('wheel', onMouseWheel);
4451
4454
  document.addEventListener('contextmenu', onContextMenu);
4452
4455
  document.addEventListener('blur', onBlur);
4456
+ document.addEventListener('mouseleave', onMouseLeave);
4453
4457
 
4454
4458
  // init touch input
4455
4459
  if (isTouchDevice && touchInputEnable)
@@ -4508,6 +4512,12 @@ function inputInit()
4508
4512
  function onMouseWheel(e) { mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY); }
4509
4513
  function onContextMenu(e) { e.preventDefault(); } // prevent right click menu
4510
4514
  function onBlur() { inputClear(); } // reset input when focus is lost
4515
+ function onMouseLeave()
4516
+ {
4517
+ // set mouse position and delta when leaving canvas
4518
+ mousePosScreen = vec2(Infinity);
4519
+ mouseDeltaScreen = vec2(0);
4520
+ }
4511
4521
  }
4512
4522
 
4513
4523
  // convert a mouse or touch event position to screen space
@@ -5629,7 +5639,7 @@ class TileLayerData
5629
5639
  /** @property {boolean} - If the tile should be mirrored along the x axis */
5630
5640
  this.mirror = mirror;
5631
5641
  /** @property {Color} - Color of the tile */
5632
- this.color = color;
5642
+ this.color = color.copy();
5633
5643
  }
5634
5644
 
5635
5645
  /** Set this tile to clear, it will not be rendered */
@@ -5728,7 +5738,7 @@ class CanvasLayer extends EngineObject
5728
5738
  * @param {TileInfo} [tileInfo]
5729
5739
  * @param {Color} [color=(1,1,1,1)]
5730
5740
  * @param {number} [angle=0]
5731
- * @param {boolean} [mirror=0] */
5741
+ * @param {boolean} [mirror=false] */
5732
5742
  drawTile(pos, size=vec2(1), tileInfo, color=new Color, angle, mirror)
5733
5743
  {
5734
5744
  this.drawCanvas2D(pos, size, angle, mirror, (context)=>
@@ -7326,7 +7336,7 @@ function glPolyStrip(points)
7326
7336
  while (indices.length > 3 && attempts++ < maxAttempts)
7327
7337
  {
7328
7338
  let foundEar = false;
7329
- for (let i = indices.length; --i;)
7339
+ for (let i = 0; i < indices.length; i++)
7330
7340
  {
7331
7341
  const i0 = indices[(i + indices.length - 1) % indices.length];
7332
7342
  const i1 = indices[i];
@@ -7363,7 +7373,7 @@ function glPolyStrip(points)
7363
7373
  if (!foundEar)
7364
7374
  {
7365
7375
  let worstIndex = -1, worstValue = Infinity;
7366
- for (let i = indices.length; --i;)
7376
+ for (let i = 0; i < indices.length; i++)
7367
7377
  {
7368
7378
  const i0 = indices[(i + indices.length - 1) % indices.length];
7369
7379
  const i1 = indices[i];
@@ -7926,6 +7936,10 @@ class UISystemPlugin
7926
7936
  this.uiObjects = [];
7927
7937
  /** @property {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} - Context to render UI elements to */
7928
7938
  this.uiContext = context;
7939
+ /** @property {UIObject} - Top most object user is over */
7940
+ this.hoverObject = undefined;
7941
+ /** @property {UIObject} - Object user is currently interacting with */
7942
+ this.activeObject = undefined;
7929
7943
 
7930
7944
  engineAddPlugin(uiUpdate, uiRender);
7931
7945
 
@@ -7955,6 +7969,8 @@ class UISystemPlugin
7955
7969
  else
7956
7970
  updateInvisibleObject(o);
7957
7971
  }
7972
+ // reset hover object at start of update
7973
+ uiSystem.hoverObject = undefined;
7958
7974
  for (let i = uiSystem.uiObjects.length; i--;)
7959
7975
  {
7960
7976
  const o = uiSystem.uiObjects[i];
@@ -8066,6 +8082,8 @@ class UIObject
8066
8082
  this.size = size.copy();
8067
8083
  /** @property {Color} - Color of the object */
8068
8084
  this.color = uiSystem.defaultColor;
8085
+ /** @property {Color} - Color of the object when active, uses color if undefined */
8086
+ this.activeColor = undefined;
8069
8087
  /** @property {string} - Text for this ui object */
8070
8088
  this.text = undefined;
8071
8089
  /** @property {Color} - Color when disabled */
@@ -8096,7 +8114,7 @@ class UIObject
8096
8114
  this.children = [];
8097
8115
  /** @property {UIObject} - This object's parent, position is in parent space */
8098
8116
  this.parent = undefined;
8099
- /** @property {number} - Extra size added to make small buttons easier to touch on mobile devices */
8117
+ /** @property {number} - Added size to make small buttons easier to touch on mobile devices */
8100
8118
  this.extraTouchSize = 0;
8101
8119
  /** @property {Sound} - Sound when interactive element is pressed */
8102
8120
  this.soundPress = uiSystem.defaultSoundPress;
@@ -8104,12 +8122,10 @@ class UIObject
8104
8122
  this.soundRelease = uiSystem.defaultSoundRelease;
8105
8123
  /** @property {Sound} - Sound when interactive element is clicked */
8106
8124
  this.soundClick = uiSystem.defaultSoundClick;
8107
- /** @property {boolean} - Is the mouse over this element */
8108
- this.mouseIsOver = false;
8109
8125
  /** @property {boolean} - Is this element interactive */
8110
8126
  this.interactive = false;
8111
- /** @property {boolean} - Is the mouse held on this element (was pressed while over and hasn't been released) */
8112
- this.mouseIsHeld = false;
8127
+ /** @property {boolean} - Activate when dragged over with mouse held down */
8128
+ this.dragActivate = false;
8113
8129
  uiSystem.uiObjects.push(this);
8114
8130
  }
8115
8131
 
@@ -8136,15 +8152,18 @@ class UIObject
8136
8152
  /** Update the object, called automatically by plugin once each frame */
8137
8153
  update()
8138
8154
  {
8139
- const mouseWasOver = this.mouseIsOver;
8140
- const mousePress = mouseWasPressed(0);
8155
+ const wasHover = this.isHoverObject();
8156
+ const isActive = this.isActiveObject();
8141
8157
  const mouseDown = mouseIsDown(0);
8142
- if (mousePress || !mouseDown || this.mouseIsHeld)
8158
+ const mousePress = this.dragActivate ? mouseDown : mouseWasPressed(0);
8159
+ if (!uiSystem.hoverObject)
8160
+ if (mousePress || !mouseDown || isActive)
8143
8161
  {
8144
8162
  const size = this.size.add(vec2(isTouchDevice && this.extraTouchSize || 0));
8145
- this.mouseIsOver = isOverlapping(this.pos, size, mousePosScreen);
8163
+ if (isOverlapping(this.pos, size, mousePosScreen))
8164
+ uiSystem.hoverObject = this;
8146
8165
  }
8147
- if (this.mouseIsOver)
8166
+ if (this.isHoverObject())
8148
8167
  {
8149
8168
  if (mousePress)
8150
8169
  inputClearKey(0,0,0,1,0); // clear mouse was pressed state
@@ -8157,10 +8176,12 @@ class UIObject
8157
8176
  this.onPress();
8158
8177
  if (this.soundPress)
8159
8178
  this.soundPress.play();
8179
+ if (uiSystem.activeObject && !isActive)
8180
+ uiSystem.activeObject.onRelease();
8181
+ uiSystem.activeObject = this;
8160
8182
  }
8161
- this.mouseIsHeld = true;
8162
8183
  }
8163
- if (!mouseDown && this.mouseIsHeld && this.interactive)
8184
+ if (!mouseDown && uiSystem.activeObject === this && this.interactive)
8164
8185
  {
8165
8186
  this.onClick();
8166
8187
  if (this.soundClick)
@@ -8168,35 +8189,34 @@ class UIObject
8168
8189
  }
8169
8190
  }
8170
8191
  }
8171
- if (!mouseDown)
8192
+ if (isActive && (!mouseDown || !this.isHoverObject()))
8172
8193
  {
8173
- if (this.mouseIsHeld && this.interactive)
8174
- {
8175
- this.onRelease();
8176
- if (this.soundRelease)
8177
- this.soundRelease.play();
8178
- }
8179
- if (isTouchDevice)
8180
- this.mouseIsOver = false;
8181
- this.mouseIsHeld = false;
8194
+ this.onRelease();
8195
+ if (this.soundRelease)
8196
+ this.soundRelease.play();
8197
+ uiSystem.activeObject = undefined;
8182
8198
  }
8183
8199
 
8184
- if (this.mouseIsOver !== mouseWasOver)
8185
- this.mouseIsOver ? this.onEnter() : this.onLeave();
8200
+ if (this.isHoverObject() !== wasHover)
8201
+ this.isHoverObject() ? this.onEnter() : this.onLeave();
8186
8202
  }
8187
8203
 
8188
8204
  /** Render the object, called automatically by plugin once each frame */
8189
8205
  render()
8190
8206
  {
8191
- if (this.size.x && this.size.y)
8192
- uiSystem.drawRect(this.pos, this.size, this.color, this.lineWidth, this.lineColor, this.cornerRadius);
8207
+ if (!this.size.x || !this.size.y) return;
8208
+
8209
+ const lineColor = this.interactive && this.isActiveObject() && !this.disabled ? this.color : this.lineColor;
8210
+ const color = this.interactive ? this.disabled ? this.disabledColor : this.isActiveObject() ? this.activeColor || this.color : this.isHoverObject() ? this.hoverColor : this.color : this.color;
8211
+ uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor, this.cornerRadius);
8193
8212
  }
8194
8213
 
8195
8214
  /** Special update when object is not visible */
8196
8215
  updateInvisible()
8197
8216
  {
8198
8217
  // reset input state when not visible
8199
- this.mouseIsOver = this.mouseIsHeld = false;
8218
+ if (this.isActiveObject())
8219
+ uiSystem.activeObject = undefined;
8200
8220
  }
8201
8221
 
8202
8222
  /** Get the size for text with overrides and scale
@@ -8209,6 +8229,12 @@ class UIObject
8209
8229
  this.textHeight || this.textScale * this.size.y);
8210
8230
  }
8211
8231
 
8232
+ /** @return {boolean} - Is the mouse hovering over this element */
8233
+ isHoverObject() { return uiSystem.hoverObject === this; }
8234
+
8235
+ /** @return {boolean} - Is the mouse held onto this element */
8236
+ isActiveObject() { return uiSystem.activeObject === this; }
8237
+
8212
8238
  /** Called when the mouse enters the object */
8213
8239
  onEnter() {}
8214
8240
 
@@ -8319,10 +8345,7 @@ class UIButton extends UIObject
8319
8345
  }
8320
8346
  render()
8321
8347
  {
8322
- // draw the button
8323
- const lineColor = this.mouseIsHeld && !this.disabled ? this.color : this.lineColor;
8324
- const color = this.disabled ? this.disabledColor : this.mouseIsOver ? this.hoverColor : this.color;
8325
- uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor, this.cornerRadius);
8348
+ super.render();
8326
8349
 
8327
8350
  // draw the text scaled to fit
8328
8351
  const textSize = this.getTextSize();
@@ -8363,8 +8386,7 @@ class UICheckbox extends UIObject
8363
8386
  }
8364
8387
  render()
8365
8388
  {
8366
- const color = this.disabled ? this.disabledColor : this.mouseIsOver ? this.hoverColor : this.color;
8367
- uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, this.lineColor, this.cornerRadius);
8389
+ super.render();
8368
8390
  if (this.checked)
8369
8391
  {
8370
8392
  const p = this.cornerRadius / min(this.size.x, this.size.y) * 2;
@@ -8414,7 +8436,7 @@ class UIScrollbar extends UIObject
8414
8436
  update()
8415
8437
  {
8416
8438
  super.update();
8417
- if (this.mouseIsHeld && this.interactive)
8439
+ if (this.isActiveObject() && this.interactive)
8418
8440
  {
8419
8441
  // check if value changed
8420
8442
  const handleSize = vec2(this.size.y);
@@ -8428,12 +8450,7 @@ class UIScrollbar extends UIObject
8428
8450
  }
8429
8451
  render()
8430
8452
  {
8431
- // draw the scrollbar background
8432
- const lineColor = this.interactive && this.mouseIsHeld && !this.disabled ?
8433
- this.color : this.lineColor;
8434
- const color = this.disabled ? this.disabledColor :
8435
- this.interactive && this.mouseIsHeld ? this.hoverColor : this.color;
8436
- uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor, this.cornerRadius);
8453
+ super.render();
8437
8454
 
8438
8455
  // draw the scrollbar handle
8439
8456
  const handleSize = vec2(this.size.y);
@@ -8442,7 +8459,7 @@ class UIScrollbar extends UIObject
8442
8459
  const p2 = this.pos.x + handleWidth/2;
8443
8460
  const handlePos = vec2(lerp(p1, p2, this.value), this.pos.y);
8444
8461
  const handleColor = this.disabled ? this.disabledColor :
8445
- this.interactive && this.mouseIsHeld ? this.color : this.handleColor;
8462
+ this.interactive && this.isActiveObject() ? this.color : this.handleColor;
8446
8463
  uiSystem.drawRect(handlePos, handleSize, handleColor, this.lineWidth, this.lineColor, this.cornerRadius);
8447
8464
 
8448
8465
  // draw the text scaled to fit on the scrollbar