littlejsengine 1.12.4 → 1.13.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.
Files changed (88) hide show
  1. package/dist/box2d.wasm.js +630 -0
  2. package/dist/box2d.wasm.wasm +0 -0
  3. package/dist/littlejs.d.ts +437 -219
  4. package/dist/littlejs.esm.js +5950 -5307
  5. package/dist/littlejs.esm.min.js +4 -1
  6. package/dist/littlejs.js +5921 -5295
  7. package/dist/littlejs.min.js +4 -1
  8. package/dist/littlejs.release.js +5485 -4886
  9. package/examples/box2d/game.js +37 -42
  10. package/examples/box2d/gameObjects.js +41 -37
  11. package/examples/box2d/index.html +2 -2
  12. package/examples/box2d/scenes.js +24 -20
  13. package/examples/box2d/tiles.png +0 -0
  14. package/examples/breakout/game.js +1 -1
  15. package/examples/breakout/gameObjects.js +2 -2
  16. package/examples/breakout/index.html +1 -1
  17. package/examples/breakoutTutorial/README.md +2 -2
  18. package/examples/breakoutTutorial/game.js +1 -1
  19. package/examples/breakoutTutorial/index.html +1 -1
  20. package/examples/empty/index.html +1 -1
  21. package/examples/htmlMenu/index.html +1 -1
  22. package/examples/index.html +410 -159
  23. package/examples/module/game.js +1 -1
  24. package/examples/module/index.html +1 -1
  25. package/examples/platformer/game.js +6 -15
  26. package/examples/platformer/gameCharacter.js +6 -6
  27. package/examples/platformer/gameEffects.js +74 -57
  28. package/examples/platformer/gameLevel.js +61 -70
  29. package/examples/platformer/gameObjects.js +4 -4
  30. package/examples/platformer/index.html +1 -1
  31. package/examples/puzzle/index.html +1 -1
  32. package/examples/shorts/base.html +24 -3
  33. package/examples/shorts/box2d.js +46 -0
  34. package/examples/shorts/box2dCar.js +45 -0
  35. package/examples/shorts/flappyGame.js +52 -0
  36. package/examples/shorts/helloWorld.js +6 -3
  37. package/examples/shorts/hillGlideGame.js +56 -0
  38. package/examples/shorts/landerGame.js +32 -0
  39. package/examples/shorts/maze.js +47 -0
  40. package/examples/shorts/medals.js +42 -0
  41. package/examples/shorts/nineSlice.js +21 -0
  42. package/examples/shorts/piano.js +52 -0
  43. package/examples/shorts/platformer.js +24 -20
  44. package/examples/shorts/{pong.js → pongGame.js} +1 -1
  45. package/examples/shorts/postProcess.js +45 -0
  46. package/examples/shorts/raycasting.js +71 -0
  47. package/examples/shorts/slidingPuzzle.js +42 -0
  48. package/examples/shorts/spaceGame.js +56 -0
  49. package/examples/shorts/starfield.js +17 -0
  50. package/examples/shorts/tileLayer.js +3 -5
  51. package/examples/shorts/tiles.png +0 -0
  52. package/examples/shorts/tiltedView.js +8 -7
  53. package/examples/shorts/topDown.js +18 -26
  54. package/examples/shorts/uiSystem.js +36 -0
  55. package/examples/starter/build.bat +6 -1
  56. package/examples/starter/game.js +4 -2
  57. package/examples/starter/index.html +23 -13
  58. package/examples/stress/index.html +2 -3
  59. package/examples/style.css +136 -0
  60. package/examples/typescript/build.js +1 -2
  61. package/examples/typescript/game.js +2 -2
  62. package/examples/typescript/game.ts +1 -1
  63. package/examples/typescript/index.html +1 -1
  64. package/examples/uiSystem/game.js +9 -25
  65. package/examples/uiSystem/index.html +1 -1
  66. package/package.json +1 -1
  67. package/plugins/box2d.js +29 -35
  68. package/plugins/drawUtilities.js +126 -0
  69. package/plugins/newgrounds.js +1 -1
  70. package/plugins/pluginExport.js +8 -2
  71. package/plugins/postProcess.js +2 -2
  72. package/plugins/uiSystem.js +162 -68
  73. package/plugins/zzfxm.js +1 -1
  74. package/reference.md +10 -10
  75. package/src/engine.js +59 -39
  76. package/src/engineAudio.js +38 -20
  77. package/src/engineBuild.bat +6 -1
  78. package/src/engineBuild.js +32 -7
  79. package/src/engineDebug.js +53 -26
  80. package/src/engineDraw.js +108 -57
  81. package/src/engineExport.js +22 -9
  82. package/src/engineInput.js +112 -40
  83. package/src/engineObject.js +68 -67
  84. package/src/engineParticles.js +26 -9
  85. package/src/engineSettings.js +15 -16
  86. package/src/engineTileLayer.js +312 -153
  87. package/src/engineUtilities.js +198 -130
  88. package/src/engineWebGL.js +58 -35
