littlejsengine 1.14.16 → 1.14.23
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 +173 -50
- package/dist/littlejs.esm.js +892 -419
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +879 -419
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +854 -420
- package/examples/box2d/game.js +3 -2
- package/examples/box2d/tiles.png +0 -0
- package/examples/breakout/game.js +5 -5
- 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 +69 -55
- package/examples/module/game.js +6 -8
- package/examples/particles/index.html +6 -9
- package/examples/platformer/gameCharacter.js +1 -1
- package/examples/platformer/gameEffects.js +8 -3
- package/examples/platformer/gameObjects.js +3 -3
- package/examples/shorts/animation.js +2 -2
- package/examples/shorts/base.html +4 -1
- package/examples/shorts/cameraDrag.js +22 -0
- package/examples/shorts/debugDraw.js +38 -0
- package/examples/shorts/fps.js +90 -0
- package/examples/shorts/helloWorld.js +1 -1
- package/examples/shorts/hillGlideGame.js +1 -1
- package/examples/shorts/landerGame.js +1 -1
- package/examples/shorts/musicPlayer.js +1 -0
- package/examples/shorts/parallax.js +1 -1
- package/examples/shorts/postProcess.js +1 -1
- package/examples/shorts/sequencer.js +5 -5
- package/examples/shorts/shader.js +29 -0
- package/examples/shorts/spaceGame.js +1 -1
- package/examples/shorts/spriteAtlas.js +6 -6
- package/examples/shorts/starfield.js +1 -1
- package/examples/shorts/tileLayer.js +1 -1
- package/examples/shorts/tileRaycast.js +39 -0
- package/examples/shorts/tiles.png +0 -0
- package/examples/shorts/tiltedView.js +14 -6
- package/examples/shorts/topDown.js +1 -1
- package/examples/starter/game.js +6 -8
- package/examples/starter/index.html +2 -2
- package/examples/style.css +1 -0
- package/examples/typescript/game.js +5 -4
- package/examples/typescript/game.ts +5 -7
- package/examples/uiSystem/game.js +3 -2
- package/package.json +1 -1
- package/plugins/postProcess.js +71 -51
- package/plugins/uiSystem.js +76 -23
- package/src/engine.js +30 -16
- package/src/engineAudio.js +33 -23
- package/src/engineDebug.js +27 -0
- package/src/engineDraw.js +103 -49
- package/src/engineExport.js +13 -0
- package/src/engineInput.js +14 -16
- package/src/engineObject.js +31 -6
- package/src/engineParticles.js +37 -16
- package/src/engineRelease.js +2 -1
- package/src/engineSettings.js +20 -1
- package/src/engineTileLayer.js +89 -94
- package/src/engineUtilities.js +206 -58
- package/src/engineWebGL.js +145 -69
- package/examples/shorts/raycasting.js +0 -79
package/src/engineDraw.js
CHANGED
|
@@ -173,16 +173,15 @@ class TileInfo
|
|
|
173
173
|
}
|
|
174
174
|
|
|
175
175
|
/**
|
|
176
|
-
* Set this tile to use a full image
|
|
177
|
-
* @param {
|
|
178
|
-
* @param {WebGLTexture} [glTexture] - WebGL texture
|
|
176
|
+
* Set this tile to use a full image in a texture info
|
|
177
|
+
* @param {TextureInfo} textureInfo
|
|
179
178
|
* @return {TileInfo}
|
|
180
179
|
*/
|
|
181
|
-
setFullImage(
|
|
180
|
+
setFullImage(textureInfo)
|
|
182
181
|
{
|
|
183
182
|
this.pos = new Vector2;
|
|
184
|
-
this.size =
|
|
185
|
-
this.textureInfo =
|
|
183
|
+
this.size = textureInfo.size.copy();
|
|
184
|
+
this.textureInfo = textureInfo;
|
|
186
185
|
// do not use padding or bleed
|
|
187
186
|
this.bleedScale = this.padding = 0;
|
|
188
187
|
return this;
|
|
@@ -198,26 +197,30 @@ class TextureInfo
|
|
|
198
197
|
/**
|
|
199
198
|
* Create a TextureInfo, called automatically by the engine
|
|
200
199
|
* @param {HTMLImageElement|OffscreenCanvas} image
|
|
201
|
-
* @param {
|
|
200
|
+
* @param {boolean} [useWebGL] - Should use WebGL if available?
|
|
202
201
|
*/
|
|
203
|
-
constructor(image,
|
|
202
|
+
constructor(image, useWebGL=true)
|
|
204
203
|
{
|
|
205
|
-
/** @property {HTMLImageElement} - image source */
|
|
204
|
+
/** @property {HTMLImageElement|OffscreenCanvas} - image source */
|
|
206
205
|
this.image = image;
|
|
207
206
|
/** @property {Vector2} - size of the image */
|
|
208
|
-
this.size = vec2(image.width, image.height);
|
|
207
|
+
this.size = image ? vec2(image.width, image.height) : vec2();
|
|
209
208
|
/** @property {Vector2} - inverse of the size, cached for rendering */
|
|
210
|
-
this.sizeInverse = vec2(1/image.width, 1/image.height);
|
|
209
|
+
this.sizeInverse = image ? vec2(1/image.width, 1/image.height) : vec2();
|
|
211
210
|
/** @property {WebGLTexture} - WebGL texture */
|
|
212
|
-
this.glTexture =
|
|
211
|
+
this.glTexture = undefined;
|
|
212
|
+
useWebGL && this.createWebGLTexture();
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
|
|
216
|
-
{
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
215
|
+
/** Creates the WebGL texture, updates if already created */
|
|
216
|
+
createWebGLTexture() { glRegisterTextureInfo(this); }
|
|
217
|
+
|
|
218
|
+
/** Destroys the WebGL texture */
|
|
219
|
+
destroyWebGLTexture() { glUnregisterTextureInfo(this); }
|
|
220
|
+
|
|
221
|
+
/** Check if the texture is webgl enabled
|
|
222
|
+
* @return {boolean} */
|
|
223
|
+
hasWebGL() { return !!this.glTexture; }
|
|
221
224
|
}
|
|
222
225
|
|
|
223
226
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -249,6 +252,7 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
|
|
|
249
252
|
const bleedScale = tileInfo ? tileInfo.bleedScale : 0;
|
|
250
253
|
if (useWebGL)
|
|
251
254
|
{
|
|
255
|
+
ASSERT(!!glContext, 'WebGL is not enabled!');
|
|
252
256
|
if (screenSpace)
|
|
253
257
|
{
|
|
254
258
|
// convert to world space
|
|
@@ -344,6 +348,7 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
|
|
|
344
348
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
345
349
|
if (useWebGL)
|
|
346
350
|
{
|
|
351
|
+
ASSERT(!!glContext, 'WebGL is not enabled!');
|
|
347
352
|
if (screenSpace)
|
|
348
353
|
{
|
|
349
354
|
// convert to world space
|
|
@@ -355,7 +360,7 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
|
|
|
355
360
|
const halfSizeX = size.x/2, halfSizeY = size.y/2;
|
|
356
361
|
const colorTopInt = colorTop.rgbaInt();
|
|
357
362
|
const colorBottomInt = colorBottom.rgbaInt();
|
|
358
|
-
const c =
|
|
363
|
+
const c = cos(-angle), s = sin(-angle);
|
|
359
364
|
for (let i=4; i--;)
|
|
360
365
|
{
|
|
361
366
|
const x = i & 1 ? halfSizeX : -halfSizeX;
|
|
@@ -406,6 +411,7 @@ function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0,
|
|
|
406
411
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
407
412
|
if (useWebGL)
|
|
408
413
|
{
|
|
414
|
+
ASSERT(!!glContext, 'WebGL is not enabled!');
|
|
409
415
|
let scale = 1;
|
|
410
416
|
if (screenSpace)
|
|
411
417
|
{
|
|
@@ -455,6 +461,8 @@ function drawLine(posA, posB, width=.1, color, pos=vec2(), angle=0, useWebGL, sc
|
|
|
455
461
|
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
456
462
|
const size = vec2(width, halfDelta.length()*2);
|
|
457
463
|
pos = pos.add(posA.add(halfDelta));
|
|
464
|
+
if (screenSpace)
|
|
465
|
+
halfDelta.y *= -1; // flip angle Y if screen space
|
|
458
466
|
angle += halfDelta.angle();
|
|
459
467
|
drawRect(pos, size, color, angle, useWebGL, screenSpace, context);
|
|
460
468
|
}
|
|
@@ -482,7 +490,7 @@ function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, l
|
|
|
482
490
|
for (let i=sides; i--;)
|
|
483
491
|
{
|
|
484
492
|
const a = (i/sides)*PI*2;
|
|
485
|
-
points.push(vec2(
|
|
493
|
+
points.push(vec2(sin(a)*sizeX, cos(a)*sizeY));
|
|
486
494
|
}
|
|
487
495
|
drawPoly(points, color, lineWidth, lineColor, pos, angle, useWebGL, screenSpace, context);
|
|
488
496
|
}
|
|
@@ -509,6 +517,7 @@ function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(),
|
|
|
509
517
|
|
|
510
518
|
if (useWebGL)
|
|
511
519
|
{
|
|
520
|
+
ASSERT(!!glContext, 'WebGL is not enabled!');
|
|
512
521
|
let scale = 1;
|
|
513
522
|
if (screenSpace)
|
|
514
523
|
{
|
|
@@ -650,12 +659,13 @@ function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpac
|
|
|
650
659
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
651
660
|
* @param {CanvasTextAlign} [textAlign='center']
|
|
652
661
|
* @param {string} [font=fontDefault]
|
|
662
|
+
* @param {string} [fontStyle]
|
|
653
663
|
* @param {number} [maxWidth]
|
|
654
664
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
655
665
|
* @memberof Draw */
|
|
656
|
-
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, maxWidth, context=drawContext)
|
|
666
|
+
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth, context=drawContext)
|
|
657
667
|
{
|
|
658
|
-
drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, maxWidth, context);
|
|
668
|
+
drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, fontStyle, maxWidth, context);
|
|
659
669
|
}
|
|
660
670
|
|
|
661
671
|
/** Draw text on overlay canvas in world space
|
|
@@ -668,11 +678,12 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
|
|
|
668
678
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
669
679
|
* @param {CanvasTextAlign} [textAlign='center']
|
|
670
680
|
* @param {string} [font=fontDefault]
|
|
681
|
+
* @param {string} [fontStyle]
|
|
671
682
|
* @param {number} [maxWidth]
|
|
672
683
|
* @memberof Draw */
|
|
673
|
-
function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, maxWidth)
|
|
684
|
+
function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth)
|
|
674
685
|
{
|
|
675
|
-
drawText(text, pos, size, color, lineWidth, lineColor, textAlign, font, maxWidth, overlayContext);
|
|
686
|
+
drawText(text, pos, size, color, lineWidth, lineColor, textAlign, font, fontStyle, maxWidth, overlayContext);
|
|
676
687
|
}
|
|
677
688
|
|
|
678
689
|
/** Draw text on overlay canvas in screen space
|
|
@@ -685,10 +696,11 @@ function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textA
|
|
|
685
696
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
686
697
|
* @param {CanvasTextAlign} [textAlign]
|
|
687
698
|
* @param {string} [font=fontDefault]
|
|
699
|
+
* @param {string} [fontStyle]
|
|
688
700
|
* @param {number} [maxWidth]
|
|
689
701
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
|
|
690
702
|
* @memberof Draw */
|
|
691
|
-
function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, maxWidth, context=overlayContext)
|
|
703
|
+
function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, context=overlayContext)
|
|
692
704
|
{
|
|
693
705
|
ASSERT(isString(text), 'text must be a string');
|
|
694
706
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
@@ -696,15 +708,15 @@ function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=B
|
|
|
696
708
|
ASSERT(isColor(color), 'color must be a color');
|
|
697
709
|
ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
|
|
698
710
|
ASSERT(isColor(lineColor), 'lineColor must be a color');
|
|
699
|
-
ASSERT(isColor(lineColor), 'lineColor must be a color');
|
|
700
711
|
ASSERT(['left','center','right'].includes(textAlign), 'align must be left, center, or right');
|
|
701
712
|
ASSERT(isString(font), 'font must be a string');
|
|
713
|
+
ASSERT(isString(fontStyle), 'fontStyle must be a string');
|
|
702
714
|
|
|
703
715
|
context.fillStyle = color.toString();
|
|
704
716
|
context.strokeStyle = lineColor.toString();
|
|
705
717
|
context.lineWidth = lineWidth;
|
|
706
718
|
context.textAlign = textAlign;
|
|
707
|
-
context.font = size + 'px '+ font;
|
|
719
|
+
context.font = fontStyle + ' ' + size + 'px '+ font;
|
|
708
720
|
context.textBaseline = 'middle';
|
|
709
721
|
|
|
710
722
|
const lines = (text+'').split('\n');
|
|
@@ -727,18 +739,18 @@ function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=B
|
|
|
727
739
|
* @memberof Draw */
|
|
728
740
|
function screenToWorld(screenPos)
|
|
729
741
|
{
|
|
730
|
-
let
|
|
731
|
-
let
|
|
742
|
+
let x = (screenPos.x - mainCanvasSize.x/2 + .5) / cameraScale;
|
|
743
|
+
let y = (screenPos.y - mainCanvasSize.y/2 + .5) / -cameraScale;
|
|
732
744
|
if (cameraAngle)
|
|
733
745
|
{
|
|
734
746
|
// apply camera rotation
|
|
735
|
-
const
|
|
736
|
-
const rotatedX =
|
|
737
|
-
const rotatedY =
|
|
738
|
-
|
|
739
|
-
|
|
747
|
+
const c = cos(-cameraAngle), s = sin(-cameraAngle);
|
|
748
|
+
const rotatedX = x * c - y * s;
|
|
749
|
+
const rotatedY = x * s + y * c;
|
|
750
|
+
x = rotatedX;
|
|
751
|
+
y = rotatedY;
|
|
740
752
|
}
|
|
741
|
-
return new Vector2(
|
|
753
|
+
return new Vector2(x + cameraPos.x, y + cameraPos.y);
|
|
742
754
|
}
|
|
743
755
|
|
|
744
756
|
/** Convert from world to screen space coordinates
|
|
@@ -747,24 +759,64 @@ function screenToWorld(screenPos)
|
|
|
747
759
|
* @memberof Draw */
|
|
748
760
|
function worldToScreen(worldPos)
|
|
749
761
|
{
|
|
750
|
-
let
|
|
751
|
-
let
|
|
762
|
+
let x = worldPos.x - cameraPos.x;
|
|
763
|
+
let y = worldPos.y - cameraPos.y;
|
|
752
764
|
if (cameraAngle)
|
|
753
765
|
{
|
|
754
766
|
// apply inverse camera rotation
|
|
755
|
-
const
|
|
756
|
-
const rotatedX =
|
|
757
|
-
const rotatedY =
|
|
758
|
-
|
|
759
|
-
|
|
767
|
+
const c = cos(cameraAngle), s = sin(cameraAngle);
|
|
768
|
+
const rotatedX = x * c - y * s;
|
|
769
|
+
const rotatedY = x * s + y * c;
|
|
770
|
+
x = rotatedX;
|
|
771
|
+
y = rotatedY;
|
|
760
772
|
}
|
|
761
773
|
return new Vector2
|
|
762
774
|
(
|
|
763
|
-
|
|
764
|
-
|
|
775
|
+
x * cameraScale + mainCanvasSize.x/2 - .5,
|
|
776
|
+
y * -cameraScale + mainCanvasSize.y/2 - .5
|
|
765
777
|
);
|
|
766
778
|
}
|
|
767
779
|
|
|
780
|
+
/** Convert from screen to world space coordinates for a directional vector (no translation)
|
|
781
|
+
* @param {Vector2} screenDelta
|
|
782
|
+
* @return {Vector2}
|
|
783
|
+
* @memberof Draw */
|
|
784
|
+
function screenToWorldDelta(screenDelta)
|
|
785
|
+
{
|
|
786
|
+
let x = screenDelta.x / cameraScale;
|
|
787
|
+
let y = screenDelta.y / -cameraScale;
|
|
788
|
+
if (cameraAngle)
|
|
789
|
+
{
|
|
790
|
+
// apply camera rotation
|
|
791
|
+
const c = cos(-cameraAngle), s = sin(-cameraAngle);
|
|
792
|
+
const rotatedX = x * c - y * s;
|
|
793
|
+
const rotatedY = x * s + y * c;
|
|
794
|
+
x = rotatedX;
|
|
795
|
+
y = rotatedY;
|
|
796
|
+
}
|
|
797
|
+
return new Vector2(x, y);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/** Convert from screen to world space coordinates for a directional vector (no translation)
|
|
801
|
+
* @param {Vector2} worldDelta
|
|
802
|
+
* @return {Vector2}
|
|
803
|
+
* @memberof Draw */
|
|
804
|
+
function worldToScreenDelta(worldDelta)
|
|
805
|
+
{
|
|
806
|
+
let x = worldDelta.x;
|
|
807
|
+
let y = worldDelta.y;
|
|
808
|
+
if (cameraAngle)
|
|
809
|
+
{
|
|
810
|
+
// apply inverse camera rotation
|
|
811
|
+
const c = cos(cameraAngle), s = sin(cameraAngle);
|
|
812
|
+
const rotatedX = x * c - y * s;
|
|
813
|
+
const rotatedY = x * s + y * c;
|
|
814
|
+
x = rotatedX;
|
|
815
|
+
y = rotatedY;
|
|
816
|
+
}
|
|
817
|
+
return new Vector2(x * cameraScale, y * -cameraScale);
|
|
818
|
+
}
|
|
819
|
+
|
|
768
820
|
/** Get the camera's visible area in world space
|
|
769
821
|
* @return {Vector2}
|
|
770
822
|
* @memberof Draw */
|
|
@@ -817,6 +869,8 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
|
|
|
817
869
|
function isBlack(c) { return c.r <= 0 && c.g <= 0 && c.b <= 0 && c.a <= 0; }
|
|
818
870
|
const sx2 = bleedScale;
|
|
819
871
|
const sy2 = bleedScale;
|
|
872
|
+
sWidth = max(1,sWidth|0);
|
|
873
|
+
sHeight = max(1,sHeight|0);
|
|
820
874
|
const sWidth2 = sWidth - 2*bleedScale;
|
|
821
875
|
const sHeight2 = sHeight - 2*bleedScale;
|
|
822
876
|
if (!canvasColorTiles || (additiveColor ? isWhite(color.add(additiveColor)) && additiveColor.a <= 0 : isWhite(color)))
|
|
@@ -831,7 +885,7 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
|
|
|
831
885
|
// copy to offscreen canvas
|
|
832
886
|
workCanvas.width = sWidth;
|
|
833
887
|
workCanvas.height = sHeight;
|
|
834
|
-
workContext.drawImage(image, sx, sy, sWidth, sHeight, 0, 0, sWidth, sHeight);
|
|
888
|
+
workContext.drawImage(image, sx|0, sy|0, sWidth, sHeight, 0, 0, sWidth, sHeight);
|
|
835
889
|
|
|
836
890
|
// tint image using offscreen work context
|
|
837
891
|
const imageData = workContext.getImageData(0, 0, sWidth, sHeight);
|
|
@@ -912,11 +966,11 @@ let engineFontImage;
|
|
|
912
966
|
class FontImage
|
|
913
967
|
{
|
|
914
968
|
/** Create an image font
|
|
915
|
-
* @param {HTMLImageElement} [image]
|
|
916
|
-
* @param {Vector2} [tileSize=(8,8)]
|
|
917
|
-
* @param {Vector2} [paddingSize=(0,1)] - How much
|
|
969
|
+
* @param {HTMLImageElement} [image] - Image for the font, default if undefined
|
|
970
|
+
* @param {Vector2} [tileSize=(8,8)] - Size of the font source tiles
|
|
971
|
+
* @param {Vector2} [paddingSize=(0,1)] - How much space between characters
|
|
918
972
|
*/
|
|
919
|
-
constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1)
|
|
973
|
+
constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1))
|
|
920
974
|
{
|
|
921
975
|
// load default font image
|
|
922
976
|
if (!engineFontImage)
|
package/src/engineExport.js
CHANGED
|
@@ -79,6 +79,7 @@ export
|
|
|
79
79
|
gamepadDirectionEmulateStick,
|
|
80
80
|
inputWASDEmulateDirection,
|
|
81
81
|
touchGamepadEnable,
|
|
82
|
+
touchGamepadCenterButton,
|
|
82
83
|
touchGamepadAnalog,
|
|
83
84
|
touchGamepadSize,
|
|
84
85
|
touchGamepadAlpha,
|
|
@@ -122,6 +123,7 @@ export
|
|
|
122
123
|
setGamepadDirectionEmulateStick,
|
|
123
124
|
setInputWASDEmulateDirection,
|
|
124
125
|
setTouchGamepadEnable,
|
|
126
|
+
setTouchGamepadCenterButton,
|
|
125
127
|
setTouchGamepadAnalog,
|
|
126
128
|
setTouchGamepadSize,
|
|
127
129
|
setTouchGamepadAlpha,
|
|
@@ -140,9 +142,18 @@ export
|
|
|
140
142
|
// Utilities
|
|
141
143
|
PI,
|
|
142
144
|
abs,
|
|
145
|
+
floor,
|
|
146
|
+
ceil,
|
|
147
|
+
round,
|
|
143
148
|
min,
|
|
144
149
|
max,
|
|
145
150
|
sign,
|
|
151
|
+
hypot,
|
|
152
|
+
log2,
|
|
153
|
+
sin,
|
|
154
|
+
cos,
|
|
155
|
+
tan,
|
|
156
|
+
atan2,
|
|
146
157
|
mod,
|
|
147
158
|
clamp,
|
|
148
159
|
percent,
|
|
@@ -210,6 +221,8 @@ export
|
|
|
210
221
|
drawCount,
|
|
211
222
|
screenToWorld,
|
|
212
223
|
worldToScreen,
|
|
224
|
+
screenToWorldDelta,
|
|
225
|
+
worldToScreenDelta,
|
|
213
226
|
drawTile,
|
|
214
227
|
drawRect,
|
|
215
228
|
drawRectGradient,
|
package/src/engineInput.js
CHANGED
|
@@ -182,9 +182,9 @@ function inputUpdate()
|
|
|
182
182
|
if(!(touchInputEnable && isTouchDevice) && !document.hasFocus())
|
|
183
183
|
inputClear();
|
|
184
184
|
|
|
185
|
-
// update mouse world space position
|
|
185
|
+
// update mouse world space position and delta
|
|
186
186
|
mousePos = screenToWorld(mousePosScreen);
|
|
187
|
-
mouseDelta = mouseDeltaScreen
|
|
187
|
+
mouseDelta = screenToWorldDelta(mouseDeltaScreen);
|
|
188
188
|
|
|
189
189
|
// update gamepads if enabled
|
|
190
190
|
gamepadsUpdate();
|
|
@@ -216,7 +216,6 @@ function inputInit()
|
|
|
216
216
|
document.addEventListener('wheel', onMouseWheel);
|
|
217
217
|
document.addEventListener('contextmenu', onContextMenu);
|
|
218
218
|
document.addEventListener('blur', onBlur);
|
|
219
|
-
document.addEventListener('mouseleave', onMouseLeave);
|
|
220
219
|
|
|
221
220
|
// init touch input
|
|
222
221
|
if (isTouchDevice && touchInputEnable)
|
|
@@ -258,7 +257,10 @@ function inputInit()
|
|
|
258
257
|
|
|
259
258
|
isUsingGamepad = false;
|
|
260
259
|
inputData[0][e.button] = 3;
|
|
260
|
+
|
|
261
|
+
let mousePosScreenLast = mousePosScreen;
|
|
261
262
|
mousePosScreen = mouseEventToScreen(vec2(e.x,e.y));
|
|
263
|
+
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
|
|
262
264
|
inputPreventDefault && e.button && e.preventDefault();
|
|
263
265
|
}
|
|
264
266
|
function onMouseUp(e)
|
|
@@ -269,18 +271,13 @@ function inputInit()
|
|
|
269
271
|
}
|
|
270
272
|
function onMouseMove(e)
|
|
271
273
|
{
|
|
274
|
+
let mousePosScreenLast = mousePosScreen;
|
|
272
275
|
mousePosScreen = mouseEventToScreen(vec2(e.x,e.y));
|
|
273
|
-
mouseDeltaScreen = mouseDeltaScreen.add(
|
|
276
|
+
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
|
|
274
277
|
}
|
|
275
278
|
function onMouseWheel(e) { mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY); }
|
|
276
279
|
function onContextMenu(e) { e.preventDefault(); } // prevent right click menu
|
|
277
280
|
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
281
|
}
|
|
285
282
|
|
|
286
283
|
// convert a mouse or touch event position to screen space
|
|
@@ -324,8 +321,8 @@ function gamepadsUpdate()
|
|
|
324
321
|
else if (touchGamepadStick.lengthSquared() > .3)
|
|
325
322
|
{
|
|
326
323
|
// convert to 8 way dpad
|
|
327
|
-
sticks[0].x =
|
|
328
|
-
sticks[0].y = -
|
|
324
|
+
sticks[0].x = round(touchGamepadStick.x);
|
|
325
|
+
sticks[0].y = -round(touchGamepadStick.y);
|
|
329
326
|
sticks[0] = sticks[0].clampLength();
|
|
330
327
|
}
|
|
331
328
|
|
|
@@ -453,11 +450,11 @@ function touchInputInit()
|
|
|
453
450
|
{
|
|
454
451
|
// set event pos and pass it along
|
|
455
452
|
const pos = vec2(e.touches[0].clientX, e.touches[0].clientY);
|
|
456
|
-
const
|
|
453
|
+
const mousePosScreenLast = mousePosScreen;
|
|
457
454
|
mousePosScreen = mouseEventToScreen(pos);
|
|
458
455
|
if (wasTouching)
|
|
459
456
|
{
|
|
460
|
-
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(
|
|
457
|
+
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
|
|
461
458
|
isUsingGamepad = touchGamepadEnable;
|
|
462
459
|
}
|
|
463
460
|
else
|
|
@@ -489,7 +486,7 @@ function touchInputInit()
|
|
|
489
486
|
if (touching)
|
|
490
487
|
{
|
|
491
488
|
touchGamepadTimer.set();
|
|
492
|
-
if (
|
|
489
|
+
if (touchGamepadCenterButton && !wasTouching && paused)
|
|
493
490
|
{
|
|
494
491
|
// touch anywhere to press start when paused
|
|
495
492
|
touchGamepadButtons[9] = 1;
|
|
@@ -528,7 +525,8 @@ function touchInputInit()
|
|
|
528
525
|
if (button < touchGamepadButtonCount)
|
|
529
526
|
touchGamepadButtons[button] = 1;
|
|
530
527
|
}
|
|
531
|
-
else if (
|
|
528
|
+
else if (touchGamepadCenterButton && !wasTouching &&
|
|
529
|
+
startCenter.distance(touchPos) < touchGamepadSize)
|
|
532
530
|
{
|
|
533
531
|
// virtual start button in center
|
|
534
532
|
touchGamepadButtons[9] = 1;
|
package/src/engineObject.js
CHANGED
|
@@ -287,6 +287,27 @@ class EngineObject
|
|
|
287
287
|
// test which side we bounced off (or both if a corner)
|
|
288
288
|
const blockedLayerY = tileCollisionTest(vec2(oldPos.x, this.pos.y), this.size, this);
|
|
289
289
|
const blockedLayerX = tileCollisionTest(vec2(this.pos.x, oldPos.y), this.size, this);
|
|
290
|
+
|
|
291
|
+
if (blockedLayerX)
|
|
292
|
+
{
|
|
293
|
+
// try to move up a tiny bit
|
|
294
|
+
const epsilon = 1e-3;
|
|
295
|
+
const maxMoveUp = .1;
|
|
296
|
+
const y = floor(oldPos.y-this.size.y/2+1) +
|
|
297
|
+
this.size.y/2 + epsilon;
|
|
298
|
+
const delta = y - this.pos.y;
|
|
299
|
+
if (delta < maxMoveUp)
|
|
300
|
+
if (!tileCollisionTest(vec2(this.pos.x, y), this.size, this))
|
|
301
|
+
{
|
|
302
|
+
this.pos.y = y;
|
|
303
|
+
debugPhysics && debugRect(this.pos, this.size, '#ff0');
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// move to previous position and bounce
|
|
308
|
+
this.pos.x = oldPos.x;
|
|
309
|
+
this.velocity.x *= -this.restitution;
|
|
310
|
+
}
|
|
290
311
|
if (blockedLayerY || !blockedLayerX)
|
|
291
312
|
{
|
|
292
313
|
// bounce velocity
|
|
@@ -310,12 +331,6 @@ class EngineObject
|
|
|
310
331
|
this.groundObject = undefined;
|
|
311
332
|
}
|
|
312
333
|
}
|
|
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
334
|
debugPhysics && debugRect(this.pos, this.size, '#f00');
|
|
320
335
|
}
|
|
321
336
|
}
|
|
@@ -373,6 +388,16 @@ class EngineObject
|
|
|
373
388
|
*/
|
|
374
389
|
collideWithObject(object) { return true; }
|
|
375
390
|
|
|
391
|
+
/** Get this object's up vector
|
|
392
|
+
* @param {number} [scale] - length of the vector
|
|
393
|
+
* @return {Vector2} */
|
|
394
|
+
getUp(scale=1) { return vec2().setAngle(this.angle, scale); }
|
|
395
|
+
|
|
396
|
+
/** Get this object's right vector
|
|
397
|
+
* @param {number} [scale] - length of the vector
|
|
398
|
+
* @return {Vector2} */
|
|
399
|
+
getRight(scale=1) { return vec2().setAngle(this.angle+PI/2, scale); }
|
|
400
|
+
|
|
376
401
|
/** How long since the object was created
|
|
377
402
|
* @return {number} */
|
|
378
403
|
getAliveTime() { return time - this.spawnTime; }
|
package/src/engineParticles.js
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* tile(0, 16), // tileInfo
|
|
24
24
|
* rgb(1,1,1,1), rgb(0,0,0,1), // colorStartA, colorStartB
|
|
25
25
|
* rgb(1,1,1,0), rgb(0,0,0,0), // colorEndA, colorEndB
|
|
26
|
-
*
|
|
26
|
+
* 1, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
|
|
27
27
|
* .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate,
|
|
28
28
|
* .5, 1 // randomness, collide, additive, randomColorLinear, renderOrder
|
|
29
29
|
* );
|
|
@@ -152,6 +152,12 @@ 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 */
|
|
@@ -160,6 +166,18 @@ class ParticleEmitter extends EngineObject
|
|
|
160
166
|
// only do default update to apply parent transforms
|
|
161
167
|
this.parent && super.update();
|
|
162
168
|
|
|
169
|
+
if (this.velocityInheritance)
|
|
170
|
+
{
|
|
171
|
+
// pass emitter velocity to particles
|
|
172
|
+
const p = this.velocityInheritance;
|
|
173
|
+
this.velocity.x = p * (this.pos.x - this.previousPos.x);
|
|
174
|
+
this.velocity.y = p * (this.pos.y - this.previousPos.y);
|
|
175
|
+
this.angleVelocity = p * (this.angle - this.previousAngle);
|
|
176
|
+
this.previousAngle = this.angle;
|
|
177
|
+
this.previousPos.x = this.pos.x;
|
|
178
|
+
this.previousPos.y = this.pos.y;
|
|
179
|
+
}
|
|
180
|
+
|
|
163
181
|
// update emitter
|
|
164
182
|
if (!this.emitTime || this.getAliveTime() <= this.emitTime)
|
|
165
183
|
{
|
|
@@ -217,7 +235,7 @@ class ParticleEmitter extends EngineObject
|
|
|
217
235
|
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
236
|
particle.velocity = vec2().setAngle(velocityAngle, speed);
|
|
219
237
|
particle.angleVelocity = angleSpeed;
|
|
220
|
-
if (!this.localSpace)
|
|
238
|
+
if (!this.localSpace && this.velocityInheritance > 0)
|
|
221
239
|
{
|
|
222
240
|
// apply emitter velocity to particle
|
|
223
241
|
particle.velocity.x += this.velocity.x;
|
|
@@ -315,6 +333,16 @@ class Particle extends EngineObject
|
|
|
315
333
|
this.velocity.y *= s;
|
|
316
334
|
}
|
|
317
335
|
}
|
|
336
|
+
|
|
337
|
+
if (this.lifeTime > 0 && time - this.spawnTime > this.lifeTime)
|
|
338
|
+
{
|
|
339
|
+
// destroy particle when its time runs out
|
|
340
|
+
const c = this.colorEnd;
|
|
341
|
+
this.color.set(c.r, c.g, c.b, c.a);
|
|
342
|
+
this.size.set(this.sizeEnd, this.sizeEnd);
|
|
343
|
+
this.destroyCallback && this.destroyCallback(this);
|
|
344
|
+
this.destroyed = 1;
|
|
345
|
+
}
|
|
318
346
|
}
|
|
319
347
|
|
|
320
348
|
/** Render the particle, automatically called each frame, sorted by renderOrder */
|
|
@@ -323,7 +351,7 @@ class Particle extends EngineObject
|
|
|
323
351
|
// lerp color and size
|
|
324
352
|
const p1 = this.lifeTime > 0 ? min((time - this.spawnTime) / this.lifeTime, 1) : 1, p2 = 1-p1;
|
|
325
353
|
const radius = p2 * this.sizeStart + p1 * this.sizeEnd;
|
|
326
|
-
|
|
354
|
+
const size = vec2(radius);
|
|
327
355
|
this.color.r = p2 * this.colorStart.r + p1 * this.colorEnd.r;
|
|
328
356
|
this.color.g = p2 * this.colorStart.g + p1 * this.colorEnd.g;
|
|
329
357
|
this.color.b = p2 * this.colorStart.b + p1 * this.colorEnd.b;
|
|
@@ -343,7 +371,7 @@ class Particle extends EngineObject
|
|
|
343
371
|
{
|
|
344
372
|
// in local space of emitter
|
|
345
373
|
const a = this.localSpaceEmitter.angle;
|
|
346
|
-
const c =
|
|
374
|
+
const c = cos(a), s = sin(a);
|
|
347
375
|
pos = this.localSpaceEmitter.pos.add(
|
|
348
376
|
new Vector2(pos.x*c - pos.y*s, pos.x*s + pos.y*c));
|
|
349
377
|
angle += this.localSpaceEmitter.angle;
|
|
@@ -359,21 +387,14 @@ class Particle extends EngineObject
|
|
|
359
387
|
{
|
|
360
388
|
// stretch in direction of motion
|
|
361
389
|
const trailLength = speed * this.trailScale;
|
|
362
|
-
|
|
363
|
-
angle =
|
|
364
|
-
drawTile(pos,
|
|
390
|
+
size.y = max(size.x, trailLength);
|
|
391
|
+
angle = atan2(direction.x, direction.y);
|
|
392
|
+
drawTile(pos, size, this.tileInfo, this.color, angle, this.mirror);
|
|
365
393
|
}
|
|
366
394
|
}
|
|
367
395
|
else
|
|
368
|
-
drawTile(pos,
|
|
396
|
+
drawTile(pos, size, this.tileInfo, this.color, angle, this.mirror);
|
|
369
397
|
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
|
-
}
|
|
398
|
+
debugParticles && debugRect(pos, size, '#f005', 0, angle);
|
|
378
399
|
}
|
|
379
400
|
}
|
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 */
|