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.
- package/dist/box2d.wasm.js +630 -0
- package/dist/box2d.wasm.wasm +0 -0
- package/dist/littlejs.d.ts +437 -219
- package/dist/littlejs.esm.js +5950 -5307
- package/dist/littlejs.esm.min.js +4 -1
- package/dist/littlejs.js +5921 -5295
- package/dist/littlejs.min.js +4 -1
- package/dist/littlejs.release.js +5485 -4886
- package/examples/box2d/game.js +37 -42
- package/examples/box2d/gameObjects.js +41 -37
- package/examples/box2d/index.html +2 -2
- package/examples/box2d/scenes.js +24 -20
- package/examples/box2d/tiles.png +0 -0
- package/examples/breakout/game.js +1 -1
- package/examples/breakout/gameObjects.js +2 -2
- package/examples/breakout/index.html +1 -1
- package/examples/breakoutTutorial/README.md +2 -2
- package/examples/breakoutTutorial/game.js +1 -1
- package/examples/breakoutTutorial/index.html +1 -1
- package/examples/empty/index.html +1 -1
- package/examples/htmlMenu/index.html +1 -1
- package/examples/index.html +410 -159
- package/examples/module/game.js +1 -1
- package/examples/module/index.html +1 -1
- package/examples/platformer/game.js +6 -15
- package/examples/platformer/gameCharacter.js +6 -6
- package/examples/platformer/gameEffects.js +74 -57
- package/examples/platformer/gameLevel.js +61 -70
- package/examples/platformer/gameObjects.js +4 -4
- package/examples/platformer/index.html +1 -1
- package/examples/puzzle/index.html +1 -1
- package/examples/shorts/base.html +24 -3
- package/examples/shorts/box2d.js +46 -0
- package/examples/shorts/box2dCar.js +45 -0
- package/examples/shorts/flappyGame.js +52 -0
- package/examples/shorts/helloWorld.js +6 -3
- package/examples/shorts/hillGlideGame.js +56 -0
- package/examples/shorts/landerGame.js +32 -0
- package/examples/shorts/maze.js +47 -0
- package/examples/shorts/medals.js +42 -0
- package/examples/shorts/nineSlice.js +21 -0
- package/examples/shorts/piano.js +52 -0
- package/examples/shorts/platformer.js +24 -20
- package/examples/shorts/{pong.js → pongGame.js} +1 -1
- package/examples/shorts/postProcess.js +45 -0
- package/examples/shorts/raycasting.js +71 -0
- package/examples/shorts/slidingPuzzle.js +42 -0
- package/examples/shorts/spaceGame.js +56 -0
- package/examples/shorts/starfield.js +17 -0
- package/examples/shorts/tileLayer.js +3 -5
- package/examples/shorts/tiles.png +0 -0
- package/examples/shorts/tiltedView.js +8 -7
- package/examples/shorts/topDown.js +18 -26
- package/examples/shorts/uiSystem.js +36 -0
- package/examples/starter/build.bat +6 -1
- package/examples/starter/game.js +4 -2
- package/examples/starter/index.html +23 -13
- package/examples/stress/index.html +2 -3
- package/examples/style.css +136 -0
- package/examples/typescript/build.js +1 -2
- package/examples/typescript/game.js +2 -2
- package/examples/typescript/game.ts +1 -1
- package/examples/typescript/index.html +1 -1
- package/examples/uiSystem/game.js +9 -25
- package/examples/uiSystem/index.html +1 -1
- package/package.json +1 -1
- package/plugins/box2d.js +29 -35
- package/plugins/drawUtilities.js +126 -0
- package/plugins/newgrounds.js +1 -1
- package/plugins/pluginExport.js +8 -2
- package/plugins/postProcess.js +2 -2
- package/plugins/uiSystem.js +162 -68
- package/plugins/zzfxm.js +1 -1
- package/reference.md +10 -10
- package/src/engine.js +59 -39
- package/src/engineAudio.js +38 -20
- package/src/engineBuild.bat +6 -1
- package/src/engineBuild.js +32 -7
- package/src/engineDebug.js +53 -26
- package/src/engineDraw.js +108 -57
- package/src/engineExport.js +22 -9
- package/src/engineInput.js +112 -40
- package/src/engineObject.js +68 -67
- package/src/engineParticles.js +26 -9
- package/src/engineSettings.js +15 -16
- package/src/engineTileLayer.js +312 -153
- package/src/engineUtilities.js +198 -130
- package/src/engineWebGL.js +58 -35
package/src/engineInput.js
CHANGED
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
* @memberof Input */
|
|
18
18
|
function keyIsDown(key, device=0)
|
|
19
19
|
{
|
|
20
|
-
ASSERT(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
c == '
|
|
204
|
-
c == '
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
|
394
|
-
|
|
395
|
-
|
|
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 }
|
package/src/engineObject.js
CHANGED
|
@@ -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
|
-
//
|
|
45
|
-
ASSERT(isVector2(pos) &&
|
|
46
|
-
ASSERT(
|
|
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}
|
|
60
|
+
/** @property {number} - Angle to rotate the object */
|
|
57
61
|
this.angle = angle;
|
|
58
|
-
/** @property {Color}
|
|
62
|
+
/** @property {Color} - Color to apply when rendered */
|
|
59
63
|
this.color = color;
|
|
60
|
-
/** @property {Color}
|
|
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]
|
|
67
|
-
this.mass
|
|
68
|
-
/** @property {number} [damping=objectDefaultDamping]
|
|
69
|
-
this.damping
|
|
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} [
|
|
73
|
-
this.
|
|
74
|
-
/** @property {number} [friction=objectDefaultFriction]
|
|
75
|
-
this.friction
|
|
76
|
-
/** @property {number}
|
|
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}
|
|
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}
|
|
86
|
+
/** @property {number} - Angular velocity of the object */
|
|
83
87
|
this.angleVelocity = 0;
|
|
84
|
-
/** @property {number}
|
|
88
|
+
/** @property {number} - Track when object was created */
|
|
85
89
|
this.spawnTime = time;
|
|
86
|
-
/** @property {Array<EngineObject>}
|
|
90
|
+
/** @property {Array<EngineObject>} - List of children of this object */
|
|
87
91
|
this.children = [];
|
|
88
|
-
/** @property {boolean}
|
|
89
|
-
this.
|
|
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}
|
|
100
|
+
/** @property {Vector2} - Local position if child */
|
|
97
101
|
this.localPos = vec2();
|
|
98
|
-
/** @property {number}
|
|
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
|
-
|
|
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)
|
|
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 ?
|
|
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
|
|
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 *= -
|
|
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
|
|
253
|
-
this.velocity.y = lerp(
|
|
254
|
-
o.velocity.y = lerp(
|
|
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
|
|
273
|
-
this.velocity.x = lerp(
|
|
274
|
-
o.velocity.x = lerp(
|
|
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 *= -
|
|
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
|
|
299
|
-
this.velocity.y *= -
|
|
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.
|
|
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
|
|
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
|
-
|
|
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)
|
|
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)
|
|
373
|
+
collideWithObject(object) { return true; }
|
|
377
374
|
|
|
378
375
|
/** How long since the object was created
|
|
379
376
|
* @return {number} */
|
|
380
|
-
getAliveTime()
|
|
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)
|
|
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)
|
|
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
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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
|
}
|
package/src/engineParticles.js
CHANGED
|
@@ -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),
|
|
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
|
|
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
|
|
183
|
-
randInCircle(this.emitSize/2)
|
|
184
|
-
: vec2(rand(-.5,.5), rand(-.5,.5))
|
|
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.
|
|
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 =
|
|
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
|
-
|
|
284
|
-
|
|
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 */
|