@@ -17,7 +17,8 @@
17
17
  * @memberof Input */
18
18
  function keyIsDown(key, device=0)
19
19
  {
20
- ASSERT(device > 0 || typeof key !== 'number' || key < 3, 'use code string for keyboard');
20
+ ASSERT(key !== undefined, 'key is undefined');
21
+ ASSERT(device > 0 || typeof key != 'number' || key < 3, 'use code string for keyboard');
21
22
  return inputData[device] && !!(inputData[device][key] & 1);
22
23
  }
23
24
 
@@ -28,7 +29,8 @@ function keyIsDown(key, device=0)
28
29
  * @memberof Input */
29
30
  function keyWasPressed(key, device=0)
30
31
  {
31
- ASSERT(device > 0 || typeof key !== 'number' || key < 3, 'use code string for keyboard');
32
+ ASSERT(key !== undefined, 'key is undefined');
33
+ ASSERT(device > 0 || typeof key != 'number' || key < 3, 'use code string for keyboard');
32
34
  return inputData[device] && !!(inputData[device][key] & 2);
33
35
  }
34
36
 
@@ -39,7 +41,8 @@ function keyWasPressed(key, device=0)
39
41
  * @memberof Input */
40
42
  function keyWasReleased(key, device=0)
41
43
  {
42
- ASSERT(device > 0 || typeof key !== 'number' || key < 3, 'use code string for keyboard');
44
+ ASSERT(key !== undefined, 'key is undefined');
45
+ ASSERT(device > 0 || typeof key != 'number' || key < 3, 'use code string for keyboard');
43
46
  return inputData[device] && !!(inputData[device][key] & 4);
44
47
  }
45
48
 
@@ -54,28 +57,42 @@ function keyDirection(up='ArrowUp', down='ArrowDown', left='ArrowLeft', right='A
54
57
 
55
58
  /** Clears all input
56
59
  * @memberof Input */
57
- function clearInput() { inputData = [[]]; touchGamepadButtons = []; }
60
+ function inputClear() { inputData = [[]]; touchGamepadButtons = []; }
61
+
62
+ /** Clears an input key state
63
+ * @param {string|number} key
64
+ * @param {number} [device]
65
+ * @param {boolean} [clearDown=true]
66
+ * @param {boolean} [clearPressed=true]
67
+ * @param {boolean} [clearReleased=true]
68
+ * @memberof Input */
69
+ function inputClearKey(key, device=0, clearDown=true, clearPressed=true, clearReleased=true)
70
+ {
71
+ if (!inputData[device])
72
+ return;
73
+ inputData[device][key] &= ~((clearDown?1:0)|(clearPressed?2:0)|(clearReleased?4:0));
74
+ }
58
75
 
59
76
  /** Returns true if mouse button is down
60
77
  * @function
61
78
  * @param {number} button
62
79
  * @return {boolean}
63
80
  * @memberof Input */
64
- const mouseIsDown = keyIsDown;
81
+ function mouseIsDown(button) { return keyIsDown(button); }
65
82
 
66
83
  /** Returns true if mouse button was pressed
67
84
  * @function
68
85
  * @param {number} button
69
86
  * @return {boolean}
70
87
  * @memberof Input */
71
- const mouseWasPressed = keyWasPressed;
88
+ function mouseWasPressed(button) { return keyWasPressed(button); }
72
89
 
73
90
  /** Returns true if mouse button was released
74
91
  * @function
75
92
  * @param {number} button
76
93
  * @return {boolean}
77
94
  * @memberof Input */
78
- const mouseWasReleased = keyWasReleased;
95
+ function mouseWasReleased(button) { return keyWasReleased(button); }
79
96
 
80
97
  /** Mouse pos in world space
81
98
  * @type {Vector2}
@@ -87,6 +104,16 @@ let mousePos = vec2();
87
104
  * @memberof Input */
88
105
  let mousePosScreen = vec2();
89
106
 
107
+ /** Mouse movement delta in world space
108
+ * @type {Vector2}
109
+ * @memberof Input */
110
+ let mouseDelta = vec2();
111
+
112
+ /** Mouse movement delta in screen space
113
+ * @type {Vector2}
114
+ * @memberof Input */
115
+ let mouseDeltaScreen = vec2();
116
+
90
117
  /** Mouse wheel delta this frame
91
118
  * @type {number}
92
119
  * @memberof Input */
@@ -153,10 +180,11 @@ function inputUpdate()
153
180
 
154
181
  // clear input when lost focus (prevent stuck keys)
155
182
  if(!(touchInputEnable && isTouchDevice) && !document.hasFocus())
156
- clearInput();
183
+ inputClear();
157
184
 
158
185
  // update mouse world space position
159
186
  mousePos = screenToWorld(mousePosScreen);
187
+ mouseDelta = mouseDeltaScreen.multiply(vec2(1,-1)).rotate(-cameraAngle);
160
188
 
161
189
  // update gamepads if enabled
162
190
  gamepadsUpdate();
@@ -171,13 +199,29 @@ function inputUpdatePost()
171
199
  for (const i in deviceInputData)
172
200
  deviceInputData[i] &= 1;
173
201
  mouseWheel = 0;
202
+ mouseDelta = vec2();
203
+ mouseDeltaScreen = vec2();
174
204
  }
