littlejsengine 1.14.19 → 1.14.28
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.d.ts +217 -38
- package/dist/littlejs.esm.js +879 -324
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +861 -324
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +799 -325
- package/examples/box2d/gameObjects.js +6 -10
- package/examples/box2d/scenes.js +2 -2
- package/examples/breakout/gameObjects.js +1 -1
- package/examples/electron/game.js +1 -41
- package/examples/electron/index.html +2 -2
- package/examples/htmlMenu/game.js +1 -1
- package/examples/index.html +19 -10
- package/examples/module/game.js +5 -7
- package/examples/particles/index.html +6 -9
- package/examples/platformer/gameCharacter.js +4 -3
- package/examples/platformer/gameEffects.js +7 -2
- package/examples/platformer/gameObjects.js +6 -11
- package/examples/shorts/animation.js +2 -2
- package/examples/shorts/base.html +4 -1
- package/examples/shorts/box2d.js +2 -2
- package/examples/shorts/box2dCar.js +18 -7
- package/examples/shorts/box2dPool.js +108 -0
- package/examples/shorts/cameraDrag.js +22 -0
- package/examples/shorts/debugDraw.js +37 -0
- package/examples/shorts/flappyGame.js +1 -0
- package/examples/shorts/fps.js +90 -0
- package/examples/shorts/helloWorld.js +1 -1
- package/examples/shorts/hillGlideGame.js +1 -3
- package/examples/shorts/input.js +59 -0
- package/examples/shorts/landerGame.js +1 -3
- package/examples/shorts/medals.js +15 -9
- package/examples/shorts/music.js +15 -17
- package/examples/shorts/musicPlayer.js +24 -24
- package/examples/shorts/platformer.js +0 -2
- package/examples/shorts/postProcess.js +1 -1
- package/examples/shorts/sequencer.js +3 -3
- package/examples/shorts/slidingPuzzle.js +1 -1
- package/examples/shorts/spaceGame.js +1 -3
- package/examples/shorts/spriteAtlas.js +6 -8
- package/examples/shorts/starfield.js +1 -1
- package/examples/shorts/tileRaycast.js +39 -0
- package/examples/shorts/tiles.png +0 -0
- package/examples/shorts/tiltedView.js +1 -4
- package/examples/shorts/timers.js +1 -2
- package/examples/shorts/topDown.js +0 -2
- package/examples/starter/game.js +5 -7
- package/examples/starter/index.html +2 -2
- package/examples/typescript/game.js +3 -2
- package/examples/typescript/game.ts +5 -7
- package/examples/uiSystem/game.js +2 -2
- package/package.json +1 -1
- package/plugins/box2d.js +170 -50
- package/plugins/pluginExport.js +2 -0
- package/plugins/uiSystem.js +55 -27
- package/src/engine.js +19 -16
- package/src/engineAudio.js +27 -15
- package/src/engineDebug.js +64 -0
- package/src/engineDraw.js +88 -32
- package/src/engineExport.js +16 -0
- package/src/engineInput.js +35 -25
- package/src/engineMedals.js +2 -2
- package/src/engineObject.js +55 -10
- package/src/engineParticles.js +36 -20
- package/src/engineRelease.js +2 -1
- package/src/engineSettings.js +20 -1
- package/src/engineTileLayer.js +64 -59
- package/src/engineUtilities.js +213 -59
- package/src/engineWebGL.js +13 -8
- package/examples/shorts/raycasting.js +0 -79
package/src/engineInput.js
CHANGED
|
@@ -119,6 +119,11 @@ let mouseDeltaScreen = vec2();
|
|
|
119
119
|
* @memberof Input */
|
|
120
120
|
let mouseWheel = 0;
|
|
121
121
|
|
|
122
|
+
/** True if mouse was inside the document window, set to false when mouse leaves
|
|
123
|
+
* @type {boolean}
|
|
124
|
+
* @memberof Input */
|
|
125
|
+
let mouseInWindow = true;
|
|
126
|
+
|
|
122
127
|
/** Returns true if user is using gamepad (has more recently pressed a gamepad button)
|
|
123
128
|
* @type {boolean}
|
|
124
129
|
* @memberof Input */
|
|
@@ -182,9 +187,9 @@ function inputUpdate()
|
|
|
182
187
|
if(!(touchInputEnable && isTouchDevice) && !document.hasFocus())
|
|
183
188
|
inputClear();
|
|
184
189
|
|
|
185
|
-
// update mouse world space position
|
|
190
|
+
// update mouse world space position and delta
|
|
186
191
|
mousePos = screenToWorld(mousePosScreen);
|
|
187
|
-
mouseDelta = mouseDeltaScreen
|
|
192
|
+
mouseDelta = screenToWorldDelta(mouseDeltaScreen);
|
|
188
193
|
|
|
189
194
|
// update gamepads if enabled
|
|
190
195
|
gamepadsUpdate();
|
|
@@ -213,10 +218,10 @@ function inputInit()
|
|
|
213
218
|
document.addEventListener('mousedown', onMouseDown);
|
|
214
219
|
document.addEventListener('mouseup', onMouseUp);
|
|
215
220
|
document.addEventListener('mousemove', onMouseMove);
|
|
221
|
+
document.addEventListener('mouseleave', onMouseLeave);
|
|
216
222
|
document.addEventListener('wheel', onMouseWheel);
|
|
217
223
|
document.addEventListener('contextmenu', onContextMenu);
|
|
218
224
|
document.addEventListener('blur', onBlur);
|
|
219
|
-
document.addEventListener('mouseleave', onMouseLeave);
|
|
220
225
|
|
|
221
226
|
// init touch input
|
|
222
227
|
if (isTouchDevice && touchInputEnable)
|
|
@@ -238,14 +243,14 @@ function inputInit()
|
|
|
238
243
|
if (inputWASDEmulateDirection)
|
|
239
244
|
inputData[0][remapKey(e.code)] = 4;
|
|
240
245
|
}
|
|
241
|
-
function remapKey(
|
|
246
|
+
function remapKey(k)
|
|
242
247
|
{
|
|
243
248
|
// handle remapping wasd keys to directions
|
|
244
249
|
return inputWASDEmulateDirection ?
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
250
|
+
k === 'KeyW' ? 'ArrowUp' :
|
|
251
|
+
k === 'KeyS' ? 'ArrowDown' :
|
|
252
|
+
k === 'KeyA' ? 'ArrowLeft' :
|
|
253
|
+
k === 'KeyD' ? 'ArrowRight' : k : k;
|
|
249
254
|
}
|
|
250
255
|
function onMouseDown(e)
|
|
251
256
|
{
|
|
@@ -258,7 +263,10 @@ function inputInit()
|
|
|
258
263
|
|
|
259
264
|
isUsingGamepad = false;
|
|
260
265
|
inputData[0][e.button] = 3;
|
|
266
|
+
|
|
267
|
+
const mousePosScreenLast = mousePosScreen;
|
|
261
268
|
mousePosScreen = mouseEventToScreen(vec2(e.x,e.y));
|
|
269
|
+
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
|
|
262
270
|
inputPreventDefault && e.button && e.preventDefault();
|
|
263
271
|
}
|
|
264
272
|
function onMouseUp(e)
|
|
@@ -269,18 +277,20 @@ function inputInit()
|
|
|
269
277
|
}
|
|
270
278
|
function onMouseMove(e)
|
|
271
279
|
{
|
|
280
|
+
mouseInWindow = true;
|
|
281
|
+
const mousePosScreenLast = mousePosScreen;
|
|
272
282
|
mousePosScreen = mouseEventToScreen(vec2(e.x,e.y));
|
|
273
|
-
|
|
283
|
+
|
|
284
|
+
// when pointer is locked use movementX/Y for delta
|
|
285
|
+
const movement = pointerLockIsActive() ?
|
|
286
|
+
vec2(e.movementX, e.movementY) :
|
|
287
|
+
mousePosScreen.subtract(mousePosScreenLast);
|
|
288
|
+
mouseDeltaScreen = mouseDeltaScreen.add(movement);
|
|
274
289
|
}
|
|
290
|
+
function onMouseLeave() { mouseInWindow = false; } // mouse moved off window
|
|
275
291
|
function onMouseWheel(e) { mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY); }
|
|
276
292
|
function onContextMenu(e) { e.preventDefault(); } // prevent right click menu
|
|
277
293
|
function onBlur() { inputClear(); } // reset input when focus is lost
|
|
278
|
-
function onMouseLeave()
|
|
279
|
-
{
|
|
280
|
-
// set mouse position and delta when leaving canvas
|
|
281
|
-
mousePosScreen = vec2(-1);
|
|
282
|
-
mouseDeltaScreen = vec2(0);
|
|
283
|
-
}
|
|
284
294
|
}
|
|
285
295
|
|
|
286
296
|
// convert a mouse or touch event position to screen space
|
|
@@ -324,8 +334,8 @@ function gamepadsUpdate()
|
|
|
324
334
|
else if (touchGamepadStick.lengthSquared() > .3)
|
|
325
335
|
{
|
|
326
336
|
// convert to 8 way dpad
|
|
327
|
-
sticks[0].x =
|
|
328
|
-
sticks[0].y = -
|
|
337
|
+
sticks[0].x = round(touchGamepadStick.x);
|
|
338
|
+
sticks[0].y = -round(touchGamepadStick.y);
|
|
329
339
|
sticks[0] = sticks[0].clampLength();
|
|
330
340
|
}
|
|
331
341
|
|
|
@@ -416,7 +426,7 @@ let touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadStick =
|
|
|
416
426
|
function touchGamepadButtonCenter()
|
|
417
427
|
{
|
|
418
428
|
// draw right face buttons
|
|
419
|
-
|
|
429
|
+
const center = vec2(mainCanvasSize.x-touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
420
430
|
if (touchGamepadButtonCount <= 2)
|
|
421
431
|
center.x += touchGamepadSize/2;
|
|
422
432
|
return center;
|
|
@@ -426,14 +436,13 @@ function touchGamepadButtonCenter()
|
|
|
426
436
|
function touchInputInit()
|
|
427
437
|
{
|
|
428
438
|
// add non passive touch event listeners
|
|
429
|
-
let handleTouch = handleTouchDefault;
|
|
430
439
|
document.addEventListener('touchstart', (e) => handleTouch(e), { passive: false });
|
|
431
440
|
document.addEventListener('touchmove', (e) => handleTouch(e), { passive: false });
|
|
432
441
|
document.addEventListener('touchend', (e) => handleTouch(e), { passive: false });
|
|
433
442
|
|
|
434
443
|
// handle all touch events the same way
|
|
435
444
|
let wasTouching;
|
|
436
|
-
function
|
|
445
|
+
function handleTouch(e)
|
|
437
446
|
{
|
|
438
447
|
if (!touchInputEnable)
|
|
439
448
|
return;
|
|
@@ -453,11 +462,11 @@ function touchInputInit()
|
|
|
453
462
|
{
|
|
454
463
|
// set event pos and pass it along
|
|
455
464
|
const pos = vec2(e.touches[0].clientX, e.touches[0].clientY);
|
|
456
|
-
const
|
|
465
|
+
const mousePosScreenLast = mousePosScreen;
|
|
457
466
|
mousePosScreen = mouseEventToScreen(pos);
|
|
458
467
|
if (wasTouching)
|
|
459
468
|
{
|
|
460
|
-
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(
|
|
469
|
+
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
|
|
461
470
|
isUsingGamepad = touchGamepadEnable;
|
|
462
471
|
}
|
|
463
472
|
else
|
|
@@ -489,7 +498,7 @@ function touchInputInit()
|
|
|
489
498
|
if (touching)
|
|
490
499
|
{
|
|
491
500
|
touchGamepadTimer.set();
|
|
492
|
-
if (
|
|
501
|
+
if (touchGamepadCenterButton && !wasTouching && paused)
|
|
493
502
|
{
|
|
494
503
|
// touch anywhere to press start when paused
|
|
495
504
|
touchGamepadButtons[9] = 1;
|
|
@@ -528,7 +537,8 @@ function touchInputInit()
|
|
|
528
537
|
if (button < touchGamepadButtonCount)
|
|
529
538
|
touchGamepadButtons[button] = 1;
|
|
530
539
|
}
|
|
531
|
-
else if (
|
|
540
|
+
else if (touchGamepadCenterButton && !wasTouching &&
|
|
541
|
+
startCenter.distance(touchPos) < touchGamepadSize)
|
|
532
542
|
{
|
|
533
543
|
// virtual start button in center
|
|
534
544
|
touchGamepadButtons[9] = 1;
|
|
@@ -583,7 +593,7 @@ function touchGamepadRender()
|
|
|
583
593
|
const buttonSize = touchGamepadButtonCount > 1 ? touchGamepadSize/4 : touchGamepadSize/2;
|
|
584
594
|
for (let i=0; i<touchGamepadButtonCount; i++)
|
|
585
595
|
{
|
|
586
|
-
|
|
596
|
+
const j = mod(i-1, 4);
|
|
587
597
|
let button = touchGamepadButtonCount > 2 ?
|
|
588
598
|
j : min(j, touchGamepadButtonCount-1);
|
|
589
599
|
// fix button locations (swap 2 and 3 to match gamepad layout)
|
package/src/engineMedals.js
CHANGED
|
@@ -167,11 +167,11 @@ class Medal
|
|
|
167
167
|
const descriptionSize = height*.3;
|
|
168
168
|
const pos = vec2(x + medalDisplayIconSize + 2*gap.x, y + gap.y*2 + nameSize/2);
|
|
169
169
|
const textWidth = width - medalDisplayIconSize - 3*gap.x;
|
|
170
|
-
drawTextScreen(this.name, pos, nameSize, BLACK, 0, undefined, 'left', undefined, textWidth);
|
|
170
|
+
drawTextScreen(this.name, pos, nameSize, BLACK, 0, undefined, 'left', undefined, undefined, textWidth);
|
|
171
171
|
|
|
172
172
|
// draw the description
|
|
173
173
|
pos.y = y + height - gap.y*2 - descriptionSize/2;
|
|
174
|
-
drawTextScreen(this.description, pos, descriptionSize, BLACK, 0, undefined, 'left', undefined, textWidth);
|
|
174
|
+
drawTextScreen(this.description, pos, descriptionSize, BLACK, 0, undefined, 'left', undefined, undefined, textWidth);
|
|
175
175
|
context.restore();
|
|
176
176
|
}
|
|
177
177
|
|
package/src/engineObject.js
CHANGED
|
@@ -135,11 +135,10 @@ class EngineObject
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
/** Update the object physics, called automatically by engine once each frame */
|
|
138
|
-
|
|
138
|
+
updatePhysics()
|
|
139
139
|
{
|
|
140
140
|
// child objects do not have physics
|
|
141
|
-
|
|
142
|
-
return;
|
|
141
|
+
ASSERT(!this.parent);
|
|
143
142
|
|
|
144
143
|
if (this.clampSpeed)
|
|
145
144
|
{
|
|
@@ -189,7 +188,7 @@ class EngineObject
|
|
|
189
188
|
continue;
|
|
190
189
|
|
|
191
190
|
// check collision
|
|
192
|
-
if (!
|
|
191
|
+
if (!this.isOverlappingObject(o))
|
|
193
192
|
continue;
|
|
194
193
|
|
|
195
194
|
// notify objects of collision and check if should be resolved
|
|
@@ -287,6 +286,27 @@ class EngineObject
|
|
|
287
286
|
// test which side we bounced off (or both if a corner)
|
|
288
287
|
const blockedLayerY = tileCollisionTest(vec2(oldPos.x, this.pos.y), this.size, this);
|
|
289
288
|
const blockedLayerX = tileCollisionTest(vec2(this.pos.x, oldPos.y), this.size, this);
|
|
289
|
+
|
|
290
|
+
if (blockedLayerX)
|
|
291
|
+
{
|
|
292
|
+
// try to move up a tiny bit
|
|
293
|
+
const epsilon = 1e-3;
|
|
294
|
+
const maxMoveUp = .1;
|
|
295
|
+
const y = floor(oldPos.y-this.size.y/2+1) +
|
|
296
|
+
this.size.y/2 + epsilon;
|
|
297
|
+
const delta = y - this.pos.y;
|
|
298
|
+
if (delta < maxMoveUp)
|
|
299
|
+
if (!tileCollisionTest(vec2(this.pos.x, y), this.size, this))
|
|
300
|
+
{
|
|
301
|
+
this.pos.y = y;
|
|
302
|
+
debugPhysics && debugRect(this.pos, this.size, '#ff0');
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// move to previous position and bounce
|
|
307
|
+
this.pos.x = oldPos.x;
|
|
308
|
+
this.velocity.x *= -this.restitution;
|
|
309
|
+
}
|
|
290
310
|
if (blockedLayerY || !blockedLayerX)
|
|
291
311
|
{
|
|
292
312
|
// bounce velocity
|
|
@@ -310,18 +330,15 @@ class EngineObject
|
|
|
310
330
|
this.groundObject = undefined;
|
|
311
331
|
}
|
|
312
332
|
}
|
|
313
|
-
if (blockedLayerX)
|
|
314
|
-
{
|
|
315
|
-
// move to previous position and bounce
|
|
316
|
-
this.pos.x = oldPos.x;
|
|
317
|
-
this.velocity.x *= -this.restitution;
|
|
318
|
-
}
|
|
319
333
|
debugPhysics && debugRect(this.pos, this.size, '#f00');
|
|
320
334
|
}
|
|
321
335
|
}
|
|
322
336
|
}
|
|
323
337
|
}
|
|
324
338
|
|
|
339
|
+
/** Update the object, called automatically by engine once each frame. Does nothing by default. */
|
|
340
|
+
update() {}
|
|
341
|
+
|
|
325
342
|
/** Render the object, draws a tile by default, automatically called each frame, sorted by renderOrder */
|
|
326
343
|
render()
|
|
327
344
|
{
|
|
@@ -373,10 +390,24 @@ class EngineObject
|
|
|
373
390
|
*/
|
|
374
391
|
collideWithObject(object) { return true; }
|
|
375
392
|
|
|
393
|
+
/** Get this object's up vector
|
|
394
|
+
* @param {number} [scale] - length of the vector
|
|
395
|
+
* @return {Vector2} */
|
|
396
|
+
getUp(scale=1) { return vec2().setAngle(this.angle, scale); }
|
|
397
|
+
|
|
398
|
+
/** Get this object's right vector
|
|
399
|
+
* @param {number} [scale] - length of the vector
|
|
400
|
+
* @return {Vector2} */
|
|
401
|
+
getRight(scale=1) { return vec2().setAngle(this.angle+PI/2, scale); }
|
|
402
|
+
|
|
376
403
|
/** How long since the object was created
|
|
377
404
|
* @return {number} */
|
|
378
405
|
getAliveTime() { return time - this.spawnTime; }
|
|
379
406
|
|
|
407
|
+
/** Get the speed of this object
|
|
408
|
+
* @return {number} */
|
|
409
|
+
getSpeed() { return this.velocity.length(); }
|
|
410
|
+
|
|
380
411
|
/** Apply acceleration to this object (adjust velocity, not affected by mass)
|
|
381
412
|
* @param {Vector2} acceleration */
|
|
382
413
|
applyAcceleration(acceleration)
|
|
@@ -421,6 +452,20 @@ class EngineObject
|
|
|
421
452
|
child.parent = 0;
|
|
422
453
|
}
|
|
423
454
|
|
|
455
|
+
/** Check if overlapping another engine object
|
|
456
|
+
* Collisions are resoloved to prevent overlaps
|
|
457
|
+
* @param {EngineObject} object
|
|
458
|
+
* @return {boolean} */
|
|
459
|
+
isOverlappingObject(object)
|
|
460
|
+
{ return this.isOverlapping(object.pos, object.size); }
|
|
461
|
+
|
|
462
|
+
/** Check if overlapping a point or aligned bounding box
|
|
463
|
+
* @param {Vector2} pos - Center of box
|
|
464
|
+
* @param {Vector2} [size=(0,0)] - Size of box, uses a point if undefined
|
|
465
|
+
* @return {boolean} */
|
|
466
|
+
isOverlapping(pos, size=vec2())
|
|
467
|
+
{ return isOverlapping(this.pos, this.size, pos, size); }
|
|
468
|
+
|
|
424
469
|
/** Set how this object collides
|
|
425
470
|
* @param {boolean} [collideSolidObjects] - Does it collide with solid objects?
|
|
426
471
|
* @param {boolean} [isSolid] - Does it collide with and block other objects? (expensive in large numbers)
|
package/src/engineParticles.js
CHANGED
|
@@ -152,19 +152,34 @@ class ParticleEmitter extends EngineObject
|
|
|
152
152
|
this.particleCreateCallback = undefined;
|
|
153
153
|
/** @property {number} - Track particle emit time */
|
|
154
154
|
this.emitTimeBuffer = 0;
|
|
155
|
+
/** @property {number} - Percentage of velocity to pass to particles (0-1) */
|
|
156
|
+
this.velocityInheritance = 0;
|
|
157
|
+
|
|
158
|
+
// track previous position and angle
|
|
159
|
+
this.previousAngle = this.angle;
|
|
160
|
+
this.previousPos = this.pos.copy();
|
|
155
161
|
}
|
|
156
162
|
|
|
157
163
|
/** Update the emitter to spawn particles, called automatically by engine once each frame */
|
|
158
164
|
update()
|
|
159
165
|
{
|
|
160
|
-
|
|
161
|
-
|
|
166
|
+
if (this.velocityInheritance)
|
|
167
|
+
{
|
|
168
|
+
// pass emitter velocity to particles
|
|
169
|
+
const p = this.velocityInheritance;
|
|
170
|
+
this.velocity.x = p * (this.pos.x - this.previousPos.x);
|
|
171
|
+
this.velocity.y = p * (this.pos.y - this.previousPos.y);
|
|
172
|
+
this.angleVelocity = p * (this.angle - this.previousAngle);
|
|
173
|
+
this.previousAngle = this.angle;
|
|
174
|
+
this.previousPos.x = this.pos.x;
|
|
175
|
+
this.previousPos.y = this.pos.y;
|
|
176
|
+
}
|
|
162
177
|
|
|
163
178
|
// update emitter
|
|
164
179
|
if (!this.emitTime || this.getAliveTime() <= this.emitTime)
|
|
165
180
|
{
|
|
166
181
|
// emit particles
|
|
167
|
-
if (this.emitRate
|
|
182
|
+
if (this.emitRate && particleEmitRateScale)
|
|
168
183
|
{
|
|
169
184
|
const rate = 1/this.emitRate/particleEmitRateScale;
|
|
170
185
|
for (this.emitTimeBuffer += timeDelta; this.emitTimeBuffer > 0; this.emitTimeBuffer -= rate)
|
|
@@ -217,7 +232,7 @@ class ParticleEmitter extends EngineObject
|
|
|
217
232
|
const particle = new Particle(pos, this.tileInfo, angle, colorStart, colorEnd, particleTime, sizeStart, sizeEnd, this.fadeRate, this.additive, this.trailScale, this.localSpace && this, this.particleDestroyCallback);
|
|
218
233
|
particle.velocity = vec2().setAngle(velocityAngle, speed);
|
|
219
234
|
particle.angleVelocity = angleSpeed;
|
|
220
|
-
if (!this.localSpace)
|
|
235
|
+
if (!this.localSpace && this.velocityInheritance > 0)
|
|
221
236
|
{
|
|
222
237
|
// apply emitter velocity to particle
|
|
223
238
|
particle.velocity.x += this.velocity.x;
|
|
@@ -302,8 +317,6 @@ class Particle extends EngineObject
|
|
|
302
317
|
/** Update the object physics, called automatically by engine once each frame */
|
|
303
318
|
update()
|
|
304
319
|
{
|
|
305
|
-
super.update();
|
|
306
|
-
|
|
307
320
|
if (this.collideTiles || this.collideSolidObjects)
|
|
308
321
|
{
|
|
309
322
|
// only apply max circular speed if particle can collide
|
|
@@ -315,6 +328,16 @@ class Particle extends EngineObject
|
|
|
315
328
|
this.velocity.y *= s;
|
|
316
329
|
}
|
|
317
330
|
}
|
|
331
|
+
|
|
332
|
+
if (this.lifeTime > 0 && time - this.spawnTime > this.lifeTime)
|
|
333
|
+
{
|
|
334
|
+
// destroy particle when its time runs out
|
|
335
|
+
const c = this.colorEnd;
|
|
336
|
+
this.color.set(c.r, c.g, c.b, c.a);
|
|
337
|
+
this.size.set(this.sizeEnd, this.sizeEnd);
|
|
338
|
+
this.destroyCallback && this.destroyCallback(this);
|
|
339
|
+
this.destroyed = 1;
|
|
340
|
+
}
|
|
318
341
|
}
|
|
319
342
|
|
|
320
343
|
/** Render the particle, automatically called each frame, sorted by renderOrder */
|
|
@@ -323,7 +346,7 @@ class Particle extends EngineObject
|
|
|
323
346
|
// lerp color and size
|
|
324
347
|
const p1 = this.lifeTime > 0 ? min((time - this.spawnTime) / this.lifeTime, 1) : 1, p2 = 1-p1;
|
|
325
348
|
const radius = p2 * this.sizeStart + p1 * this.sizeEnd;
|
|
326
|
-
|
|
349
|
+
const size = vec2(radius);
|
|
327
350
|
this.color.r = p2 * this.colorStart.r + p1 * this.colorEnd.r;
|
|
328
351
|
this.color.g = p2 * this.colorStart.g + p1 * this.colorEnd.g;
|
|
329
352
|
this.color.b = p2 * this.colorStart.b + p1 * this.colorEnd.b;
|
|
@@ -343,7 +366,7 @@ class Particle extends EngineObject
|
|
|
343
366
|
{
|
|
344
367
|
// in local space of emitter
|
|
345
368
|
const a = this.localSpaceEmitter.angle;
|
|
346
|
-
const c =
|
|
369
|
+
const c = cos(a), s = sin(a);
|
|
347
370
|
pos = this.localSpaceEmitter.pos.add(
|
|
348
371
|
new Vector2(pos.x*c - pos.y*s, pos.x*s + pos.y*c));
|
|
349
372
|
angle += this.localSpaceEmitter.angle;
|
|
@@ -359,21 +382,14 @@ class Particle extends EngineObject
|
|
|
359
382
|
{
|
|
360
383
|
// stretch in direction of motion
|
|
361
384
|
const trailLength = speed * this.trailScale;
|
|
362
|
-
|
|
363
|
-
angle =
|
|
364
|
-
drawTile(pos,
|
|
385
|
+
size.y = max(size.x, trailLength);
|
|
386
|
+
angle = atan2(direction.x, direction.y);
|
|
387
|
+
drawTile(pos, size, this.tileInfo, this.color, angle, this.mirror);
|
|
365
388
|
}
|
|
366
389
|
}
|
|
367
390
|
else
|
|
368
|
-
drawTile(pos,
|
|
391
|
+
drawTile(pos, size, this.tileInfo, this.color, angle, this.mirror);
|
|
369
392
|
this.additive && setBlendMode();
|
|
370
|
-
debugParticles && debugRect(pos,
|
|
371
|
-
|
|
372
|
-
if (p1 === 1)
|
|
373
|
-
{
|
|
374
|
-
// destroy particle when its time runs out
|
|
375
|
-
this.destroyCallback && this.destroyCallback(this);
|
|
376
|
-
this.destroyed = 1;
|
|
377
|
-
}
|
|
393
|
+
debugParticles && debugRect(pos, size, '#f005', 0, angle);
|
|
378
394
|
}
|
|
379
395
|
}
|
package/src/engineRelease.js
CHANGED
|
@@ -38,4 +38,5 @@ function debugShowErrors(){}
|
|
|
38
38
|
function debugVideoCaptureIsActive(){ return false; }
|
|
39
39
|
function debugVideoCaptureStart (){}
|
|
40
40
|
function debugVideoCaptureStop (){}
|
|
41
|
-
function debugVideoCaptureUpdate(){}
|
|
41
|
+
function debugVideoCaptureUpdate(){}
|
|
42
|
+
function debugProtectConstant(o){ return o; }
|
package/src/engineSettings.js
CHANGED
|
@@ -216,6 +216,14 @@ let touchInputEnable = true;
|
|
|
216
216
|
* @memberof Settings */
|
|
217
217
|
let touchGamepadEnable = false;
|
|
218
218
|
|
|
219
|
+
/** True if touch gamepad should have start button in the center
|
|
220
|
+
* - When the game is paused, any touch will press the button
|
|
221
|
+
* - This can function as a way to pause/unpause the game
|
|
222
|
+
* @type {boolean}
|
|
223
|
+
* @default
|
|
224
|
+
* @memberof Settings */
|
|
225
|
+
let touchGamepadCenterButton = true;
|
|
226
|
+
|
|
219
227
|
/** True if touch gamepad should be analog stick or false to use if 8 way dpad
|
|
220
228
|
* @type {boolean}
|
|
221
229
|
* @default
|
|
@@ -391,9 +399,14 @@ function setHeadlessMode(headless) { headlessMode = headless; }
|
|
|
391
399
|
* @memberof Settings */
|
|
392
400
|
function setGLEnable(enable)
|
|
393
401
|
{
|
|
402
|
+
if (enable && !glCanBeEnabled)
|
|
403
|
+
{
|
|
404
|
+
console.warn('Can not enable WebGL if it was disabled on start.');
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
394
407
|
glEnable = enable;
|
|
395
408
|
if (glCanvas) // hide glCanvas if WebGL is disabled
|
|
396
|
-
glCanvas.style.
|
|
409
|
+
glCanvas.style.display = enable ? '' : 'none';
|
|
397
410
|
}
|
|
398
411
|
|
|
399
412
|
/** Set how many sided polygons to use when drawing circles and ellipses with WebGL
|
|
@@ -481,6 +494,12 @@ function setTouchInputEnable(enable) { touchInputEnable = enable; }
|
|
|
481
494
|
* @memberof Settings */
|
|
482
495
|
function setTouchGamepadEnable(enable) { touchGamepadEnable = enable; }
|
|
483
496
|
|
|
497
|
+
/** True if touch gamepad should have start button in the center
|
|
498
|
+
* - This can function as a way to pause/unpause the game
|
|
499
|
+
* @param {boolean} enable
|
|
500
|
+
* @memberof Settings */
|
|
501
|
+
function setTouchGamepadCenterButton(enable) { touchGamepadCenterButton = enable; }
|
|
502
|
+
|
|
484
503
|
/** Set if touch gamepad should be analog stick or 8 way dpad
|
|
485
504
|
* @param {boolean} analog
|
|
486
505
|
* @memberof Settings */
|
package/src/engineTileLayer.js
CHANGED
|
@@ -47,23 +47,23 @@ function tileCollisionTest(pos, size=vec2(), object, solidOnly=true)
|
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
/** Return the
|
|
51
|
-
*
|
|
50
|
+
/** Return the exact position of the boudnary of first tile hit, undefined if nothing was hit.
|
|
51
|
+
* The point will be inside the colliding tile if it hits (may have a tiny shift)
|
|
52
52
|
* @param {Vector2} posStart
|
|
53
53
|
* @param {Vector2} posEnd
|
|
54
54
|
* @param {EngineObject} [object] - An object or undefined for generic test
|
|
55
|
+
* @param {Vector2} [normal] - Optional normal of the surface hit
|
|
55
56
|
* @param {boolean} [solidOnly=true] - Only check solid layers if true
|
|
56
|
-
* @return {Vector2}
|
|
57
|
+
* @return {Vector2|undefined} - position of the center of the tile hit or undefined if no hit
|
|
57
58
|
* @memberof TileLayers */
|
|
58
|
-
function tileCollisionRaycast(posStart, posEnd, object, solidOnly=true)
|
|
59
|
+
function tileCollisionRaycast(posStart, posEnd, object, normal, solidOnly=true)
|
|
59
60
|
{
|
|
60
61
|
for (const layer of tileCollisionLayers)
|
|
61
62
|
{
|
|
62
63
|
if (!solidOnly || layer.isSolid)
|
|
63
64
|
{
|
|
64
|
-
const hitPos = layer.collisionRaycast(posStart, posEnd, object)
|
|
65
|
-
if (hitPos)
|
|
66
|
-
return hitPos;
|
|
65
|
+
const hitPos = layer.collisionRaycast(posStart, posEnd, object, normal)
|
|
66
|
+
if (hitPos) return hitPos;
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
}
|
|
@@ -201,6 +201,8 @@ class CanvasLayer extends EngineObject
|
|
|
201
201
|
/** @property {TextureInfo} - Texture info to use for this object rendering */
|
|
202
202
|
const useWebGL = false; // do not use webgl by default
|
|
203
203
|
this.textureInfo = new TextureInfo(this.canvas, useWebGL);
|
|
204
|
+
/** @property {boolean} - True if WebGL texture needs to be refreshed */
|
|
205
|
+
this.refreshWebGL = false;
|
|
204
206
|
|
|
205
207
|
// disable physics by default
|
|
206
208
|
this.mass = this.gravityScale = this.friction = this.restitution = 0;
|
|
@@ -234,8 +236,15 @@ class CanvasLayer extends EngineObject
|
|
|
234
236
|
* @memberof Draw */
|
|
235
237
|
draw(pos, size, angle=0, color=WHITE, mirror=false, additiveColor, screenSpace=false, context)
|
|
236
238
|
{
|
|
237
|
-
// draw the canvas layer as a single tile that uses the whole texture
|
|
238
239
|
const useWebGL = glEnable && this.textureInfo.hasWebGL();
|
|
240
|
+
if (useWebGL && this.refreshWebGL)
|
|
241
|
+
{
|
|
242
|
+
// update the WebGL texture
|
|
243
|
+
this.textureInfo.createWebGLTexture();
|
|
244
|
+
this.refreshWebGL = false;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// draw the canvas layer as a single tile that uses the whole texture
|
|
239
248
|
const tileInfo = new TileInfo().setFullImage(this.textureInfo);
|
|
240
249
|
drawTile(pos, size, tileInfo, color, angle, mirror, additiveColor, useWebGL, screenSpace, context);
|
|
241
250
|
}
|
|
@@ -305,9 +314,18 @@ class CanvasLayer extends EngineObject
|
|
|
305
314
|
{ this.drawTile(pos, size, undefined, color, angle); }
|
|
306
315
|
|
|
307
316
|
/** Create or update the WebGL texture for this layer
|
|
308
|
-
* @param {boolean} [enable] - enable WebGL rendering and update the texture
|
|
309
|
-
|
|
317
|
+
* @param {boolean} [enable] - enable WebGL rendering and update the texture
|
|
318
|
+
* @param {boolean} [immediate] - shoulkd the texture be updated immediately
|
|
319
|
+
*/
|
|
320
|
+
useWebGL(enable=true, immediate=false)
|
|
310
321
|
{
|
|
322
|
+
if (!immediate && enable && this.textureInfo.hasWebGL())
|
|
323
|
+
{
|
|
324
|
+
// refresh the texture when needed
|
|
325
|
+
this.refreshWebGL = true;
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
311
329
|
if (enable)
|
|
312
330
|
this.textureInfo.createWebGLTexture();
|
|
313
331
|
else
|
|
@@ -338,7 +356,7 @@ class TileLayer extends CanvasLayer
|
|
|
338
356
|
*/
|
|
339
357
|
constructor(position, size, tileInfo=tile(), renderOrder=0)
|
|
340
358
|
{
|
|
341
|
-
const canvasSize = size.multiply(tileInfo.size);
|
|
359
|
+
const canvasSize = tileInfo ? size.multiply(tileInfo.size) : size;
|
|
342
360
|
super(position, size, 0, renderOrder, canvasSize);
|
|
343
361
|
|
|
344
362
|
// set tile info
|
|
@@ -391,6 +409,13 @@ class TileLayer extends CanvasLayer
|
|
|
391
409
|
{
|
|
392
410
|
ASSERT(drawContext !== this.context, 'must call redrawEnd() after drawing tiles!');
|
|
393
411
|
|
|
412
|
+
if (this.refreshWebGL)
|
|
413
|
+
{
|
|
414
|
+
// update the WebGL texture
|
|
415
|
+
this.textureInfo.createWebGLTexture();
|
|
416
|
+
this.refreshWebGL = false;
|
|
417
|
+
}
|
|
418
|
+
|
|
394
419
|
// draw the tile layer as a single tile
|
|
395
420
|
const tileInfo = new TileInfo().setFullImage(this.textureInfo);
|
|
396
421
|
const size = this.drawSize || this.size;
|
|
@@ -427,8 +452,9 @@ class TileLayer extends CanvasLayer
|
|
|
427
452
|
drawCanvas = this.canvas;
|
|
428
453
|
drawContext = this.context;
|
|
429
454
|
cameraPos = this.size.scale(.5);
|
|
430
|
-
|
|
431
|
-
|
|
455
|
+
const tileSize = this.tileInfo ? this.tileInfo.size : vec2(1);
|
|
456
|
+
cameraScale = tileSize.x;
|
|
457
|
+
mainCanvasSize = this.size.multiply(tileSize);
|
|
432
458
|
if (clear)
|
|
433
459
|
{
|
|
434
460
|
// clear and set size
|
|
@@ -588,65 +614,44 @@ class TileCollisionLayer extends TileLayer
|
|
|
588
614
|
// check if the object should collide with this tile
|
|
589
615
|
const tileData = this.collisionData[y*this.size.x+x];
|
|
590
616
|
if (tileData)
|
|
591
|
-
if (!object || object.collideWithTile(tileData,
|
|
617
|
+
if (!object || object.collideWithTile(tileData,
|
|
618
|
+
hitPos.set(x + this.pos.x, y + this.pos.y)))
|
|
592
619
|
return true;
|
|
593
620
|
}
|
|
594
621
|
return false;
|
|
595
622
|
}
|
|
596
623
|
|
|
597
|
-
/** Return the
|
|
598
|
-
*
|
|
624
|
+
/** Return the exact position of the boudnary of first tile hit, undefined if nothing was hit.
|
|
625
|
+
* The point will be inside the colliding tile if it hits (may have a tiny shift)
|
|
599
626
|
* @param {Vector2} posStart
|
|
600
627
|
* @param {Vector2} posEnd
|
|
601
|
-
* @param {EngineObject} [object]
|
|
602
|
-
* @
|
|
603
|
-
|
|
628
|
+
* @param {EngineObject} [object] - An object or undefined for generic test
|
|
629
|
+
* @param {Vector2} [normal] - Optional normal of the surface hit
|
|
630
|
+
* @return {Vector2|undefined} */
|
|
631
|
+
collisionRaycast(posStart, posEnd, object, normal)
|
|
604
632
|
{
|
|
605
633
|
ASSERT(isVector2(posStart) && isVector2(posEnd), 'positions must be Vector2s');
|
|
606
634
|
ASSERT(!object || object instanceof EngineObject, 'object must be an EngineObject');
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
const
|
|
610
|
-
const posStartY = posStart.y - this.pos.y;
|
|
611
|
-
const posEndX = posEnd.x - this.pos.x;
|
|
612
|
-
const posEndY = posEnd.y - this.pos.y;
|
|
613
|
-
|
|
614
|
-
// test if a ray collides with tiles from start to end
|
|
615
|
-
const deltaX = posEndX - posStartX;
|
|
616
|
-
const deltaY = posEndY - posStartY;
|
|
617
|
-
const totalLength = (deltaX**2 + deltaY**2)**.5;
|
|
618
|
-
const unitX = abs(totalLength/deltaX);
|
|
619
|
-
const unitY = abs(totalLength/deltaY);
|
|
620
|
-
|
|
621
|
-
// setup iteration variables
|
|
622
|
-
const pos = posStart.floor(), signDeltaX = sign(deltaX), signDeltaY = sign(deltaY);
|
|
623
|
-
let xi = unitX * (deltaX < 0 ? posStart.x - pos.x : pos.x - posStart.x + 1) || 0;
|
|
624
|
-
let yi = unitY * (deltaY < 0 ? posStart.y - pos.y : pos.y - posStart.y + 1) || 0;
|
|
625
|
-
|
|
626
|
-
// use line drawing algorithm to test for collisions
|
|
627
|
-
while (true)
|
|
635
|
+
|
|
636
|
+
const localPos = new Vector2;
|
|
637
|
+
const collisionTest = (pos)=>
|
|
628
638
|
{
|
|
629
639
|
// check for tile collision
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
pos.x += .5; pos.y += .5;
|
|
634
|
-
debugRaycast && debugLine(posStart, posEnd, '#f00', .02);
|
|
635
|
-
debugRaycast && debugPoint(pos, '#ff0');
|
|
636
|
-
return pos;
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
// check if past the end
|
|
640
|
-
if (xi >= totalLength && yi >= totalLength)
|
|
641
|
-
break;
|
|
642
|
-
|
|
643
|
-
// get coordinates of next tile to check
|
|
644
|
-
if (xi > yi)
|
|
645
|
-
pos.y += signDeltaY, yi += unitY;
|
|
646
|
-
else
|
|
647
|
-
pos.x += signDeltaX, xi += unitX;
|
|
640
|
+
localPos.set(pos.x - this.pos.x, pos.y - this.pos.y);
|
|
641
|
+
const tileData = this.getCollisionData(localPos);
|
|
642
|
+
return tileData && (!object || object.collideWithTile(tileData, pos));
|
|
648
643
|
}
|
|
649
|
-
|
|
650
644
|
debugRaycast && debugLine(posStart, posEnd, '#00f', .02);
|
|
645
|
+
const hitPos = lineTest(posStart, posEnd, collisionTest, normal);
|
|
646
|
+
if (hitPos)
|
|
647
|
+
{
|
|
648
|
+
const tilePos = hitPos.floor().add(vec2(.5));
|
|
649
|
+
debugRaycast && debugRect(tilePos, vec2(1), '#f008');
|
|
650
|
+
debugRaycast && debugLine(posStart, hitPos, '#f00', .02);
|
|
651
|
+
debugRaycast && debugPoint(hitPos, '#0f0');
|
|
652
|
+
debugRaycast && normal &&
|
|
653
|
+
debugLine(hitPos, hitPos.add(normal), '#ff0', .02);
|
|
654
|
+
return hitPos;
|
|
655
|
+
}
|
|
651
656
|
}
|
|
652
657
|
}
|