littlejsengine 1.14.28 → 1.15.3

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/src/engineDraw.js CHANGED
@@ -250,15 +250,11 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
250
250
 
251
251
  const textureInfo = tileInfo && tileInfo.textureInfo;
252
252
  const bleedScale = tileInfo ? tileInfo.bleedScale : 0;
253
- if (useWebGL)
253
+ if (useWebGL && glEnable)
254
254
  {
255
255
  ASSERT(!!glContext, 'WebGL is not enabled!');
256
256
  if (screenSpace)
257
- {
258
- // convert to world space
259
- pos = screenToWorld(pos);
260
- size = size.scale(1/cameraScale);
261
- }
257
+ [pos, size, angle] = screenToWorldTransform(pos, size, angle);
262
258
  if (textureInfo)
263
259
  {
264
260
  // calculate uvs and render
@@ -294,7 +290,7 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
294
290
  {
295
291
  // normal canvas 2D rendering method (slower)
296
292
  ++drawCount;
297
- size = new Vector2(size.x, -size.y); // fix upside down sprites
293
+ size = new Vector2(size.x, -size.y); // flip upside down sprites
298
294
  drawCanvas2D(pos, size, angle, mirror, (context)=>
299
295
  {
300
296
  if (textureInfo)
@@ -346,7 +342,8 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
346
342
  ASSERT(isColor(colorTop) && isColor(colorBottom), 'color is invalid');
347
343
  ASSERT(isNumber(angle), 'angle must be a number');
348
344
  ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
349
- if (useWebGL)
345
+
346
+ if (useWebGL && glEnable)
350
347
  {
351
348
  ASSERT(!!glContext, 'WebGL is not enabled!');
352
349
  if (screenSpace)
@@ -354,6 +351,7 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
354
351
  // convert to world space
355
352
  pos = screenToWorld(pos);
356
353
  size = size.scale(1/cameraScale);
354
+ angle += cameraAngle;
357
355
  }
358
356
  // build 4 corner points for the rectangle
359
357
  const points = [], colors = [];
@@ -409,17 +407,14 @@ function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0,
409
407
  ASSERT(isVector2(pos), 'pos must be a vec2');
410
408
  ASSERT(isNumber(angle), 'angle must be a number');
411
409
  ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
412
- if (useWebGL)
410
+
411
+ if (useWebGL && glEnable)
413
412
  {
414
413
  ASSERT(!!glContext, 'WebGL is not enabled!');
415
- let scale = 1;
414
+ let size = vec2(1);
416
415
  if (screenSpace)
417
- {
418
- // convert to world space
419
- pos = screenToWorld(pos);
420
- scale = 1/cameraScale;
421
- }
422
- glDrawOutlineTransform(points, color.rgbaInt(), width, pos.x, pos.y, scale, scale, angle, wrap);
416
+ [pos, size, angle] = screenToWorldTransform(pos, size, angle);
417
+ glDrawOutlineTransform(points, color.rgbaInt(), width, pos.x, pos.y, size.x, size.y, angle, wrap);
423
418
  }
424
419
  else
425
420
  {
@@ -515,19 +510,15 @@ function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(),
515
510
  ASSERT(isNumber(angle), 'angle must be a number');
516
511
  ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
517
512
 
518
- if (useWebGL)
513
+ if (useWebGL && glEnable)
519
514
  {
520
515
  ASSERT(!!glContext, 'WebGL is not enabled!');
521
- let scale = 1;
516
+ let size = vec2(1);
522
517
  if (screenSpace)
523
- {
524
- // convert to world space
525
- pos = screenToWorld(pos);
526
- scale = 1/cameraScale;
527
- }
528
- glDrawPointsTransform(points, color.rgbaInt(), pos.x, pos.y, scale, scale, angle);
518
+ [pos, size, angle] = screenToWorldTransform(pos, size, angle);
519
+ glDrawPointsTransform(points, color.rgbaInt(), pos.x, pos.y, size.x, size.y, angle);
529
520
  if (lineWidth > 0)
530
- glDrawOutlineTransform(points, lineColor.rgbaInt(), lineWidth, pos.x, pos.y, scale, scale, angle);
521
+ glDrawOutlineTransform(points, lineColor.rgbaInt(), lineWidth, pos.x, pos.y, size.x, size.y, angle);
531
522
  }
532
523
  else
533
524
  {
@@ -570,7 +561,7 @@ function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineC
570
561
  ASSERT(lineWidth >= 0 && lineWidth < size.x && lineWidth < size.y, 'invalid lineWidth');
571
562
  ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
572
563
 
573
- if (useWebGL)
564
+ if (useWebGL && glEnable)
574
565
  {
575
566
  // draw as a regular polygon
576
567
  const sides = glCircleSides;
@@ -633,14 +624,10 @@ function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpac
633
624
  ASSERT(typeof drawFunction === 'function', 'drawFunction must be a function');
634
625
 
635
626
  if (!screenSpace)
636
- {
637
- // transform from world space to screen space
638
- pos = worldToScreen(pos);
639
- size = size.scale(cameraScale);
640
- }
627
+ [pos, size, angle] = worldToScreenTransform(pos, size, angle);
641
628
  context.save();
642
629
  context.translate(pos.x+.5, pos.y+.5);
643
- context.rotate(angle-cameraAngle);
630
+ context.rotate(angle);
644
631
  context.scale(mirror ? -size.x : size.x, -size.y);
645
632
  drawFunction(context);
646
633
  context.restore();
@@ -666,7 +653,14 @@ function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpac
666
653
  * @memberof Draw */
667
654
  function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth, angle=0, context=drawContext)
668
655
  {
669
- drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, fontStyle, maxWidth, angle, context);
656
+ // convert to screen space
657
+ pos = worldToScreen(pos);
658
+ size *= cameraScale;
659
+ lineWidth *= cameraScale;
660
+ angle -= cameraAngle;
661
+ angle *= -1;
662
+
663
+ drawTextScreen(text, pos, size, color, lineWidth, lineColor, textAlign, font, fontStyle, maxWidth, angle, context);
670
664
  }
671
665
 
672
666
  /** Draw text on overlay canvas in world space
@@ -825,6 +819,36 @@ function worldToScreenDelta(worldDelta)
825
819
  return new Vector2(x * cameraScale, y * -cameraScale);
826
820
  }
827
821
 
822
+ /** Convert screen space transform to world space
823
+ * @param {Vector2} screenPos
824
+ * @param {Vector2} screenSize
825
+ * @param {number} [screenAngle]
826
+ * @return {[Vector2, Vector2, number]} - [pos, size, angle]
827
+ * @memberof Draw */
828
+ function screenToWorldTransform(screenPos, screenSize, screenAngle=0)
829
+ {
830
+ return [
831
+ screenToWorld(screenPos),
832
+ screenSize.scale(1/cameraScale),
833
+ screenAngle + cameraAngle
834
+ ];
835
+ }
836
+
837
+ /** Convert world space transform to screen space
838
+ * @param {Vector2} worldPos
839
+ * @param {Vector2} worldSize
840
+ * @param {number} [worldAngle]
841
+ * @return {[Vector2, Vector2, number]} - [pos, size, angle]
842
+ * @memberof Draw */
843
+ function worldToScreenTransform(worldPos, worldSize, worldAngle=0)
844
+ {
845
+ return [
846
+ worldToScreen(worldPos),
847
+ worldSize.scale(cameraScale),
848
+ worldAngle - cameraAngle
849
+ ];
850
+ }
851
+
828
852
  /** Get the camera's visible area in world space
829
853
  * @return {Vector2}
830
854
  * @memberof Draw */
@@ -184,7 +184,7 @@ function inputUpdate()
184
184
  if (headlessMode) return;
185
185
 
186
186
  // clear input when lost focus (prevent stuck keys)
187
- if(!(touchInputEnable && isTouchDevice) && !document.hasFocus())
187
+ if (!(touchInputEnable && isTouchDevice) && !document.hasFocus())
188
188
  inputClear();
189
189
 
190
190
  // update mouse world space position and delta
@@ -448,7 +448,9 @@ class EngineObject
448
448
  {
449
449
  ASSERT(child.parent === this && this.children.includes(child));
450
450
  ASSERT(child instanceof EngineObject, 'child must be an EngineObject');
451
- this.children.splice(this.children.indexOf(child), 1);
451
+ const index = this.children.indexOf(child);
452
+ ASSERT(index >= 0, 'child not found in children array');
453
+ index >= 0 && this.children.splice(index, 1);
452
454
  child.parent = 0;
453
455
  }
454
456
 
@@ -160,6 +160,9 @@ class ParticleEmitter extends EngineObject
160
160
  this.previousPos = this.pos.copy();
161
161
  }
162
162
 
163
+ /** Emitters do not have physics */
164
+ updatePhysics() {}
165
+
163
166
  /** Update the emitter to spawn particles, called automatically by engine once each frame */
164
167
  update()
165
168
  {
@@ -553,7 +553,7 @@ class TileCollisionLayer extends TileLayer
553
553
  // remove from collision layers array and destroy
554
554
  const index = tileCollisionLayers.indexOf(this);
555
555
  ASSERT(index >= 0, 'tile collision layer not found in array');
556
- tileCollisionLayers.splice(index, 1);
556
+ index >= 0 && tileCollisionLayers.splice(index, 1);
557
557
  super.destroy();
558
558
  }
559
559
 
@@ -148,7 +148,7 @@ function percentLerp(value, percentA, percentB, lerpA, lerpB)
148
148
  * @param {number} valueA
149
149
  * @param {number} valueB
150
150
  * @param {number} [wrapSize]
151
- * @returns {number}
151
+ * @return {number}
152
152
  * @memberof Utilities */
153
153
  function distanceWrap(valueA, valueB, wrapSize=1)
154
154
  { const d = (valueA - valueB) % wrapSize; return d*2 % wrapSize - d; }
@@ -158,7 +158,7 @@ function distanceWrap(valueA, valueB, wrapSize=1)
158
158
  * @param {number} valueB
159
159
  * @param {number} percent
160
160
  * @param {number} [wrapSize]
161
- * @returns {number}
161
+ * @return {number}
162
162
  * @memberof Utilities */
163
163
  function lerpWrap(valueA, valueB, percent, wrapSize=1)
164
164
  {
@@ -170,7 +170,7 @@ function lerpWrap(valueA, valueB, percent, wrapSize=1)
170
170
  /** Returns signed wrapped distance between the two angles passed in
171
171
  * @param {number} angleA
172
172
  * @param {number} angleB
173
- * @returns {number}
173
+ * @return {number}
174
174
  * @memberof Utilities */
175
175
  function distanceAngle(angleA, angleB) { return distanceWrap(angleA, angleB, 2*PI); }
176
176
 
@@ -178,7 +178,7 @@ function distanceAngle(angleA, angleB) { return distanceWrap(angleA, angleB, 2*P
178
178
  * @param {number} angleA
179
179
  * @param {number} angleB
180
180
  * @param {number} percent
181
- * @returns {number}
181
+ * @return {number}
182
182
  * @memberof Utilities */
183
183
  function lerpAngle(angleA, angleB, percent) { return lerpWrap(angleA, angleB, percent, 2*PI); }
184
184
 
@@ -1147,11 +1147,14 @@ const MAGENTA = debugProtectConstant(rgb(1,0,1));
1147
1147
  class Timer
1148
1148
  {
1149
1149
  /** Create a timer object set time passed in
1150
- * @param {number} [timeLeft] - How much time left before the timer elapses in seconds */
1151
- constructor(timeLeft)
1150
+ * @param {number} [timeLeft] - How much time left before the timer
1151
+ * @param {boolean} [useRealTime] - Should the timer keep running even when the game is paused? (useful for UI) */
1152
+ constructor(timeLeft, useRealTime=false)
1152
1153
  {
1153
1154
  ASSERT(timeLeft === undefined || isNumber(timeLeft), 'Constructed Timer is invalid.', timeLeft);
1154
- this.time = timeLeft === undefined ? undefined : time + timeLeft;
1155
+ this.useRealTime = useRealTime;
1156
+ const globalTime = this.getGlobalTime();
1157
+ this.time = timeLeft === undefined ? undefined : globalTime + timeLeft;
1155
1158
  this.setTime = timeLeft;
1156
1159
  }
1157
1160
 
@@ -1160,10 +1163,19 @@ class Timer
1160
1163
  set(timeLeft=0)
1161
1164
  {
1162
1165
  ASSERT(isNumber(timeLeft), 'Timer is invalid.', timeLeft);
1163
- this.time = time + timeLeft;
1166
+ const globalTime = this.getGlobalTime();
1167
+ this.time = globalTime + timeLeft;
1164
1168
  this.setTime = timeLeft;
1165
1169
  }
1166
1170
 
1171
+ /** Set if the timer should keep running even when the game is paused
1172
+ * @param {boolean} [useRealTime] */
1173
+ setUseRealTime(useRealTime=true)
1174
+ {
1175
+ ASSERT(!this.isSet(), 'Cannot change global time setting while timer is set.');
1176
+ this.useRealTime = useRealTime;
1177
+ }
1178
+
1167
1179
  /** Unset the timer */
1168
1180
  unset() { this.time = undefined; }
1169
1181
 
@@ -1173,24 +1185,28 @@ class Timer
1173
1185
 
1174
1186
  /** Returns true if set and has not elapsed
1175
1187
  * @return {boolean} */
1176
- active() { return time < this.time; }
1188
+ active() { return this.getGlobalTime() < this.time; }
1177
1189
 
1178
1190
  /** Returns true if set and elapsed
1179
1191
  * @return {boolean} */
1180
- elapsed() { return time >= this.time; }
1192
+ elapsed() { return this.getGlobalTime() >= this.time; }
1181
1193
 
1182
1194
  /** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
1183
1195
  * @return {number} */
1184
- get() { return this.isSet()? time - this.time : 0; }
1196
+ get() { return this.isSet()? this.getGlobalTime() - this.time : 0; }
1185
1197
 
1186
1198
  /** Get percentage elapsed based on time it was set to, returns 0 if not set
1187
1199
  * @return {number} */
1188
- getPercent() { return this.isSet()? 1-percent(this.time - time, 0, this.setTime) : 0; }
1200
+ getPercent() { return this.isSet()? 1-percent(this.time - this.getGlobalTime(), 0, this.setTime) : 0; }
1189
1201
 
1190
1202
  /** Get the time this timer was set to, returns 0 if not set
1191
1203
  * @return {number} */
1192
1204
  getSetTime() { return this.isSet() ? this.setTime : 0; }
1193
1205
 
1206
+ /** Get the current global time this timer is based on
1207
+ * @return {number} */
1208
+ getGlobalTime() { return this.useRealTime ? timeReal : time; }
1209
+
1194
1210
  /** Returns this timer expressed as a string
1195
1211
  * @return {string} */
1196
1212
  toString() { return this.isSet() ? abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ) : 'unset'; }