175
205
 
176
206
  function inputInit()
177
207
  {
178
208
  if (headlessMode) return;
179
209
 
180
- onkeydown = (e)=>
210
+ // add event listeners
211
+ document.addEventListener('keydown', onKeyDown);
212
+ document.addEventListener('keyup', onKeyUp);
213
+ document.addEventListener('mousedown', onMouseDown);
214
+ document.addEventListener('mouseup', onMouseUp);
215
+ document.addEventListener('mousemove', onMouseMove);
216
+ document.addEventListener('wheel', onMouseWheel);
217
+ document.addEventListener('contextmenu', onContextMenu);
218
+ document.addEventListener('blur', onBlur);
219
+
220
+ // init touch input
221
+ if (isTouchDevice && touchInputEnable)
222
+ touchInputInit();
223
+
224
+ function onKeyDown(e)
181
225
  {
182
226
  if (!e.repeat)
183
227
  {
@@ -187,45 +231,49 @@ function inputInit()
187
231
  inputData[0][remapKey(e.code)] = 3;
188
232
  }
189
233
  }
190
-
191
- onkeyup = (e)=>
234
+ function onKeyUp(e)
192
235
  {
193
- inputData[0][e.code] = 4;
236
+ inputData[0][e.code] = (inputData[0][e.code]&2) | 4;
194
237
  if (inputWASDEmulateDirection)
195
238
  inputData[0][remapKey(e.code)] = 4;
196
239
  }
197
-
198
- // handle remapping wasd keys to directions
199
240
  function remapKey(c)
200
241
  {
201
- return inputWASDEmulateDirection ?
202
- c == 'KeyW' ? 'ArrowUp' :
203
- c == 'KeyS' ? 'ArrowDown' :
204
- c == 'KeyA' ? 'ArrowLeft' :
242
+ // handle remapping wasd keys to directions
243
+ return inputWASDEmulateDirection ?
244
+ c == 'KeyW' ? 'ArrowUp' :
245
+ c == 'KeyS' ? 'ArrowDown' :
246
+ c == 'KeyA' ? 'ArrowLeft' :
205
247
  c == 'KeyD' ? 'ArrowRight' : c : c;
206
248
  }
207
-
208
- // mouse event handlers
209
- onmousedown = (e)=>
249
+ function onMouseDown(e)
210
250
  {
251
+ if (isTouchDevice && touchInputEnable)
252
+ return;
253
+
211
254
  // fix stalled audio requiring user interaction
212
255
  if (soundEnable && !headlessMode && audioContext && audioContext.state != 'running')
213
256
  audioContext.resume();
214
257
 
215
- isUsingGamepad = false;
216
- inputData[0][e.button] = 3;
217
- mousePosScreen = mouseEventToScreen(e);
258
+ isUsingGamepad = false;
259
+ inputData[0][e.button] = 3;
260
+ mousePosScreen = mouseEventToScreen(vec2(e.x,e.y));
218
261
  inputPreventDefault && e.button && e.preventDefault();
219
262
  }
220
- onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
221
- onmousemove = (e)=> mousePosScreen = mouseEventToScreen(e);
222
- onwheel = (e)=> mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY);
223
- oncontextmenu = (e)=> false; // prevent right click menu
224
- onblur = (e) => clearInput(); // reset input when focus is lost
225
-
226
- // init touch input
227
- if (isTouchDevice && touchInputEnable)
228
- touchInputInit();
263
+ function onMouseUp(e)
264
+ {
265
+ if (isTouchDevice && touchInputEnable)
266
+ return;
267
+ inputData[0][e.button] = (inputData[0][e.button]&2) | 4;
268
+ }
269
+ function onMouseMove(e)
270
+ {
271
+ mousePosScreen = mouseEventToScreen(vec2(e.x,e.y));
272
+ mouseDeltaScreen = mouseDeltaScreen.add(vec2(e.movementX, e.movementY));
273
+ }
274
+ function onMouseWheel(e) { mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY); }
275
+ function onContextMenu(e) { e.preventDefault(); } // prevent right click menu
276
+ function onBlur() { inputClear(); } // reset input when focus is lost
229
277
  }
230
278
 
231
279
  // convert a mouse or touch event position to screen space
@@ -373,9 +421,6 @@ function touchInputInit()
373
421
  document.addEventListener('touchmove', (e) => handleTouch(e), { passive: false });
374
422
  document.addEventListener('touchend', (e) => handleTouch(e), { passive: false });
375
423
 
376
- // override mouse events
377
- onmousedown = onmouseup = ()=> 0;
378
-
379
424
  // handle all touch events the same way
380
425
  let wasTouching;
381
426
  function handleTouchDefault(e)
@@ -390,9 +435,16 @@ function touchInputInit()
390
435
  if (touching)
391
436
  {
392
437
  // set event pos and pass it along
393
- const p = vec2(e.touches[0].clientX, e.touches[0].clientY);
394
- mousePosScreen = mouseEventToScreen(p);
395
- wasTouching ? isUsingGamepad = touchGamepadEnable : inputData[0][button] = 3;
438
+ const pos = vec2(e.touches[0].clientX, e.touches[0].clientY);
439
+ const lastMousePosScreen = mousePosScreen;
440
+ mousePosScreen = mouseEventToScreen(pos);
441
+ if (wasTouching)
442
+ {
443
+ mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(lastMousePosScreen));
444
+ isUsingGamepad = touchGamepadEnable;
445
+ }
446
+ else
447
+ inputData[0][button] = 3;
396
448
  }
397
449
  else if (wasTouching)
398
450
  inputData[0][button] = inputData[0][button] & 2 | 4;
@@ -522,4 +574,24 @@ function touchGamepadRender()
522
574
 
523
575
  // set canvas back to normal
524
576
  context.restore();
525
- }
577
+ }
578
+
579
+ ///////////////////////////////////////////////////////////////////////////////
580
+ // Pointer Lock
581
+
582
+ /** Request to lock the pointer, does not work on touch devices
583
+ * @memberof Input */
584
+ function pointerLockRequest()
585
+ {
586
+ if (!isTouchDevice)
587
+ mainCanvas.requestPointerLock && mainCanvas.requestPointerLock();
588
+ }
589
+
590
+ /** Request to unlock the pointer
591
+ * @memberof Input */
592
+ function pointerLockExit() { document.exitPointerLock && document.exitPointerLock(); }
593
+
594
+ /** Check if pointer is locked (true if locked)
595
+ * @return {boolean}
596
+ * @memberof Input */
597
+ function pointerLockIsActive() { return document.pointerLockElement == mainCanvas }
@@ -41,9 +41,13 @@ class EngineObject
41
41
  */
42
42
  constructor(pos=vec2(), size=vec2(1), tileInfo, angle=0, color=new Color, renderOrder=0)
43
43
  {
44
- // set passed in params
45
- ASSERT(isVector2(pos) && isVector2(size), 'ensure pos and size are vec2s');
46
- ASSERT(typeof tileInfo !== 'number' || !tileInfo, 'old style tile setup');
44
+ // check passed in params
45
+ ASSERT(isVector2(pos) && pos.isValid(), 'object pos should be a vec2');
46
+ ASSERT(isVector2(size) && size.isValid(), 'object size should be a vec2');
47
+ ASSERT(!tileInfo || tileInfo instanceof TileInfo, 'object tileInfo should be a TileInfo or undefined');
48
+ ASSERT(typeof angle == 'number' && isFinite(angle), 'object angle should be a number');
49
+ ASSERT(isColor(color) && color.isValid(), 'object color should be a valid rgba color');
50
+ ASSERT(typeof renderOrder == 'number', 'object renderOrder should be a number');
47
51
 
48
52
  /** @property {Vector2} - World space position of the object */
49
53
  this.pos = pos.copy();
@@ -53,49 +57,49 @@ class EngineObject
53
57
  this.drawSize = undefined;
54
58
  /** @property {TileInfo} - Tile info to render object (undefined is untextured) */
55
59
  this.tileInfo = tileInfo;
56
- /** @property {number} - Angle to rotate the object */
60
+ /** @property {number} - Angle to rotate the object */
57
61
  this.angle = angle;
58
- /** @property {Color} - Color to apply when rendered */
62
+ /** @property {Color} - Color to apply when rendered */
59
63
  this.color = color;
60
- /** @property {Color} - Additive color to apply when rendered */
64
+ /** @property {Color} - Additive color to apply when rendered */
61
65
  this.additiveColor = undefined;
62
66
  /** @property {boolean} - Should it flip along y axis when rendered */
63
67
  this.mirror = false;
64
68
 
65
69
  // physical properties
66
- /** @property {number} [mass=objectDefaultMass] - How heavy the object is, static if 0 */
67
- this.mass = objectDefaultMass;
68
- /** @property {number} [damping=objectDefaultDamping] - How much to slow down velocity each frame (0-1) */
69
- this.damping = objectDefaultDamping;
70
+ /** @property {number} [mass=objectDefaultMass] - How heavy the object is, static if 0 */
71
+ this.mass = objectDefaultMass;
72
+ /** @property {number} [damping=objectDefaultDamping] - How much to slow down velocity each frame (0-1) */
73
+ this.damping = objectDefaultDamping;
70
74
  /** @property {number} [angleDamping=objectDefaultAngleDamping] - How much to slow down rotation each frame (0-1) */
71
75
  this.angleDamping = objectDefaultAngleDamping;
72
- /** @property {number} [elasticity=objectDefaultElasticity] - How bouncy the object is when colliding (0-1) */
73
- this.elasticity = objectDefaultElasticity;
74
- /** @property {number} [friction=objectDefaultFriction] - How much friction to apply when sliding (0-1) */
75
- this.friction = objectDefaultFriction;
76
- /** @property {number} - How much to scale gravity by for this object */
76
+ /** @property {number} [restitution=objectDefaultRestitution] - How bouncy the object is when colliding (0-1) */
77
+ this.restitution = objectDefaultRestitution;
78
+ /** @property {number} [friction=objectDefaultFriction] - How much friction to apply when sliding (0-1) */
79
+ this.friction = objectDefaultFriction;
80
+ /** @property {number} - How much to scale gravity by for this object */
77
81
  this.gravityScale = 1;
78
- /** @property {number} - Objects are sorted by render order */
82
+ /** @property {number} - Objects are sorted by render order */
79
83
  this.renderOrder = renderOrder;
80
84
  /** @property {Vector2} - Velocity of the object */
81
85
  this.velocity = vec2();
82
- /** @property {number} - Angular velocity of the object */
86
+ /** @property {number} - Angular velocity of the object */
83
87
  this.angleVelocity = 0;
84
- /** @property {number} - Track when object was created */
88
+ /** @property {number} - Track when object was created */
85
89
  this.spawnTime = time;
86
- /** @property {Array<EngineObject>} - List of children of this object */
90
+ /** @property {Array<EngineObject>} - List of children of this object */
87
91
  this.children = [];
88
- /** @property {boolean} - Limit object speed using linear or circular math */
89
- this.clampSpeedLinear = true;
92
+ /** @property {boolean} - Limit object speed along x and y axis */
93
+ this.clampSpeed = true;
90
94
  /** @property {EngineObject} - Object we are standing on, if any */
91
95
  this.groundObject = undefined;
92
96
 
93
97
  // parent child system
94
98
  /** @property {EngineObject} - Parent of object if in local space */
95
99
  this.parent = undefined;
96
- /** @property {Vector2} - Local position if child */
100
+ /** @property {Vector2} - Local position if child */
97
101
  this.localPos = vec2();
98
- /** @property {number} - Local angle if child */
102
+ /** @property {number} - Local angle if child */
99
103
  this.localAngle = 0;
100
104
 
101
105
  // collision flags
@@ -136,29 +140,20 @@ class EngineObject
136
140
  if (this.parent)
137
141
  return;
138
142
 
139
- // limit max speed to prevent missing collisions
140
- if (this.clampSpeedLinear)
143
+ if (this.clampSpeed)
141
144
  {
145
+ // limit max speed to prevent missing collisions
142
146
  this.velocity.x = clamp(this.velocity.x, -objectMaxSpeed, objectMaxSpeed);
143
147
  this.velocity.y = clamp(this.velocity.y, -objectMaxSpeed, objectMaxSpeed);
144
148
  }
145
- else
146
- {
147
- const length2 = this.velocity.lengthSquared();
148
- if (length2 > objectMaxSpeed*objectMaxSpeed)
149
- {
150
- const s = objectMaxSpeed / length2**.5;
151
- this.velocity.x *= s;
152
- this.velocity.y *= s;
153
- }
154
- }
155
149
 
156
150
  // apply physics
157
151
  const oldPos = this.pos.copy();
158
152
  this.velocity.x *= this.damping;
159
153
  this.velocity.y *= this.damping;
160
- if (this.mass) // don't apply gravity to static objects
154
+ if (this.mass)
161
155
  {
156
+ // apply gravity only if it has mass
162
157
  this.velocity.x += gravity.x * this.gravityScale;
163
158
  this.velocity.y += gravity.y * this.gravityScale;
164
159
  }
@@ -180,7 +175,6 @@ class EngineObject
180
175
  const groundSpeed = this.groundObject.velocity ? this.groundObject.velocity.x : 0;
181
176
  this.velocity.x = groundSpeed + (this.velocity.x - groundSpeed) * friction;
182
177
  this.groundObject = undefined;
183
- //debugOverlay && debugPhysics && debugPoint(this.pos.subtract(vec2(0,this.size.y/2)), '#0f0');
184
178
  }
185
179
 
186
180
  if (this.collideSolidObjects)
@@ -209,7 +203,7 @@ class EngineObject
209
203
  const deltaPos = oldPos.subtract(o.pos);
210
204
  const length = deltaPos.length();
211
205
  const pushAwayAccel = .001; // push away if already overlapping
212
- const velocity = length < .01 ? randVector(pushAwayAccel) : deltaPos.scale(pushAwayAccel/length);
206
+ const velocity = length < .01 ? randVec2(pushAwayAccel) : deltaPos.scale(pushAwayAccel/length);
213
207
  this.velocity = this.velocity.add(velocity);
214
208
  if (o.mass) // push away if not fixed
215
209
  o.velocity = o.velocity.subtract(velocity);
@@ -223,7 +217,7 @@ class EngineObject
223
217
  const smallStepUp = (oldPos.y - o.pos.y)*2 > sizeBoth.y + gravity.y; // prefer to push up if small delta
224
218
  const isBlockedX = abs(oldPos.y - o.pos.y)*2 < sizeBoth.y;
225
219
  const isBlockedY = abs(oldPos.x - o.pos.x)*2 < sizeBoth.x;
226
- const elasticity = max(this.elasticity, o.elasticity);
220
+ const restitution = max(this.restitution, o.restitution);
227
221
 
228
222
  if (smallStepUp || isBlockedY || !isBlockedX) // resolve y collision
229
223
  {
@@ -236,7 +230,7 @@ class EngineObject
236
230
  this.groundObject = o;
237
231
 
238
232
  // bounce if other object is fixed or grounded
239
- this.velocity.y *= -elasticity;
233
+ this.velocity.y *= -restitution;
240
234
  }
241
235
  else if (o.mass)
242
236
  {
@@ -249,9 +243,9 @@ class EngineObject
249
243
  const elastic1 = o.velocity.y * (o.mass - this.mass) / (this.mass + o.mass)
250
244
  + this.velocity.y * 2 * this.mass / (this.mass + o.mass);
251
245
 
252
- // lerp between elastic or inelastic based on elasticity
253
- this.velocity.y = lerp(elasticity, inelastic, elastic0);
254
- o.velocity.y = lerp(elasticity, inelastic, elastic1);
246
+ // lerp between elastic or inelastic based on restitution
247
+ this.velocity.y = lerp(inelastic, elastic0, restitution);
248
+ o.velocity.y = lerp(inelastic, elastic1, restitution);
255
249
  }
256
250
  }
257
251
  if (!smallStepUp && isBlockedX) // resolve x collision
@@ -269,12 +263,12 @@ class EngineObject
269
263
  const elastic1 = o.velocity.x * (o.mass - this.mass) / (this.mass + o.mass)
270
264
  + this.velocity.x * 2 * this.mass / (this.mass + o.mass);
271
265
 
272
- // lerp between elastic or inelastic based on elasticity
273
- this.velocity.x = lerp(elasticity, inelastic, elastic0);
274
- o.velocity.x = lerp(elasticity, inelastic, elastic1);
266
+ // lerp between elastic or inelastic based on restitution
267
+ this.velocity.x = lerp(inelastic, elastic0, restitution);
268
+ o.velocity.x = lerp(inelastic, elastic1, restitution);
275
269
  }
276
270
  else // bounce if other object is fixed
277
- this.velocity.x *= -elasticity;
271
+ this.velocity.x *= -restitution;
278
272
  }
279
273
  debugOverlay && debugPhysics && debugOverlap(this.pos, this.size, o.pos, o.size, '#f0f');
280
274
  }
@@ -295,8 +289,8 @@ class EngineObject
295
289
  if (blockedLayerY || !blockedLayerX)
296
290
  {
297
291
  // bounce velocity
298
- const elasticity = max(this.elasticity, hitLayer.elasticity);
299
- this.velocity.y *= -elasticity;
292
+ const restitution = max(this.restitution, hitLayer.restitution);
293
+ this.velocity.y *= -restitution;
300
294
 
301
295
  if (wasMovingDown)
302
296
  {
@@ -319,7 +313,7 @@ class EngineObject
319
313
  {
320
314
  // move to previous position and bounce
321
315
  this.pos.x = oldPos.x;
322
- this.velocity.x *= -this.elasticity;
316
+ this.velocity.x *= -this.restitution;
323
317
  }
324
318
  debugOverlay && debugPhysics && debugRect(this.pos, this.size, '#f00');
325
319
  }
@@ -334,7 +328,7 @@ class EngineObject
334
328
  drawTile(this.pos, this.drawSize || this.size, this.tileInfo, this.color, this.angle, this.mirror, this.additiveColor);
335
329
  }
336
330
 
337
- /** Destroy this object, destroy it's children, detach it's parent, and mark it for removal */
331
+ /** Destroy this object, destroy its children, detach it's parent, and mark it for removal */
338
332
  destroy()
339
333
  {
340
334
  if (this.destroyed)
@@ -344,7 +338,10 @@ class EngineObject
344
338
  this.destroyed = 1;
345
339
  this.parent && this.parent.removeChild(this);
346
340
  for (const child of this.children)
347
- child.destroy(child.parent = 0);
341
+ {
342
+ child.parent = 0;
343
+ child.destroy();
344
+ }
348
345
  }
349
346
 
350
347
  /** Convert from local space to world space
@@ -367,25 +364,29 @@ class EngineObject
367
364
  * @param {number} tileData - the value of the tile at the position
368
365
  * @param {Vector2} pos - tile where the collision occurred
369
366
  * @return {boolean} - true if the collision should be resolved */
370
- collideWithTile(tileData, pos) { return tileData > 0; }
367
+ collideWithTile(tileData, pos) { return tileData > 0; }
371
368
 
372
369
  /** Called to check if a object collision should be resolved
373
370
  * @param {EngineObject} object - the object to test against
374
371
  * @return {boolean} - true if the collision should be resolved
375
372
  */
376
- collideWithObject(object) { return true; }
373
+ collideWithObject(object) { return true; }
377
374
 
378
375
  /** How long since the object was created
379
376
  * @return {number} */
380
- getAliveTime() { return time - this.spawnTime; }
377
+ getAliveTime() { return time - this.spawnTime; }
381
378
 
382
379
  /** Apply acceleration to this object (adjust velocity, not affected by mass)
383
380
  * @param {Vector2} acceleration */
384
- applyAcceleration(acceleration) { if (this.mass) this.velocity = this.velocity.add(acceleration); }
381
+ applyAcceleration(acceleration) { if (this.mass) this.velocity = this.velocity.add(acceleration); }
382
+
383
+ /** Apply angular acceleration to this object
384
+ * @param {number} acceleration */
385
+ applyAngularAcceleration(acceleration) { if (this.mass) this.angleVelocity += acceleration; }
385
386
 
386
387
  /** Apply force to this object (adjust velocity, affected by mass)
387
388
  * @param {Vector2} force */
388
- applyForce(force) { this.applyAcceleration(force.scale(1/this.mass)); }
389
+ applyForce(force) { this.applyAcceleration(force.scale(1/this.mass)); }
389
390
 
390
391
  /** Get the direction of the mirror
391
392
  * @return {number} -1 if this.mirror is true, or 1 if not mirrored */
@@ -452,15 +453,15 @@ class EngineObject
452
453
  /** Render debug info for this object */
453
454
  renderDebugInfo()
454
455
  {
455
- if (debug)
456
- {
457
- // show object info for debugging
458
- const size = vec2(max(this.size.x, .2), max(this.size.y, .2));
459
- const color1 = rgb(this.collideTiles?1:0, this.collideSolidObjects?1:0, this.isSolid?1:0, this.parent?.2:.5);
460
- const color2 = this.parent ? rgb(1,1,1,.5) : rgb(0,0,0,.8);
461
- drawRect(this.pos, size, color1, this.angle, false);
462
- drawRect(this.pos, size.scale(.8), color2, this.angle, false);
463
- this.parent && drawLine(this.pos, this.parent.pos, .1, rgb(0,0,1,.5), false);
464
- }
456
+ if (!debug)
457
+ return;
458
+
459
+ // show object info for debugging
460
+ const size = vec2(max(this.size.x, .2), max(this.size.y, .2));
461
+ const color = rgb(this.collideTiles?1:0, this.collideSolidObjects?1:0, this.isSolid?1:0, .5);
462
+ drawRect(this.pos, size, color, this.angle);
463
+ if (this.parent)
464
+ drawRect(this.pos, size.scale(.8), rgb(1,1,1,.5), this.angle);
465
+ this.parent && drawLine(this.pos, this.parent.pos, .1, rgb(1,1,1,.5));
465
466
  }
466
467
  }
@@ -14,7 +14,7 @@
14
14
  * (
15
15
  * pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emitCone
16
16
  * tile(0, 16), // tileInfo
17
- * rgb(1,1,1), rgb(0,0,0), // colorStartA, colorStartB
17
+ * rgb(1,1,1,1), rgb(0,0,0,1), // colorStartA, colorStartB
18
18
  * rgb(1,1,1,0), rgb(0,0,0,0), // colorEndA, colorEndB
19
19
  * 2, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
20
20
  * .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate,
@@ -169,7 +169,7 @@ class ParticleEmitter extends EngineObject
169
169
  if (debugParticles)
170
170
  {
171
171
  // show emitter bounds
172
- const emitSize = typeof this.emitSize === 'number' ? vec2(this.emitSize) : this.emitSize;
172
+ const emitSize = typeof this.emitSize == 'number' ? vec2(this.emitSize) : this.emitSize;
173
173
  debugRect(this.pos, emitSize, '#0f0', 0, this.angle);
174
174
  }
175
175
  }
@@ -179,9 +179,9 @@ class ParticleEmitter extends EngineObject
179
179
  emitParticle()
180
180
  {
181
181
  // spawn a particle
182
- let pos = typeof this.emitSize === 'number' ? // check if number was used
183
- randInCircle(this.emitSize/2) // circle emitter
184
- : vec2(rand(-.5,.5), rand(-.5,.5)) // box emitter
182
+ let pos = typeof this.emitSize == 'number' ? // check if number was used
183
+ randInCircle(this.emitSize/2) // circle emitter
184
+ : vec2(rand(-.5,.5), rand(-.5,.5)) // box emitter
185
185
  .multiply(this.emitSize).rotate(this.angle)
186
186
  let angle = rand(this.particleConeAngle, -this.particleConeAngle);
187
187
  if (!this.localSpace)
@@ -212,12 +212,12 @@ class ParticleEmitter extends EngineObject
212
212
  particle.fadeRate = this.fadeRate;
213
213
  particle.damping = this.damping;
214
214
  particle.angleDamping = this.angleDamping;
215
- particle.elasticity = this.elasticity;
215
+ particle.restitution = this.restitution;
216
216
  particle.friction = this.friction;
217
217
  particle.gravityScale = this.gravityScale;
218
218
  particle.collideTiles = this.collideTiles;
219
219
  particle.renderOrder = this.renderOrder;
220
- particle.mirror = !!randInt(2);
220
+ particle.mirror = randBool();
221
221
 
222
222
  // call particle create callback
223
223
  this.particleCreateCallback && this.particleCreateCallback(particle);
@@ -279,9 +279,26 @@ class Particle extends EngineObject
279
279
  this.localSpaceEmitter = localSpaceEmitter;
280
280
  /** @property {Function} - Called when particle dies */
281
281
  this.destroyCallback = destroyCallback;
282
+ // particles do not clamp speed by default
283
+ this.clampSpeed = false;
284
+ }
285
+
286
+ /** Update the object physics, called automatically by engine once each frame */
287
+ update()
288
+ {
289
+ super.update();
282
290
 
283
- // particles use circular clamped speed
284
- this.clampSpeedLinear = false;
291
+ if (this.collideTiles || this.collideSolidObjects)
292
+ {
293
+ // only apply max circular speed if particle can collide
294
+ const length2 = this.velocity.lengthSquared();
295
+ if (length2 > objectMaxSpeed*objectMaxSpeed)
296
+ {
297
+ const s = objectMaxSpeed / length2**.5;
298
+ this.velocity.x *= s;
299
+ this.velocity.y *= s;
300
+ }
301
+ }
285
302
  }
286
303
 
287
304
  /** Render the particle, automatically called each frame, sorted by renderOrder */