littlejsengine 1.1.17 → 1.2.0
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/README.md +13 -3
- package/build.bat +1 -0
- package/engine/engine.all.js +520 -522
- package/engine/engine.all.min.js +1 -1
- package/engine/engine.all.release.js +444 -278
- package/engine/engine.js +68 -33
- package/engine/engineAudio.js +21 -7
- package/engine/engineBuild.bat +9 -1
- package/engine/engineBuildSetup.bat +2 -1
- package/engine/engineDebug.js +80 -246
- package/engine/engineDraw.js +106 -21
- package/engine/engineInput.js +89 -52
- package/engine/engineMedals.js +14 -17
- package/engine/engineObject.js +14 -23
- package/engine/engineParticles.js +3 -3
- package/engine/engineRelease.js +3 -1
- package/engine/engineSettings.js +39 -29
- package/engine/engineTileLayer.js +26 -24
- package/engine/engineUtilities.js +14 -12
- package/engine/engineWebGL.js +47 -56
- package/engine/index.d.ts +1612 -0
- package/examples/breakout/game.js +9 -7
- package/examples/breakout/gameObjects.js +1 -1
- package/examples/breakout/index.html +6 -4
- package/examples/platformer/game.js +2 -2
- package/examples/platformer/gameEffects.js +8 -11
- package/examples/platformer/gameLevel.js +14 -7
- package/examples/platformer/gameObjects.js +1 -1
- package/examples/platformer/gamePlayer.js +13 -8
- package/examples/platformer/index.html +9 -7
- package/examples/platformer/tiles.png +0 -0
- package/examples/puzzle/game.js +1 -1
- package/examples/puzzle/index.html +5 -3
- package/examples/stress/index.html +6 -4
- package/game.js +5 -4
- package/index.html +16 -14
- package/package.json +1 -1
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
|
-
LittleJS - Release
|
|
2
|
+
LittleJS - Release Build
|
|
3
|
+
MIT License - Copyright 2021 Frank Force
|
|
4
|
+
|
|
3
5
|
- This file is used for release builds in place of engineDebug.js
|
|
4
6
|
- Debug functionality will be disabled to lower size and increase performance
|
|
5
7
|
*/
|
|
@@ -80,27 +82,27 @@ const mod = (a, b=1)=> ((a % b) + b) % b;
|
|
|
80
82
|
|
|
81
83
|
/** Clamps the value beween max and min
|
|
82
84
|
* @param {Number} value
|
|
83
|
-
* @param {Number} [max=1]
|
|
84
85
|
* @param {Number} [min=0]
|
|
86
|
+
* @param {Number} [max=1]
|
|
85
87
|
* @return {Number}
|
|
86
88
|
* @memberof Utilities */
|
|
87
|
-
const clamp = (v,
|
|
89
|
+
const clamp = (v, min=0, max=1)=> v < min ? min : v > max ? max : v;
|
|
88
90
|
|
|
89
91
|
/** Returns what percentage the value is between max and min
|
|
90
92
|
* @param {Number} value
|
|
91
|
-
* @param {Number} [max=1]
|
|
92
93
|
* @param {Number} [min=0]
|
|
94
|
+
* @param {Number} [max=1]
|
|
93
95
|
* @return {Number}
|
|
94
96
|
* @memberof Utilities */
|
|
95
|
-
const percent = (v,
|
|
97
|
+
const percent = (v, min=0, max=1)=> max-min ? clamp((v-min) / (max-min)) : 0;
|
|
96
98
|
|
|
97
99
|
/** Linearly interpolates the percent value between max and min
|
|
98
100
|
* @param {Number} percent
|
|
99
|
-
* @param {Number} [max=1]
|
|
100
101
|
* @param {Number} [min=0]
|
|
102
|
+
* @param {Number} [max=1]
|
|
101
103
|
* @return {Number}
|
|
102
104
|
* @memberof Utilities */
|
|
103
|
-
const lerp = (p,
|
|
105
|
+
const lerp = (p, min=0, max=1)=> min + clamp(p) * (max-min);
|
|
104
106
|
|
|
105
107
|
/** Applies smoothstep function to the percentage value
|
|
106
108
|
* @param {Number} value
|
|
@@ -115,11 +117,11 @@ const smoothStep = (p)=> p * p * (3 - 2 * p);
|
|
|
115
117
|
const nearestPowerOfTwo = (v)=> 2**Math.ceil(Math.log2(v));
|
|
116
118
|
|
|
117
119
|
/** Returns true if two axis aligned bounding boxes are overlapping
|
|
118
|
-
* @param {Vector2} pointA
|
|
119
|
-
* @param {Vector2} sizeA
|
|
120
|
-
* @param {Vector2} pointB
|
|
121
|
-
* @param {Vector2} sizeB
|
|
122
|
-
* @return {Boolean}
|
|
120
|
+
* @param {Vector2} pointA - Center of box A
|
|
121
|
+
* @param {Vector2} sizeA - Size of box A
|
|
122
|
+
* @param {Vector2} pointB - Center of box B
|
|
123
|
+
* @param {Vector2} [sizeB] - Size of box B
|
|
124
|
+
* @return {Boolean} - True if overlapping
|
|
123
125
|
* @memberof Utilities */
|
|
124
126
|
const isOverlapping = (pA, sA, pB, sB)=> abs(pA.x - pB.x)*2 < sA.x + sB.x & abs(pA.y - pB.y)*2 < sA.y + sB.y;
|
|
125
127
|
|
|
@@ -216,10 +218,12 @@ const vec2 = (x=0, y)=> x.x == undefined ? new Vector2(x, y == undefined? x : y)
|
|
|
216
218
|
|
|
217
219
|
/**
|
|
218
220
|
* 2D Vector object with vector math library
|
|
221
|
+
* <br> - Functions do not change this so they can be chained together
|
|
219
222
|
* @example
|
|
220
223
|
* let a = new Vector2(2, 3); // vector with coordinates (2, 3)
|
|
221
224
|
* let b = new Vector2; // vector with coordinates (0, 0)
|
|
222
225
|
* let c = vec2(4, 2); // use the vec2 function to make a Vector2
|
|
226
|
+
* let d = a.add(b).scale(5); // operators can be chained
|
|
223
227
|
*/
|
|
224
228
|
class Vector2
|
|
225
229
|
{
|
|
@@ -506,7 +510,7 @@ class Timer
|
|
|
506
510
|
|
|
507
511
|
/** Get percentage elapsed based on time it was set to, returns 0 if not set
|
|
508
512
|
* @return {Number} */
|
|
509
|
-
getPercent() { return this.isSet()? percent(this.time - time,
|
|
513
|
+
getPercent() { return this.isSet()? percent(this.time - time, this.setTime, 0) : 0; }
|
|
510
514
|
}
|
|
511
515
|
/**
|
|
512
516
|
* LittleJS Engine Settings
|
|
@@ -522,7 +526,7 @@ class Timer
|
|
|
522
526
|
* @type {Vector2}
|
|
523
527
|
* @default
|
|
524
528
|
* @memberof Settings */
|
|
525
|
-
|
|
529
|
+
let canvasMaxSize = vec2(1920, 1200);
|
|
526
530
|
|
|
527
531
|
/** Fixed size of the canvas, if enabled cavnvas size never changes
|
|
528
532
|
* @type {Vector2}
|
|
@@ -547,12 +551,12 @@ let fontDefault = 'arial';
|
|
|
547
551
|
* @type {Vector2}
|
|
548
552
|
* @default
|
|
549
553
|
* @memberof Settings */
|
|
550
|
-
|
|
554
|
+
let tileSizeDefault = vec2(16);
|
|
551
555
|
|
|
552
556
|
/** Prevent tile bleeding from neighbors in pixels
|
|
553
557
|
* @default
|
|
554
558
|
* @memberof Settings */
|
|
555
|
-
|
|
559
|
+
let tileFixBleedScale = .3;
|
|
556
560
|
|
|
557
561
|
///////////////////////////////////////////////////////////////////////////////
|
|
558
562
|
// Object settings
|
|
@@ -561,43 +565,48 @@ const tileBleedShrinkFix = .3;
|
|
|
561
565
|
* @type {Vector2}
|
|
562
566
|
* @default
|
|
563
567
|
* @memberof Settings */
|
|
564
|
-
|
|
568
|
+
let objectDefaultSize = vec2(1);
|
|
565
569
|
|
|
566
570
|
/** Default object mass for collison calcuations (how heavy objects are)
|
|
567
571
|
* @default
|
|
568
572
|
* @memberof Settings */
|
|
569
|
-
|
|
573
|
+
let objectDefaultMass = 1;
|
|
570
574
|
|
|
571
575
|
/** How much to slow velocity by each frame (0-1)
|
|
572
576
|
* @default
|
|
573
577
|
* @memberof Settings */
|
|
574
|
-
|
|
578
|
+
let objectDefaultDamping = .99;
|
|
575
579
|
|
|
576
580
|
/** How much to slow angular velocity each frame (0-1)
|
|
577
581
|
* @default
|
|
578
582
|
* @memberof Settings */
|
|
579
|
-
|
|
583
|
+
let objectDefaultAngleDamping = .99;
|
|
580
584
|
|
|
581
585
|
/** How much to bounce when a collision occurs (0-1)
|
|
582
586
|
* @default
|
|
583
587
|
* @memberof Settings */
|
|
584
|
-
|
|
588
|
+
let objectDefaultElasticity = 0;
|
|
585
589
|
|
|
586
590
|
/** How much to slow when touching (0-1)
|
|
587
591
|
* @default
|
|
588
592
|
* @memberof Settings */
|
|
589
|
-
|
|
593
|
+
let objectDefaultFriction = .8;
|
|
590
594
|
|
|
591
595
|
/** Clamp max speed to avoid fast objects missing collisions
|
|
592
596
|
* @default
|
|
593
597
|
* @memberof Settings */
|
|
594
|
-
|
|
598
|
+
let objectMaxSpeed = 1;
|
|
595
599
|
|
|
596
600
|
/** How much gravity to apply to objects along the Y axis, negative is down
|
|
597
601
|
* @default
|
|
598
602
|
* @memberof Settings */
|
|
599
603
|
let gravity = 0;
|
|
600
604
|
|
|
605
|
+
/** Scales emit rate of particles, useful for low graphics mode (0 disables particle emitters)
|
|
606
|
+
* @default
|
|
607
|
+
* @memberof Settings */
|
|
608
|
+
let particleEmitRateScale = 1;
|
|
609
|
+
|
|
601
610
|
///////////////////////////////////////////////////////////////////////////////
|
|
602
611
|
// Camera settings
|
|
603
612
|
|
|
@@ -618,7 +627,7 @@ let cameraScale = max(tileSizeDefault.x, tileSizeDefault.y);
|
|
|
618
627
|
/** Enable webgl rendering, webgl can be disabled and removed from build (with some features disabled)
|
|
619
628
|
* @default
|
|
620
629
|
* @memberof Settings */
|
|
621
|
-
|
|
630
|
+
let glEnable = 1;
|
|
622
631
|
|
|
623
632
|
/** Fixes slow rendering in some browsers by not compositing the WebGL canvas
|
|
624
633
|
* @default
|
|
@@ -631,34 +640,39 @@ let glOverlay = 1;
|
|
|
631
640
|
/** Should gamepads be allowed
|
|
632
641
|
* @default
|
|
633
642
|
* @memberof Settings */
|
|
634
|
-
|
|
643
|
+
let gamepadsEnable = 1;
|
|
635
644
|
|
|
636
645
|
/** If true, the dpad input is also routed to the left analog stick (for better accessability)
|
|
637
646
|
* @default
|
|
638
647
|
* @memberof Settings */
|
|
639
|
-
|
|
648
|
+
let gamepadDirectionEmulateStick = 1;
|
|
640
649
|
|
|
641
650
|
/** If true the WASD keys are also routed to the direction keys (for better accessability)
|
|
642
651
|
* @default
|
|
643
652
|
* @memberof Settings */
|
|
644
|
-
|
|
653
|
+
let inputWASDEmulateDirection = 1;
|
|
645
654
|
|
|
646
|
-
/**
|
|
655
|
+
/** True if touch gamepad should appear on mobile devices
|
|
656
|
+
* <br> - Supports left analog stick, 4 face buttons and start button (button 9)
|
|
657
|
+
* <br> - Must be set by end of gameInit to be activated
|
|
647
658
|
* @default
|
|
648
659
|
* @memberof Settings */
|
|
649
|
-
|
|
660
|
+
let touchGamepadEnable = 0;
|
|
650
661
|
|
|
651
|
-
/** Size of virutal gamepad for touch devices in pixels
|
|
652
|
-
* <br> - Supports left analog stick, 4 face buttons and start button (button 9)
|
|
653
|
-
* <br> - Must be set by end of gameInit to be activated
|
|
662
|
+
/** Size of virutal gamepad for touch devices in pixels
|
|
654
663
|
* @default
|
|
655
664
|
* @memberof Settings */
|
|
656
|
-
let touchGamepadSize =
|
|
665
|
+
let touchGamepadSize = 80;
|
|
657
666
|
|
|
658
667
|
/** Transparency of touch gamepad overlay
|
|
659
668
|
* @default
|
|
660
669
|
* @memberof Settings */
|
|
661
|
-
let touchGamepadAlpha = .
|
|
670
|
+
let touchGamepadAlpha = .3;
|
|
671
|
+
|
|
672
|
+
/** Allow vibration hardware if it exists
|
|
673
|
+
* @default
|
|
674
|
+
* @memberof Settings */
|
|
675
|
+
let vibrateEnable = 1;
|
|
662
676
|
|
|
663
677
|
///////////////////////////////////////////////////////////////////////////////
|
|
664
678
|
// Audio settings
|
|
@@ -671,17 +685,17 @@ let soundVolume = .5;
|
|
|
671
685
|
/** All audio code can be disabled and removed from build
|
|
672
686
|
* @default
|
|
673
687
|
* @memberof Settings */
|
|
674
|
-
|
|
688
|
+
let soundEnable = 1;
|
|
675
689
|
|
|
676
690
|
/** Default range where sound no longer plays
|
|
677
691
|
* @default
|
|
678
692
|
* @memberof Settings */
|
|
679
|
-
|
|
693
|
+
let soundDefaultRange = 30;
|
|
680
694
|
|
|
681
695
|
/** Default range percent to start tapering off sound (0-1)
|
|
682
696
|
* @default
|
|
683
697
|
* @memberof Settings */
|
|
684
|
-
|
|
698
|
+
let soundDefaultTaper = .7;
|
|
685
699
|
|
|
686
700
|
///////////////////////////////////////////////////////////////////////////////
|
|
687
701
|
// Medals settings
|
|
@@ -689,27 +703,27 @@ const soundDefaultTaper = .7;
|
|
|
689
703
|
/** How long to show medals for in seconds
|
|
690
704
|
* @default
|
|
691
705
|
* @memberof Settings */
|
|
692
|
-
|
|
706
|
+
let medalDisplayTime = 5;
|
|
693
707
|
|
|
694
708
|
/** How quickly to slide on/off medals in seconds
|
|
695
709
|
* @default
|
|
696
710
|
* @memberof Settings */
|
|
697
|
-
|
|
711
|
+
let medalDisplaySlideTime = .5;
|
|
698
712
|
|
|
699
713
|
/** Width of medal display
|
|
700
714
|
* @default
|
|
701
715
|
* @memberof Settings */
|
|
702
|
-
|
|
716
|
+
let medalDisplayWidth = 640;
|
|
703
717
|
|
|
704
718
|
/** Height of medal display
|
|
705
719
|
* @default
|
|
706
720
|
* @memberof Settings */
|
|
707
|
-
|
|
721
|
+
let medalDisplayHeight = 80;
|
|
708
722
|
|
|
709
723
|
/** Size of icon in medal display
|
|
710
724
|
* @default
|
|
711
725
|
* @memberof Settings */
|
|
712
|
-
|
|
726
|
+
let medalDisplayIconSize = 50;
|
|
713
727
|
/*
|
|
714
728
|
LittleJS - The Tiny JavaScript Game Engine That Can!
|
|
715
729
|
MIT License - Copyright 2021 Frank Force
|
|
@@ -734,7 +748,7 @@ const medalDisplayIconSize = 80;
|
|
|
734
748
|
const engineName = 'LittleJS';
|
|
735
749
|
|
|
736
750
|
/** Version of engine */
|
|
737
|
-
const engineVersion = '1.
|
|
751
|
+
const engineVersion = '1.2.0';
|
|
738
752
|
|
|
739
753
|
/** Frames per second to update objects
|
|
740
754
|
* @default */
|
|
@@ -747,7 +761,7 @@ const timeDelta = 1/frameRate;
|
|
|
747
761
|
/** Array containing all engine objects */
|
|
748
762
|
let engineObjects = [];
|
|
749
763
|
|
|
750
|
-
/** Array containing only objects that are set to collide with other objects (for optimization) */
|
|
764
|
+
/** Array containing only objects that are set to collide with other objects this frame (for optimization) */
|
|
751
765
|
let engineObjectsCollide = [];
|
|
752
766
|
|
|
753
767
|
/** Current update frame, used to calculate time */
|
|
@@ -763,8 +777,15 @@ let timeReal = 0;
|
|
|
763
777
|
let paused = 0;
|
|
764
778
|
|
|
765
779
|
// Engine internal variables not exposed to documentation
|
|
766
|
-
let frameTimeLastMS = 0, frameTimeBufferMS = 0,
|
|
767
|
-
|
|
780
|
+
let frameTimeLastMS = 0, frameTimeBufferMS = 0, tileImageSize, tileImageFixBleed;
|
|
781
|
+
|
|
782
|
+
// Engine stat tracking, if showWatermark is true
|
|
783
|
+
let averageFPS, drawCount;
|
|
784
|
+
|
|
785
|
+
// css text used for elements created by engine
|
|
786
|
+
const styleBody = 'margin:0;overflow:hidden;background:#000' +
|
|
787
|
+
';touch-action:none;user-select:none;-webkit-user-select:none;-moz-user-select:none';
|
|
788
|
+
const styleCanvas = 'position:absolute';
|
|
768
789
|
|
|
769
790
|
///////////////////////////////////////////////////////////////////////////////
|
|
770
791
|
|
|
@@ -782,17 +803,14 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
782
803
|
tileImage.onerror = tileImage.onload = ()=>
|
|
783
804
|
{
|
|
784
805
|
// save tile image info
|
|
785
|
-
|
|
806
|
+
tileImageFixBleed = vec2(tileFixBleedScale).divide(tileImageSize = vec2(tileImage.width, tileImage.height));
|
|
786
807
|
debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
|
|
787
|
-
shrinkTilesX = tileBleedShrinkFix/tileImageSize.x;
|
|
788
|
-
shrinkTilesY = tileBleedShrinkFix/tileImageSize.y;
|
|
789
808
|
|
|
790
809
|
// setup html
|
|
810
|
+
document.body.style = styleBody;
|
|
791
811
|
document.body.appendChild(mainCanvas = document.createElement('canvas'));
|
|
792
|
-
document.body.style = 'margin:0;overflow:hidden;background:#000' +
|
|
793
|
-
(touchMouseEnable?';touch-action:none;user-select:none;-webkit-user-select:none;-moz-user-select:none':'');
|
|
794
|
-
mainCanvas.style = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
|
|
795
812
|
mainContext = mainCanvas.getContext('2d');
|
|
813
|
+
mainCanvas.style = styleCanvas;
|
|
796
814
|
|
|
797
815
|
// init stuff and start engine
|
|
798
816
|
debugInit();
|
|
@@ -800,8 +818,8 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
800
818
|
|
|
801
819
|
// create overlay canvas for hud to appear above gl canvas
|
|
802
820
|
document.body.appendChild(overlayCanvas = document.createElement('canvas'));
|
|
803
|
-
overlayCanvas.style = mainCanvas.style.cssText;
|
|
804
821
|
overlayContext = overlayCanvas.getContext('2d');
|
|
822
|
+
overlayCanvas.style = styleCanvas;
|
|
805
823
|
|
|
806
824
|
gameInit();
|
|
807
825
|
touchGamepadCreate();
|
|
@@ -817,7 +835,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
817
835
|
let frameTimeDeltaMS = frameTimeMS - frameTimeLastMS;
|
|
818
836
|
frameTimeLastMS = frameTimeMS;
|
|
819
837
|
if (debug || showWatermark)
|
|
820
|
-
|
|
838
|
+
averageFPS = lerp(.05, averageFPS || 0, 1e3/(frameTimeDeltaMS||1));
|
|
821
839
|
if (debug)
|
|
822
840
|
frameTimeDeltaMS *= keyIsDown(107) ? 5 : keyIsDown(109) ? .2 : 1; // +/- to speed/slow time
|
|
823
841
|
timeReal += frameTimeDeltaMS / 1e3;
|
|
@@ -868,13 +886,24 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
868
886
|
|
|
869
887
|
// fit to window by adding space on top or bottom if necessary
|
|
870
888
|
const aspect = innerWidth / innerHeight;
|
|
871
|
-
const fixedAspect =
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
889
|
+
const fixedAspect = canvasFixedSize.x / canvasFixedSize.y;
|
|
890
|
+
|
|
891
|
+
// clear css
|
|
892
|
+
mainCanvas.style.width = overlayCanvas.style.width =
|
|
893
|
+
mainCanvas.style.height = overlayCanvas.style.height =
|
|
894
|
+
mainCanvas.style.top = overlayCanvas.style.top =
|
|
895
|
+
mainCanvas.style.left = overlayCanvas.style.left = '';
|
|
896
|
+
|
|
897
|
+
// letterbox center canvas
|
|
898
|
+
if (aspect < fixedAspect)
|
|
899
|
+
{
|
|
900
|
+
mainCanvas.style.width = overlayCanvas.style.width = innerWidth;
|
|
901
|
+
mainCanvas.style.top = overlayCanvas.style.top = (innerHeight - innerWidth/fixedAspect)/2;
|
|
902
|
+
}
|
|
903
|
+
else
|
|
875
904
|
{
|
|
876
|
-
|
|
877
|
-
|
|
905
|
+
mainCanvas.style.height = overlayCanvas.style.height = innerHeight;
|
|
906
|
+
mainCanvas.style.left = overlayCanvas.style.left = (innerWidth - innerHeight*fixedAspect)/2;
|
|
878
907
|
}
|
|
879
908
|
}
|
|
880
909
|
else
|
|
@@ -882,14 +911,22 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
882
911
|
// clear and set size to same as window
|
|
883
912
|
mainCanvas.width = min(innerWidth, canvasMaxSize.x);
|
|
884
913
|
mainCanvas.height = min(innerHeight, canvasMaxSize.y);
|
|
914
|
+
|
|
915
|
+
// center canvas
|
|
916
|
+
overlayCanvas.style.top = mainCanvas.style.top = (innerHeight - mainCanvas.height)/2;
|
|
917
|
+
overlayCanvas.style.left = mainCanvas.style.left = (innerWidth - mainCanvas.width)/2;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
if (glEnable)
|
|
921
|
+
{
|
|
922
|
+
glCanvas.style.width = mainCanvas.style.width;
|
|
923
|
+
glCanvas.style.height = mainCanvas.style.height;
|
|
924
|
+
glCanvas.style.top = mainCanvas.style.top;
|
|
925
|
+
glCanvas.style.left = mainCanvas.style.left;
|
|
885
926
|
}
|
|
886
927
|
|
|
887
|
-
// save canvas size and clear overlay canvas
|
|
888
|
-
mainCanvasSize = vec2(overlayCanvas.width = mainCanvas.width, overlayCanvas.height = mainCanvas.height);
|
|
889
|
-
mainContext.imageSmoothingEnabled = !cavasPixelated; // disable smoothing for pixel art
|
|
890
|
-
|
|
891
928
|
// render sort then render while removing destroyed objects
|
|
892
|
-
|
|
929
|
+
enginePreRender();
|
|
893
930
|
gameRender();
|
|
894
931
|
engineObjects.sort((a,b)=> a.renderOrder - b.renderOrder);
|
|
895
932
|
for (const o of engineObjects)
|
|
@@ -908,7 +945,8 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
908
945
|
overlayContext.font = '1em monospace';
|
|
909
946
|
overlayContext.fillStyle = '#000';
|
|
910
947
|
const text = engineName + ' ' + 'v' + engineVersion + ' / '
|
|
911
|
-
+ drawCount + ' / ' + engineObjects.length + ' / ' +
|
|
948
|
+
+ drawCount + ' / ' + engineObjects.length + ' / ' + averageFPS.toFixed(1)
|
|
949
|
+
+ ' ' + (glEnable ? 'GL' : '2D') ;
|
|
912
950
|
overlayContext.fillText(text, mainCanvas.width-3, 3);
|
|
913
951
|
overlayContext.fillStyle = '#fff';
|
|
914
952
|
overlayContext.fillText(text, mainCanvas.width-2, 2);
|
|
@@ -920,12 +958,27 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
920
958
|
tileImageSource ? tileImage.src = tileImageSource : tileImage.onload();
|
|
921
959
|
}
|
|
922
960
|
|
|
961
|
+
// called by engine to setup render system
|
|
962
|
+
function enginePreRender()
|
|
963
|
+
{
|
|
964
|
+
// save canvas size and clear canvases
|
|
965
|
+
mainCanvasSize = vec2(overlayCanvas.width = mainCanvas.width, overlayCanvas.height = mainCanvas.height);
|
|
966
|
+
|
|
967
|
+
// disable smoothing for pixel art
|
|
968
|
+
mainContext.imageSmoothingEnabled = !cavasPixelated;
|
|
969
|
+
|
|
970
|
+
// setup gl rendering if enabled
|
|
971
|
+
glPreRender(mainCanvas.width, mainCanvas.height, cameraPos.x, cameraPos.y, cameraScale);
|
|
972
|
+
}
|
|
923
973
|
|
|
924
974
|
///////////////////////////////////////////////////////////////////////////////
|
|
925
975
|
|
|
926
976
|
/** Calls update on each engine object (recursively if child), removes destroyed objects, and updated time */
|
|
927
977
|
function engineObjectsUpdate()
|
|
928
978
|
{
|
|
979
|
+
// get list of solid objects for physics optimzation
|
|
980
|
+
engineObjectsCollide = engineObjects.filter(o=>o.collideSolidObjects);
|
|
981
|
+
|
|
929
982
|
// recursive object update
|
|
930
983
|
const updateObject = (o)=>
|
|
931
984
|
{
|
|
@@ -941,17 +994,16 @@ function engineObjectsUpdate()
|
|
|
941
994
|
|
|
942
995
|
// remove destroyed objects
|
|
943
996
|
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
944
|
-
engineObjectsCollide = engineObjectsCollide.filter(o=>!o.destroyed);
|
|
945
997
|
|
|
946
998
|
// increment frame and update time
|
|
947
999
|
time = ++frame / frameRate;
|
|
948
1000
|
}
|
|
949
1001
|
|
|
950
|
-
/**
|
|
1002
|
+
/** Destroy and remove all objects */
|
|
951
1003
|
function engineObjectsDestroy()
|
|
952
1004
|
{
|
|
953
1005
|
for (const o of engineObjects)
|
|
954
|
-
o.
|
|
1006
|
+
o.parent || o.destroy();
|
|
955
1007
|
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
956
1008
|
}
|
|
957
1009
|
|
|
@@ -962,21 +1014,18 @@ function engineObjectsDestroy()
|
|
|
962
1014
|
* @param {Array} [objects=engineObjects] - List of objects to check */
|
|
963
1015
|
function engineObjectsCallback(pos, size, callbackFunction, objects=engineObjects)
|
|
964
1016
|
{
|
|
965
|
-
if (!pos)
|
|
1017
|
+
if (!pos) // all objects
|
|
966
1018
|
{
|
|
967
|
-
// all objects
|
|
968
1019
|
for (const o of objects)
|
|
969
1020
|
callbackFunction(o);
|
|
970
1021
|
}
|
|
971
|
-
else if (size.x != undefined)
|
|
1022
|
+
else if (size.x != undefined) // bounding box test
|
|
972
1023
|
{
|
|
973
|
-
// aabb test
|
|
974
1024
|
for (const o of objects)
|
|
975
1025
|
isOverlapping(pos, size, o.pos, o.size) && callbackFunction(o);
|
|
976
1026
|
}
|
|
977
|
-
else
|
|
1027
|
+
else // circle test
|
|
978
1028
|
{
|
|
979
|
-
// circle test
|
|
980
1029
|
const sizeSquared = size*size;
|
|
981
1030
|
for (const o of objects)
|
|
982
1031
|
pos.distanceSquared(o.pos) < sizeSquared && callbackFunction(o);
|
|
@@ -1016,7 +1065,7 @@ function engineObjectsCallback(pos, size, callbackFunction, objects=engineObject
|
|
|
1016
1065
|
class EngineObject
|
|
1017
1066
|
{
|
|
1018
1067
|
/** Create an engine object and adds it to the list of objects
|
|
1019
|
-
* @param {Vector2} [position=new Vector2(
|
|
1068
|
+
* @param {Vector2} [position=new Vector2()] - World space position of the object
|
|
1020
1069
|
* @param {Vector2} [size=objectDefaultSize] - World space size of the object
|
|
1021
1070
|
* @param {Number} [tileIndex=-1] - Tile to use to render object, untextured if -1
|
|
1022
1071
|
* @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
|
|
@@ -1061,12 +1110,15 @@ class EngineObject
|
|
|
1061
1110
|
this.gravityScale = 1;
|
|
1062
1111
|
/** @property {Number} [renderOrder=0] - Objects are sorted by render order */
|
|
1063
1112
|
this.renderOrder = renderOrder;
|
|
1113
|
+
/** @property {Vector2} [velocity=new Vector2()] - Velocity of the object */
|
|
1114
|
+
this.velocity = new Vector2();
|
|
1115
|
+
/** @property {Number} [angleVelocity=0] - Angular velocity of the object */
|
|
1116
|
+
this.angleVelocity = 0;
|
|
1064
1117
|
|
|
1065
1118
|
// init other internal object stuff
|
|
1066
1119
|
this.spawnTime = time;
|
|
1067
|
-
this.velocity = vec2(this.collideSolidObjects = this.angleVelocity = 0);
|
|
1068
|
-
this.collideTiles = 1;
|
|
1069
1120
|
this.children = [];
|
|
1121
|
+
this.collideTiles = 1;
|
|
1070
1122
|
|
|
1071
1123
|
// add to list of objects
|
|
1072
1124
|
engineObjects.push(this);
|
|
@@ -1085,8 +1137,8 @@ class EngineObject
|
|
|
1085
1137
|
}
|
|
1086
1138
|
|
|
1087
1139
|
// limit max speed to prevent missing collisions
|
|
1088
|
-
this.velocity.x = clamp(this.velocity.x, objectMaxSpeed,
|
|
1089
|
-
this.velocity.y = clamp(this.velocity.y, objectMaxSpeed,
|
|
1140
|
+
this.velocity.x = clamp(this.velocity.x, -objectMaxSpeed, objectMaxSpeed);
|
|
1141
|
+
this.velocity.y = clamp(this.velocity.y, -objectMaxSpeed, objectMaxSpeed);
|
|
1090
1142
|
|
|
1091
1143
|
// apply physics
|
|
1092
1144
|
const oldPos = this.pos.copy();
|
|
@@ -1134,7 +1186,7 @@ class EngineObject
|
|
|
1134
1186
|
// if already was touching, try to push away
|
|
1135
1187
|
const deltaPos = oldPos.subtract(o.pos);
|
|
1136
1188
|
const length = deltaPos.length();
|
|
1137
|
-
const pushAwayAccel = .001; // push away if
|
|
1189
|
+
const pushAwayAccel = .001; // push away if already overlapping
|
|
1138
1190
|
const velocity = length < .01 ? randVector(pushAwayAccel) : deltaPos.scale(pushAwayAccel/length);
|
|
1139
1191
|
this.velocity = this.velocity.add(velocity);
|
|
1140
1192
|
if (o.mass) // push away if not fixed
|
|
@@ -1176,8 +1228,8 @@ class EngineObject
|
|
|
1176
1228
|
|
|
1177
1229
|
// lerp betwen elastic or inelastic based on elasticity
|
|
1178
1230
|
const elasticity = max(this.elasticity, o.elasticity);
|
|
1179
|
-
this.velocity.y = lerp(elasticity,
|
|
1180
|
-
o.velocity.y = lerp(elasticity,
|
|
1231
|
+
this.velocity.y = lerp(elasticity, inelastic, elastic0);
|
|
1232
|
+
o.velocity.y = lerp(elasticity, inelastic, elastic1);
|
|
1181
1233
|
}
|
|
1182
1234
|
}
|
|
1183
1235
|
if (!smallStepUp && (isBlockedX || !isBlockedY)) // resolve x collision
|
|
@@ -1197,8 +1249,8 @@ class EngineObject
|
|
|
1197
1249
|
|
|
1198
1250
|
// lerp betwen elastic or inelastic based on elasticity
|
|
1199
1251
|
const elasticity = max(this.elasticity, o.elasticity);
|
|
1200
|
-
this.velocity.x = lerp(elasticity,
|
|
1201
|
-
o.velocity.x = lerp(elasticity,
|
|
1252
|
+
this.velocity.x = lerp(elasticity, inelastic, elastic0);
|
|
1253
|
+
o.velocity.x = lerp(elasticity, inelastic, elastic1);
|
|
1202
1254
|
}
|
|
1203
1255
|
else // bounce if other object is fixed
|
|
1204
1256
|
this.velocity.x *= -this.elasticity;
|
|
@@ -1228,7 +1280,7 @@ class EngineObject
|
|
|
1228
1280
|
|
|
1229
1281
|
// adjust next velocity to settle on ground
|
|
1230
1282
|
const o = (oldPos.y - this.size.y/2|0) - (oldPos.y - this.size.y/2);
|
|
1231
|
-
if (o < 0 && o >
|
|
1283
|
+
if (o < 0 && o > this.damping * this.velocity.y + gravity * this.gravityScale)
|
|
1232
1284
|
this.velocity.y = this.damping ? (o - gravity * this.gravityScale) / this.damping : 0;
|
|
1233
1285
|
|
|
1234
1286
|
// move to previous position
|
|
@@ -1329,18 +1381,6 @@ class EngineObject
|
|
|
1329
1381
|
{
|
|
1330
1382
|
ASSERT(collideSolidObjects || !isSolid); // solid objects must be set to collide
|
|
1331
1383
|
|
|
1332
|
-
// track collidable objects in separate list
|
|
1333
|
-
if (collideSolidObjects && !this.collideSolidObjects)
|
|
1334
|
-
{
|
|
1335
|
-
ASSERT(!engineObjectsCollide.includes(this));
|
|
1336
|
-
engineObjectsCollide.push(this);
|
|
1337
|
-
}
|
|
1338
|
-
else if (!collideSolidObjects && this.collideSolidObjects)
|
|
1339
|
-
{
|
|
1340
|
-
ASSERT(engineObjectsCollide.includes(this))
|
|
1341
|
-
engineObjectsCollide.splice(engineObjectsCollide.indexOf(this), 1);
|
|
1342
|
-
}
|
|
1343
|
-
|
|
1344
1384
|
this.collideSolidObjects = collideSolidObjects;
|
|
1345
1385
|
this.isSolid = isSolid;
|
|
1346
1386
|
this.collideTiles = collideTiles;
|
|
@@ -1440,17 +1480,13 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, col
|
|
|
1440
1480
|
{
|
|
1441
1481
|
// calculate uvs and render
|
|
1442
1482
|
const cols = tileImageSize.x / tileSize.x |0;
|
|
1443
|
-
const uvSizeX = tileSize.x
|
|
1444
|
-
const uvSizeY = tileSize.y
|
|
1483
|
+
const uvSizeX = tileSize.x / tileImageSize.x;
|
|
1484
|
+
const uvSizeY = tileSize.y / tileImageSize.y;
|
|
1445
1485
|
const uvX = (tileIndex%cols)*uvSizeX, uvY = (tileIndex/cols|0)*uvSizeY;
|
|
1446
|
-
|
|
1447
|
-
// shrink uvs to prevent bleeding
|
|
1448
|
-
const shrinkTilesX = tileBleedShrinkFix * tileImageSizeInverse.x;
|
|
1449
|
-
const shrinkTilesY = tileBleedShrinkFix * tileImageSizeInverse.y;
|
|
1450
1486
|
|
|
1451
1487
|
glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
|
|
1452
|
-
uvX +
|
|
1453
|
-
uvX -
|
|
1488
|
+
uvX + tileImageFixBleed.x, uvY + tileImageFixBleed.y,
|
|
1489
|
+
uvX - tileImageFixBleed.x + uvSizeX, uvY - tileImageFixBleed.y + uvSizeY,
|
|
1454
1490
|
color.rgbaInt(), additiveColor.rgbaInt());
|
|
1455
1491
|
}
|
|
1456
1492
|
}
|
|
@@ -1469,10 +1505,10 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, col
|
|
|
1469
1505
|
{
|
|
1470
1506
|
// calculate uvs and render
|
|
1471
1507
|
const cols = tileImageSize.x / tileSize.x |0;
|
|
1472
|
-
const sX = (tileIndex%cols)*tileSize.x +
|
|
1473
|
-
const sY = (tileIndex/cols|0)*tileSize.y +
|
|
1474
|
-
const sWidth = tileSize.x - 2*
|
|
1475
|
-
const sHeight = tileSize.y - 2*
|
|
1508
|
+
const sX = (tileIndex%cols)*tileSize.x + tileFixBleedScale;
|
|
1509
|
+
const sY = (tileIndex/cols|0)*tileSize.y + tileFixBleedScale;
|
|
1510
|
+
const sWidth = tileSize.x - 2*tileFixBleedScale;
|
|
1511
|
+
const sHeight = tileSize.y - 2*tileFixBleedScale;
|
|
1476
1512
|
context.globalAlpha = color.a; // only alpha is supported
|
|
1477
1513
|
context.drawImage(tileImage, sX, sY, sWidth, sHeight, -.5, -.5, 1, 1);
|
|
1478
1514
|
}
|
|
@@ -1555,6 +1591,18 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainCont
|
|
|
1555
1591
|
context.restore();
|
|
1556
1592
|
}
|
|
1557
1593
|
|
|
1594
|
+
/** Enable normal or additive blend mode
|
|
1595
|
+
* @param {Boolean} [additive=0]
|
|
1596
|
+
* @param {Boolean} [useWebGL=glEnable]
|
|
1597
|
+
* @memberof Draw */
|
|
1598
|
+
function setBlendMode(additive, useWebGL=glEnable)
|
|
1599
|
+
{
|
|
1600
|
+
if (glEnable && useWebGL)
|
|
1601
|
+
glSetBlendMode(additive);
|
|
1602
|
+
else
|
|
1603
|
+
mainContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1558
1606
|
/** Draw text on overlay canvas in world space
|
|
1559
1607
|
* @param {String} text
|
|
1560
1608
|
* @param {Vector2} pos
|
|
@@ -1580,16 +1628,93 @@ function drawText(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new
|
|
|
1580
1628
|
overlayContext.fillText(text, pos.x, pos.y);
|
|
1581
1629
|
}
|
|
1582
1630
|
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
*
|
|
1587
|
-
|
|
1631
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1632
|
+
|
|
1633
|
+
/**
|
|
1634
|
+
* Font Image Object - Draw text on a 2D canvas by using characters in an image
|
|
1635
|
+
* <br> - 96 characters (from space to tilde) are stored in an image
|
|
1636
|
+
* <br> - Uses a default 8x8 font if none is supplied
|
|
1637
|
+
* <br> - You can also use fonts from the main tile sheet
|
|
1638
|
+
* @example
|
|
1639
|
+
* // use built in font
|
|
1640
|
+
* const font = new ImageFont;
|
|
1641
|
+
*
|
|
1642
|
+
* // draw text
|
|
1643
|
+
* font.drawTextScreen("LittleJS\nHello World!", vec2(200, 50));
|
|
1644
|
+
*/
|
|
1645
|
+
class FontImage
|
|
1588
1646
|
{
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1647
|
+
/** Create an image font
|
|
1648
|
+
* @param {Image} [image] - The image the font is stored in, if undefined the default font is used
|
|
1649
|
+
* @param {Vector2} [tileSize=vec2(8)] - The size of the font source tiles
|
|
1650
|
+
* @param {Vector2} [paddingSize=vec2(0,1)] - How much extra space to add between characters
|
|
1651
|
+
* @param {Number} [startTileIndex=0] - Tile index in image where font starts
|
|
1652
|
+
* @param {CanvasRenderingContext2D} [context=overlayContext] - context to draw to
|
|
1653
|
+
*/
|
|
1654
|
+
constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), startTileIndex=0, context=overlayContext)
|
|
1655
|
+
{
|
|
1656
|
+
if (!image)
|
|
1657
|
+
{
|
|
1658
|
+
// use default font image
|
|
1659
|
+
image = new Image();
|
|
1660
|
+
image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6MCIBdFY+Q0PMNgf0yCgsSAaZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWw1hPWNRcEyWh2npdpBmSUBrRaN1WSlQAWXaWLF0kkwR7C/N7XUvIaQr4xpeqeQigjLwBRZ/6KkufAACcv5z7GYhvgWHII8xN/4C2hvLCkmZSVgIJMHNTxpYOsJOiitEjUQnIQAFFP+opoQfCbjWmfzWiNzCjEvxAPFARmw/Y5Te0sl2zEp4M+m7cToJjdJS7y1xcFlFT7EtWBZ8nl7uLSff7UgYewtlyY6yHM4RrjNrnsfn/W0DBK9xNZctZ7gsTeXTe5eqLqXFlKnAn+vkNXzH6TqPW10ksKoSlauKk1TcncZLSTxNnQ8Av/8RGPXOFIuMCZ/fpwIZx5vU4QIRQ44HwUyHx/QrZhm5VtXFex7ja2se0KBpnPT0DSwS4xd86uAy+SqnLBgkPY5JolhJ33ENyK6n0VyRzCnzebIkFdY/NUtM66lTdhix1KUss6q5snH5ftVJXG0QITb1gUDvzD3z/BbyOLMirwCtPzAAAAAElFTkSuQmCC';
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
this.image = image;
|
|
1664
|
+
this.tileSize = tileSize;
|
|
1665
|
+
this.paddingSize = paddingSize;
|
|
1666
|
+
this.startTileIndex = startTileIndex;
|
|
1667
|
+
this.context = context;
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
/** Draw text in screen space using the image font
|
|
1671
|
+
* @param {String} text
|
|
1672
|
+
* @param {Vector2} pos
|
|
1673
|
+
* @param {Number} [scale=4]
|
|
1674
|
+
* @param {Boolean} [center]
|
|
1675
|
+
*/
|
|
1676
|
+
drawTextScreen(text, pos, scale=4, center)
|
|
1677
|
+
{
|
|
1678
|
+
const context = this.context;
|
|
1679
|
+
context.save();
|
|
1680
|
+
context.imageSmoothingEnabled = !cavasPixelated;
|
|
1681
|
+
|
|
1682
|
+
const size = this.tileSize;
|
|
1683
|
+
const drawSize = size.add(this.paddingSize).scale(scale);
|
|
1684
|
+
const cols = this.image.width / this.tileSize.x |0;
|
|
1685
|
+
text.split('\n').forEach((line, i)=>
|
|
1686
|
+
{
|
|
1687
|
+
const centerOffset = center ? line.length * size.x * scale / 2 |0 : 0;
|
|
1688
|
+
for(let j=line.length; j--;)
|
|
1689
|
+
{
|
|
1690
|
+
// draw each character
|
|
1691
|
+
let charCode = line[j].charCodeAt();
|
|
1692
|
+
if (charCode < 32 || charCode > 127)
|
|
1693
|
+
charCode = 127; // unknown character
|
|
1694
|
+
|
|
1695
|
+
// get the character source location and draw it
|
|
1696
|
+
const tile = this.startTileIndex + charCode - 32;
|
|
1697
|
+
const x = tile % cols;
|
|
1698
|
+
const y = tile / cols |0;
|
|
1699
|
+
const drawPos = pos.add(vec2(j,i).multiply(drawSize));
|
|
1700
|
+
context.drawImage(this.image, x * size.x, y * size.y, size.x, size.y,
|
|
1701
|
+
drawPos.x - centerOffset, drawPos.y, size.x * scale, size.y * scale);
|
|
1702
|
+
}
|
|
1703
|
+
});
|
|
1704
|
+
|
|
1705
|
+
context.restore();
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
/** Draw text in world space using the image font
|
|
1709
|
+
* @param {String} text
|
|
1710
|
+
* @param {Vector2} pos
|
|
1711
|
+
* @param {Number} [scale=.25]
|
|
1712
|
+
* @param {Boolean} [center]
|
|
1713
|
+
*/
|
|
1714
|
+
drawText(text, pos, scale=1, center)
|
|
1715
|
+
{
|
|
1716
|
+
this.drawTextScreen(text, worldToScreen(pos).floor(), scale*cameraScale|0, center);
|
|
1717
|
+
}
|
|
1593
1718
|
}
|
|
1594
1719
|
|
|
1595
1720
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -1653,7 +1778,7 @@ const keyWasReleased = (key, device=0)=> inputData[device] && inputData[device][
|
|
|
1653
1778
|
|
|
1654
1779
|
/** Clears all input
|
|
1655
1780
|
* @memberof Input */
|
|
1656
|
-
const clearInput = ()=> inputData
|
|
1781
|
+
const clearInput = ()=> inputData = [[]];
|
|
1657
1782
|
|
|
1658
1783
|
/** Returns true if mouse button is down
|
|
1659
1784
|
* @param {Number} button
|
|
@@ -1722,7 +1847,9 @@ const gamepadStick = (stick, gamepad=0)=> stickData[gamepad] ? stickData[gamepa
|
|
|
1722
1847
|
///////////////////////////////////////////////////////////////////////////////
|
|
1723
1848
|
// Input update called by engine
|
|
1724
1849
|
|
|
1725
|
-
|
|
1850
|
+
// store input as a bit field for each key: 1 = isDown, 2 = wasPressed, 4 = wasReleased
|
|
1851
|
+
// mouse and keyboard are stored together in device 0, gamepads are in devices > 0
|
|
1852
|
+
let inputData = [[]];
|
|
1726
1853
|
|
|
1727
1854
|
function inputUpdate()
|
|
1728
1855
|
{
|
|
@@ -1748,36 +1875,38 @@ function inputUpdatePost()
|
|
|
1748
1875
|
///////////////////////////////////////////////////////////////////////////////
|
|
1749
1876
|
// Keyboard event handlers
|
|
1750
1877
|
|
|
1751
|
-
onkeydown = e=>
|
|
1878
|
+
onkeydown = (e)=>
|
|
1752
1879
|
{
|
|
1753
1880
|
if (debug && e.target != document.body) return;
|
|
1754
1881
|
e.repeat || (inputData[isUsingGamepad = 0][remapKeyCode(e.keyCode)] = 3);
|
|
1755
1882
|
debug || e.preventDefault();
|
|
1756
1883
|
}
|
|
1757
|
-
onkeyup = e=>
|
|
1884
|
+
onkeyup = (e)=>
|
|
1758
1885
|
{
|
|
1759
1886
|
if (debug && e.target != document.body) return;
|
|
1760
1887
|
inputData[0][remapKeyCode(e.keyCode)] = 4;
|
|
1761
1888
|
}
|
|
1762
|
-
const remapKeyCode = c=> inputWASDEmulateDirection ? c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
|
|
1889
|
+
const remapKeyCode = (c)=> inputWASDEmulateDirection ? c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
|
|
1763
1890
|
|
|
1764
1891
|
///////////////////////////////////////////////////////////////////////////////
|
|
1765
1892
|
// Mouse event handlers
|
|
1766
1893
|
|
|
1767
|
-
onmousedown = e=> {inputData[isUsingGamepad = 0][e.button] = 3; onmousemove(e); e.button && e.preventDefault();}
|
|
1768
|
-
onmouseup = e=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
|
|
1769
|
-
onmousemove = e=>
|
|
1894
|
+
onmousedown = (e)=> {inputData[isUsingGamepad = 0][e.button] = 3; onmousemove(e); e.button && e.preventDefault();}
|
|
1895
|
+
onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
|
|
1896
|
+
onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
|
|
1897
|
+
onwheel = (e)=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
|
|
1898
|
+
oncontextmenu = (e)=> !1; // prevent right click menu
|
|
1899
|
+
|
|
1900
|
+
// convert a mouse or touch event position to screen space
|
|
1901
|
+
const mouseToScreen = (mousePos)=>
|
|
1770
1902
|
{
|
|
1771
1903
|
if (!mainCanvas)
|
|
1772
|
-
return; // fix bug that can occur if user clicks before page loads
|
|
1904
|
+
return vec2(); // fix bug that can occur if user clicks before page loads
|
|
1773
1905
|
|
|
1774
|
-
// convert mouse pos to canvas space
|
|
1775
1906
|
const rect = mainCanvas.getBoundingClientRect();
|
|
1776
|
-
|
|
1777
|
-
vec2(percent(
|
|
1907
|
+
return mainCanvasSize.multiply(
|
|
1908
|
+
vec2(percent(mousePos.x, rect.left, rect.right), percent(mousePos.y, rect.top, rect.bottom)));
|
|
1778
1909
|
}
|
|
1779
|
-
onwheel = e=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
|
|
1780
|
-
oncontextmenu = e=> !1; // prevent right click menu
|
|
1781
1910
|
|
|
1782
1911
|
///////////////////////////////////////////////////////////////////////////////
|
|
1783
1912
|
// Gamepad input
|
|
@@ -1785,6 +1914,21 @@ oncontextmenu = e=> !1; // prevent right click menu
|
|
|
1785
1914
|
const stickData = [];
|
|
1786
1915
|
function gamepadsUpdate()
|
|
1787
1916
|
{
|
|
1917
|
+
if (touchGamepadEnable && touchGamepadTimer.isSet())
|
|
1918
|
+
{
|
|
1919
|
+
// read virtual analog stick
|
|
1920
|
+
const sticks = stickData[0] || (stickData[0] = []);
|
|
1921
|
+
sticks[0] = vec2(touchGamepadStick.x, -touchGamepadStick.y); // flip vertical
|
|
1922
|
+
|
|
1923
|
+
// read virtual gamepad buttons
|
|
1924
|
+
const data = inputData[1] || (inputData[1] = []);
|
|
1925
|
+
for (let i=10; i--;)
|
|
1926
|
+
{
|
|
1927
|
+
const j = i == 3 ? 2 : i == 2 ? 3 : i; // fix button locations
|
|
1928
|
+
data[j] = touchGamepadButtons[i] ? 1 + 2*!gamepadIsDown(j,0) : 4*gamepadIsDown(j,0);
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1788
1932
|
if (!gamepadsEnable || !navigator.getGamepads || !document.hasFocus() && !debug)
|
|
1789
1933
|
return;
|
|
1790
1934
|
|
|
@@ -1802,8 +1946,8 @@ function gamepadsUpdate()
|
|
|
1802
1946
|
// read clamp dead zone of analog sticks
|
|
1803
1947
|
const deadZone = .3, deadZoneMax = .8;
|
|
1804
1948
|
const applyDeadZone = (v)=>
|
|
1805
|
-
v > deadZone ? percent( v,
|
|
1806
|
-
v < -deadZone ? -percent(-v,
|
|
1949
|
+
v > deadZone ? percent( v, deadZone, deadZoneMax) :
|
|
1950
|
+
v < -deadZone ? -percent(-v, deadZone, deadZoneMax) : 0;
|
|
1807
1951
|
|
|
1808
1952
|
// read analog sticks
|
|
1809
1953
|
for (let j = 0; j < gamepad.axes.length-1; j+=2)
|
|
@@ -1815,6 +1959,7 @@ function gamepadsUpdate()
|
|
|
1815
1959
|
const button = gamepad.buttons[j];
|
|
1816
1960
|
data[j] = button.pressed ? 1 + 2*!gamepadIsDown(j,i) : 4*gamepadIsDown(j,i);
|
|
1817
1961
|
isUsingGamepad |= !i && button.pressed;
|
|
1962
|
+
touchGamepadEnable && touchGamepadTimer.unset(); // disable touch gamepad if using real gamepad
|
|
1818
1963
|
}
|
|
1819
1964
|
|
|
1820
1965
|
if (gamepadDirectionEmulateStick)
|
|
@@ -1828,6 +1973,17 @@ function gamepadsUpdate()
|
|
|
1828
1973
|
}
|
|
1829
1974
|
}
|
|
1830
1975
|
|
|
1976
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1977
|
+
|
|
1978
|
+
/** Pulse the vibration hardware if it exists
|
|
1979
|
+
* @param {Number} [pattern=100] - a single value in miliseconds or vibration interval array
|
|
1980
|
+
* @memberof Input */
|
|
1981
|
+
const vibrate = (pattern)=> vibrateEnable && Navigator.vibrate && Navigator.vibrate(pattern);
|
|
1982
|
+
|
|
1983
|
+
/** Cancel any ongoing vibration
|
|
1984
|
+
* @memberof Input */
|
|
1985
|
+
const vibrateStop = ()=> vibrate(0);
|
|
1986
|
+
|
|
1831
1987
|
///////////////////////////////////////////////////////////////////////////////
|
|
1832
1988
|
// Touch input
|
|
1833
1989
|
|
|
@@ -1835,7 +1991,9 @@ function gamepadsUpdate()
|
|
|
1835
1991
|
* @const {boolean}
|
|
1836
1992
|
* @memberof Input */
|
|
1837
1993
|
const isTouchDevice = window.ontouchstart !== undefined;
|
|
1838
|
-
|
|
1994
|
+
|
|
1995
|
+
// try to enable touch mouse
|
|
1996
|
+
if (isTouchDevice)
|
|
1839
1997
|
{
|
|
1840
1998
|
// handle all touch events the same way
|
|
1841
1999
|
let wasTouching, hadTouchInput;
|
|
@@ -1866,19 +2024,25 @@ if (touchMouseEnable && isTouchDevice)
|
|
|
1866
2024
|
}
|
|
1867
2025
|
|
|
1868
2026
|
///////////////////////////////////////////////////////////////////////////////
|
|
1869
|
-
//
|
|
2027
|
+
// touch gamepad, virtual on screen gamepad emulator for touch devices
|
|
1870
2028
|
|
|
1871
|
-
//
|
|
1872
|
-
|
|
2029
|
+
// touch input internal variables
|
|
2030
|
+
let touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadStick = vec2(), touchGamepadAnalog = 1;
|
|
2031
|
+
|
|
2032
|
+
// create the touch gamepad, called automatically by the engine
|
|
1873
2033
|
function touchGamepadCreate()
|
|
1874
2034
|
{
|
|
1875
|
-
if (!
|
|
2035
|
+
if (!touchGamepadEnable || !isTouchDevice)
|
|
1876
2036
|
return;
|
|
1877
2037
|
|
|
1878
2038
|
ontouchstart = ontouchmove = ontouchend = (e)=>
|
|
1879
2039
|
{
|
|
1880
|
-
if (!
|
|
2040
|
+
if (!touchGamepadEnable)
|
|
1881
2041
|
return;
|
|
2042
|
+
|
|
2043
|
+
// clear touch gamepad input
|
|
2044
|
+
touchGamepadStick = vec2();
|
|
2045
|
+
touchGamepadButtons = [];
|
|
1882
2046
|
|
|
1883
2047
|
const touching = e.touches.length;
|
|
1884
2048
|
if (touching)
|
|
@@ -1888,6 +2052,13 @@ function touchGamepadCreate()
|
|
|
1888
2052
|
// set that gamepad is active
|
|
1889
2053
|
isUsingGamepad = 1;
|
|
1890
2054
|
touchGamepadTimer.set();
|
|
2055
|
+
|
|
2056
|
+
if (paused)
|
|
2057
|
+
{
|
|
2058
|
+
// touch anywhere to press start when paused
|
|
2059
|
+
touchGamepadButtons[9] = 1;
|
|
2060
|
+
return;
|
|
2061
|
+
}
|
|
1891
2062
|
}
|
|
1892
2063
|
|
|
1893
2064
|
// get center of left and right sides
|
|
@@ -1895,68 +2066,58 @@ function touchGamepadCreate()
|
|
|
1895
2066
|
const buttonCenter = mainCanvasSize.subtract(vec2(touchGamepadSize, touchGamepadSize));
|
|
1896
2067
|
const startCenter = mainCanvasSize.scale(.5);
|
|
1897
2068
|
|
|
1898
|
-
// update virtual gamepad
|
|
1899
|
-
const data = inputData[1] || (inputData[1] = []);
|
|
1900
|
-
const sticks = stickData[0] || (stickData[0] = []);
|
|
1901
|
-
sticks[0] = vec2();
|
|
1902
|
-
|
|
1903
2069
|
// check each touch point
|
|
1904
|
-
const buttons = [];
|
|
1905
2070
|
for (const touch of e.touches)
|
|
1906
2071
|
{
|
|
1907
|
-
const touchPos = vec2(touch.clientX, touch.clientY);
|
|
2072
|
+
const touchPos = mouseToScreen(vec2(touch.clientX, touch.clientY));
|
|
1908
2073
|
if (touchPos.distance(stickCenter) < touchGamepadSize)
|
|
1909
2074
|
{
|
|
1910
2075
|
// virtual analog stick
|
|
1911
|
-
|
|
1912
|
-
|
|
2076
|
+
if (touchGamepadAnalog)
|
|
2077
|
+
touchGamepadStick = touchPos.subtract(stickCenter).clampLength(touchGamepadSize/2).scale(2/touchGamepadSize);
|
|
2078
|
+
else
|
|
2079
|
+
{
|
|
2080
|
+
// 8 way dpad
|
|
2081
|
+
const angle = touchPos.subtract(stickCenter).angle();
|
|
2082
|
+
touchGamepadStick.setAngle((angle * 4 / PI + 8.5 | 0) * PI / 4);
|
|
2083
|
+
}
|
|
1913
2084
|
}
|
|
1914
2085
|
else if (touchPos.distance(buttonCenter) < touchGamepadSize)
|
|
1915
2086
|
{
|
|
1916
2087
|
// virtual face buttons
|
|
1917
2088
|
const button = touchPos.subtract(buttonCenter).direction();
|
|
1918
|
-
|
|
2089
|
+
touchGamepadButtons[button] = 1;
|
|
1919
2090
|
}
|
|
1920
2091
|
else if (touchPos.distance(startCenter) < touchGamepadSize)
|
|
1921
2092
|
{
|
|
1922
|
-
// virtual start button
|
|
1923
|
-
|
|
2093
|
+
// virtual start button in center
|
|
2094
|
+
touchGamepadButtons[9] = 1;
|
|
1924
2095
|
}
|
|
1925
2096
|
}
|
|
1926
|
-
|
|
1927
|
-
// read virtual buttons
|
|
1928
|
-
for (let j=10; j--;)
|
|
1929
|
-
data[j] = buttons[j] ? 1 + 2*!gamepadIsDown(j,0) : 4*gamepadIsDown(j,0);
|
|
1930
2097
|
}
|
|
1931
2098
|
}
|
|
1932
2099
|
|
|
1933
2100
|
// render the touch gamepad, called automatically by the engine
|
|
1934
2101
|
function touchGamepadRender()
|
|
1935
2102
|
{
|
|
1936
|
-
if (!
|
|
2103
|
+
if (!touchGamepadEnable || !touchGamepadTimer.isSet())
|
|
1937
2104
|
return;
|
|
1938
2105
|
|
|
1939
|
-
// fade off when not touching
|
|
1940
|
-
const alpha = percent(touchGamepadTimer.get(), 4,
|
|
1941
|
-
if (!alpha)
|
|
2106
|
+
// fade off when not touching or paused
|
|
2107
|
+
const alpha = percent(touchGamepadTimer.get(), 4, 3);
|
|
2108
|
+
if (!alpha || paused)
|
|
1942
2109
|
return;
|
|
1943
2110
|
|
|
1944
2111
|
// setup the canvas
|
|
1945
2112
|
overlayContext.save();
|
|
1946
2113
|
overlayContext.globalAlpha = alpha*touchGamepadAlpha;
|
|
1947
|
-
overlayContext.fillStyle = '#fff6';
|
|
1948
2114
|
overlayContext.strokeStyle = '#fff';
|
|
1949
2115
|
overlayContext.lineWidth = 3;
|
|
1950
2116
|
|
|
1951
|
-
// draw start button
|
|
1952
|
-
//overlayContext.beginPath();
|
|
1953
|
-
//overlayContext.arc(mainCanvasSize.x/2, mainCanvasSize.y/2, touchGamepadSize*.9, 0,9);
|
|
1954
|
-
//overlayContext.fill();
|
|
1955
|
-
//overlayContext.stroke();
|
|
1956
|
-
|
|
1957
2117
|
// draw left analog stick
|
|
2118
|
+
overlayContext.fillStyle = gamepadStick(0).length() > 0 ? '#fff' : '#000';
|
|
1958
2119
|
overlayContext.beginPath();
|
|
1959
|
-
overlayContext.arc(touchGamepadSize, mainCanvasSize.y-touchGamepadSize, touchGamepadSize
|
|
2120
|
+
overlayContext.arc(touchGamepadSize, mainCanvasSize.y-touchGamepadSize, touchGamepadSize/2, 0,9);
|
|
1960
2121
|
overlayContext.fill();
|
|
1961
2122
|
overlayContext.stroke();
|
|
1962
2123
|
|
|
@@ -1964,9 +2125,10 @@ function touchGamepadRender()
|
|
|
1964
2125
|
const rightCenter = vec2(mainCanvasSize.x-touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
1965
2126
|
for (let i=4; i--;)
|
|
1966
2127
|
{
|
|
1967
|
-
const pos = rightCenter.add((new Vector2).setAngle(i*PI/2, touchGamepadSize
|
|
2128
|
+
const pos = rightCenter.add((new Vector2).setAngle(i*PI/2, touchGamepadSize/2));
|
|
2129
|
+
overlayContext.fillStyle = touchGamepadButtons[i] ? '#fff' : '#000';
|
|
1968
2130
|
overlayContext.beginPath();
|
|
1969
|
-
overlayContext.arc(pos.x, pos.y, touchGamepadSize
|
|
2131
|
+
overlayContext.arc(pos.x, pos.y, touchGamepadSize/4, 0,9);
|
|
1970
2132
|
overlayContext.fill();
|
|
1971
2133
|
overlayContext.stroke();
|
|
1972
2134
|
}
|
|
@@ -2045,7 +2207,7 @@ class Sound
|
|
|
2045
2207
|
return; // out of range
|
|
2046
2208
|
|
|
2047
2209
|
// attenuate volume by distance
|
|
2048
|
-
volume *= percent(lengthSquared**.5, range*this.taper
|
|
2210
|
+
volume *= percent(lengthSquared**.5, range, range*this.taper);
|
|
2049
2211
|
}
|
|
2050
2212
|
|
|
2051
2213
|
// get pan from screen space coords
|
|
@@ -2171,7 +2333,7 @@ function speak(text, language='', volume=1, rate=1, pitch=1)
|
|
|
2171
2333
|
|
|
2172
2334
|
/** Stop all queued speech
|
|
2173
2335
|
* @memberof Audio */
|
|
2174
|
-
const
|
|
2336
|
+
const speakStop = ()=> speechSynthesis && speechSynthesis.cancel();
|
|
2175
2337
|
|
|
2176
2338
|
/** Get frequency of a note on a musical scale
|
|
2177
2339
|
* @param {Number} semitoneOffset - How many semitones away from the root note
|
|
@@ -2202,6 +2364,13 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
|
|
|
2202
2364
|
if (!audioContext)
|
|
2203
2365
|
audioContext = new (window.AudioContext||webkitAudioContext);
|
|
2204
2366
|
|
|
2367
|
+
// fix stalled audio
|
|
2368
|
+
audioContext.resume();
|
|
2369
|
+
|
|
2370
|
+
// prevent sounds from building up if they can't be played
|
|
2371
|
+
if (audioContext.state != 'running')
|
|
2372
|
+
return;
|
|
2373
|
+
|
|
2205
2374
|
// create buffer and source
|
|
2206
2375
|
const buffer = audioContext.createBuffer(sampleChannels.length, sampleChannels[0].length, zzfxR),
|
|
2207
2376
|
source = audioContext.createBufferSource();
|
|
@@ -2212,11 +2381,18 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
|
|
|
2212
2381
|
source.playbackRate.value = rate;
|
|
2213
2382
|
source.loop = loop;
|
|
2214
2383
|
|
|
2215
|
-
// create
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2384
|
+
// create and connect gain node (createGain is more widley spported then GainNode construtor)
|
|
2385
|
+
const gainNode = audioContext.createGain();
|
|
2386
|
+
gainNode.gain.value = soundVolume*volume;
|
|
2387
|
+
gainNode.connect(audioContext.destination);
|
|
2388
|
+
|
|
2389
|
+
// connect source to gain
|
|
2390
|
+
(
|
|
2391
|
+
window.StereoPannerNode ? // create pan node if possible
|
|
2392
|
+
source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)}))
|
|
2393
|
+
: source
|
|
2394
|
+
)
|
|
2395
|
+
.connect(gainNode);
|
|
2220
2396
|
|
|
2221
2397
|
// play and return sound
|
|
2222
2398
|
source.start();
|
|
@@ -2574,8 +2750,9 @@ class TileLayerData
|
|
|
2574
2750
|
/**
|
|
2575
2751
|
* Tile layer object - cached rendering system for tile layers
|
|
2576
2752
|
* <br> - Each Tile layer is rendered to an off screen canvas
|
|
2577
|
-
* <br> -
|
|
2578
|
-
* <br> -
|
|
2753
|
+
* <br> - To allow dynamic modifications, layers are rendered using canvas 2d
|
|
2754
|
+
* <br> - Some devices like mobile phones are limited to 4k texture resolution
|
|
2755
|
+
* <br> - So with 16x16 tiles this limits layers to 256x256 on mobile devices
|
|
2579
2756
|
* @extends EngineObject
|
|
2580
2757
|
* @example
|
|
2581
2758
|
* // create tile collision and visible tile layer
|
|
@@ -2585,11 +2762,11 @@ class TileLayerData
|
|
|
2585
2762
|
class TileLayer extends EngineObject
|
|
2586
2763
|
{
|
|
2587
2764
|
/** Create a tile layer object
|
|
2588
|
-
* @param {Vector2} [position=new Vector2(
|
|
2589
|
-
* @param {Vector2} [size=objectDefaultSize]
|
|
2590
|
-
* @param {Vector2} [tileSize=tileSizeDefault]
|
|
2591
|
-
* @param {Vector2} [scale=new Vector2(1,1)]
|
|
2592
|
-
* @param {Number} [renderOrder=0]
|
|
2765
|
+
* @param {Vector2} [position=new Vector2()] - World space position
|
|
2766
|
+
* @param {Vector2} [size=objectDefaultSize] - World space size
|
|
2767
|
+
* @param {Vector2} [tileSize=tileSizeDefault] - Size of tiles in source pixels
|
|
2768
|
+
* @param {Vector2} [scale=new Vector2(1,1)] - How much to scale this layer when rendered
|
|
2769
|
+
* @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
|
|
2593
2770
|
*/
|
|
2594
2771
|
constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1), renderOrder=0)
|
|
2595
2772
|
{
|
|
@@ -2640,7 +2817,7 @@ class TileLayer extends EngineObject
|
|
|
2640
2817
|
// flush and copy gl canvas because tile canvas does not use webgl
|
|
2641
2818
|
glEnable && !glOverlay && !this.isOverlay && glCopyToContext(mainContext);
|
|
2642
2819
|
|
|
2643
|
-
// draw the entire cached level onto the
|
|
2820
|
+
// draw the entire cached level onto the canvas
|
|
2644
2821
|
const pos = worldToScreen(this.pos.add(vec2(0,this.size.y*this.scale.y)));
|
|
2645
2822
|
(this.isOverlay ? overlayContext : mainContext).drawImage
|
|
2646
2823
|
(
|
|
@@ -2649,35 +2826,36 @@ class TileLayer extends EngineObject
|
|
|
2649
2826
|
);
|
|
2650
2827
|
}
|
|
2651
2828
|
|
|
2652
|
-
/** Draw all the tile data to an offscreen canvas
|
|
2829
|
+
/** Draw all the tile data to an offscreen canvas
|
|
2830
|
+
* - This may be slow in some browsers
|
|
2831
|
+
*/
|
|
2653
2832
|
redraw()
|
|
2654
2833
|
{
|
|
2655
|
-
this.redrawStart();
|
|
2834
|
+
this.redrawStart(1);
|
|
2656
2835
|
this.drawAllTileData();
|
|
2657
2836
|
this.redrawEnd();
|
|
2658
2837
|
}
|
|
2659
2838
|
|
|
2660
2839
|
/** Call to start the redraw process
|
|
2661
|
-
* @param {Boolean} [clear=
|
|
2662
|
-
redrawStart(clear =
|
|
2840
|
+
* @param {Boolean} [clear=0] - Should it clear the canvas before drawing */
|
|
2841
|
+
redrawStart(clear = 0)
|
|
2663
2842
|
{
|
|
2664
|
-
// clear and set size
|
|
2665
|
-
const width = this.size.x * this.tileSize.x;
|
|
2666
|
-
const height = this.size.y * this.tileSize.y;
|
|
2667
2843
|
if (clear)
|
|
2668
2844
|
{
|
|
2669
|
-
|
|
2670
|
-
this.canvas.
|
|
2845
|
+
// clear and set size
|
|
2846
|
+
this.canvas.width = this.size.x * this.tileSize.x;
|
|
2847
|
+
this.canvas.height = this.size.y * this.tileSize.y;
|
|
2671
2848
|
}
|
|
2672
2849
|
|
|
2673
2850
|
// save current render settings
|
|
2674
|
-
this.savedRenderSettings = [mainCanvas, mainContext];
|
|
2851
|
+
this.savedRenderSettings = [mainCanvas, mainContext, cameraPos, cameraScale];
|
|
2675
2852
|
|
|
2676
|
-
//
|
|
2853
|
+
// use normal rendering system to render the tiles
|
|
2677
2854
|
mainCanvas = this.canvas;
|
|
2678
2855
|
mainContext = this.context;
|
|
2679
|
-
|
|
2680
|
-
|
|
2856
|
+
cameraPos = this.size.scale(.5);
|
|
2857
|
+
cameraScale = this.tileSize.x;
|
|
2858
|
+
enginePreRender();
|
|
2681
2859
|
}
|
|
2682
2860
|
|
|
2683
2861
|
/** Call to end the redraw process */
|
|
@@ -2688,7 +2866,7 @@ class TileLayer extends EngineObject
|
|
|
2688
2866
|
//debugSaveCanvas(this.canvas);
|
|
2689
2867
|
|
|
2690
2868
|
// set stuff back to normal
|
|
2691
|
-
[mainCanvas, mainContext] = this.savedRenderSettings;
|
|
2869
|
+
[mainCanvas, mainContext, cameraPos, cameraScale] = this.savedRenderSettings;
|
|
2692
2870
|
}
|
|
2693
2871
|
|
|
2694
2872
|
/** Draw the tile at a given position
|
|
@@ -2716,7 +2894,7 @@ class TileLayer extends EngineObject
|
|
|
2716
2894
|
this.drawTileData(vec2(x,y));
|
|
2717
2895
|
}
|
|
2718
2896
|
|
|
2719
|
-
/** Draw directly to the
|
|
2897
|
+
/** Draw directly to the 2D canvas in world space (bipass webgl)
|
|
2720
2898
|
* @param {Vector2} pos
|
|
2721
2899
|
* @param {Vector2} size
|
|
2722
2900
|
* @param {Number} [angle=0]
|
|
@@ -2756,7 +2934,7 @@ class TileLayer extends EngineObject
|
|
|
2756
2934
|
else
|
|
2757
2935
|
{
|
|
2758
2936
|
const cols = tileImage.width/tileSize.x;
|
|
2759
|
-
context.globalAlpha = color.a; //
|
|
2937
|
+
context.globalAlpha = color.a; // only alpha, no color, is supported in this mode
|
|
2760
2938
|
context.drawImage(tileImage,
|
|
2761
2939
|
(tileIndex%cols)*tileSize.x, (tileIndex/cols|0)*tileSize.x,
|
|
2762
2940
|
tileSize.x, tileSize.y, -.5, -.5, 1, 1);
|
|
@@ -2824,7 +3002,7 @@ class ParticleEmitter extends EngineObject
|
|
|
2824
3002
|
* @param {Number} [randomness=.2] - Apply extra randomness percent
|
|
2825
3003
|
* @param {Boolean} [collideTiles=0] - Do particles collide against tiles
|
|
2826
3004
|
* @param {Boolean} [additive=0] - Should particles use addtive blend
|
|
2827
|
-
* @param {Boolean} [randomColorLinear=
|
|
3005
|
+
* @param {Boolean} [randomColorLinear=1] - Should color be randomized linearly or across each component
|
|
2828
3006
|
* @param {Number} [renderOrder=0] - Render order for particles (additive is above other stuff by default)
|
|
2829
3007
|
*/
|
|
2830
3008
|
constructor
|
|
@@ -2925,9 +3103,9 @@ class ParticleEmitter extends EngineObject
|
|
|
2925
3103
|
if (!this.emitTime || this.getAliveTime() <= this.emitTime)
|
|
2926
3104
|
{
|
|
2927
3105
|
// emit particles
|
|
2928
|
-
if (this.emitRate)
|
|
3106
|
+
if (this.emitRate * particleEmitRateScale)
|
|
2929
3107
|
{
|
|
2930
|
-
const rate = 1/this.emitRate;
|
|
3108
|
+
const rate = 1/this.emitRate/particleEmitRateScale;
|
|
2931
3109
|
for (this.emitTimeBuffer += timeDelta; this.emitTimeBuffer > 0; this.emitTimeBuffer -= rate)
|
|
2932
3110
|
this.emitParticle();
|
|
2933
3111
|
}
|
|
@@ -3090,7 +3268,7 @@ function medalsInit(saveName)
|
|
|
3090
3268
|
{
|
|
3091
3269
|
// check if medals are unlocked
|
|
3092
3270
|
medalsSaveName = saveName;
|
|
3093
|
-
debugMedals || medals.forEach(medal=> medal.unlocked = localStorage[medal.storageKey()]);
|
|
3271
|
+
debugMedals || medals.forEach(medal=> medal.unlocked = 1 || localStorage[medal.storageKey()]);
|
|
3094
3272
|
}
|
|
3095
3273
|
|
|
3096
3274
|
/**
|
|
@@ -3123,13 +3301,9 @@ class Medal
|
|
|
3123
3301
|
this.name = name;
|
|
3124
3302
|
this.description = description;
|
|
3125
3303
|
this.icon = icon;
|
|
3126
|
-
|
|
3304
|
+
this.image = new Image();
|
|
3127
3305
|
if (src)
|
|
3128
|
-
{
|
|
3129
|
-
// load image
|
|
3130
|
-
this.image = new Image();
|
|
3131
3306
|
this.image.src = src;
|
|
3132
|
-
}
|
|
3133
3307
|
}
|
|
3134
3308
|
|
|
3135
3309
|
/** Unlocks a medal if not already unlocked */
|
|
@@ -3159,21 +3333,23 @@ class Medal
|
|
|
3159
3333
|
const y = -medalDisplayHeight*hidePercent;
|
|
3160
3334
|
|
|
3161
3335
|
// draw containing rect and clip to that region
|
|
3162
|
-
context.
|
|
3336
|
+
context.save();
|
|
3337
|
+
context.beginPath();
|
|
3163
3338
|
context.fillStyle = '#ddd'
|
|
3164
3339
|
context.fill(context.rect(x, y, width, medalDisplayHeight));
|
|
3165
|
-
context.strokeStyle =
|
|
3166
|
-
context.lineWidth =
|
|
3340
|
+
context.strokeStyle = '#000';
|
|
3341
|
+
context.lineWidth = 3;
|
|
3167
3342
|
context.stroke();
|
|
3168
3343
|
context.clip();
|
|
3169
3344
|
|
|
3170
3345
|
// draw the icon and text
|
|
3171
3346
|
this.renderIcon(x+15+medalDisplayIconSize/2, y+medalDisplayHeight/2);
|
|
3172
3347
|
context.textAlign = 'left';
|
|
3173
|
-
context.font = '
|
|
3174
|
-
context.fillText(this.name, x+medalDisplayIconSize+
|
|
3175
|
-
context.font = '
|
|
3176
|
-
context.
|
|
3348
|
+
context.font = '38px '+ fontDefault;
|
|
3349
|
+
context.fillText(this.name, x+medalDisplayIconSize+30, y+28);
|
|
3350
|
+
context.font = '24px '+ fontDefault;
|
|
3351
|
+
context.fillText(this.description, x+medalDisplayIconSize+30, y+60);
|
|
3352
|
+
context.restore();
|
|
3177
3353
|
}
|
|
3178
3354
|
|
|
3179
3355
|
/** Render the icon for a medal
|
|
@@ -3185,11 +3361,11 @@ class Medal
|
|
|
3185
3361
|
{
|
|
3186
3362
|
// draw the image or icon
|
|
3187
3363
|
const context = overlayContext;
|
|
3364
|
+
context.fillStyle = '#000';
|
|
3188
3365
|
context.textAlign = 'center';
|
|
3189
3366
|
context.textBaseline = 'middle';
|
|
3190
|
-
context.font = size*.
|
|
3191
|
-
|
|
3192
|
-
if (this.image)
|
|
3367
|
+
context.font = size*.7 + 'px '+ fontDefault;
|
|
3368
|
+
if (this.image.src)
|
|
3193
3369
|
context.drawImage(this.image, x-size/2, y-size/2, size, size);
|
|
3194
3370
|
else
|
|
3195
3371
|
context.fillText(this.icon, x, y); // show icon if there is no image
|
|
@@ -3265,7 +3441,6 @@ class Newgrounds
|
|
|
3265
3441
|
if (medal)
|
|
3266
3442
|
{
|
|
3267
3443
|
// copy newgrounds medal data
|
|
3268
|
-
medal.image = new Image();
|
|
3269
3444
|
medal.image.src = newgroundsMedal['icon'];
|
|
3270
3445
|
medal.name = newgroundsMedal['name'];
|
|
3271
3446
|
medal.description = newgroundsMedal['description'];
|
|
@@ -3394,32 +3569,27 @@ function glInit()
|
|
|
3394
3569
|
|
|
3395
3570
|
// create the canvas and tile texture
|
|
3396
3571
|
glCanvas = document.createElement('canvas');
|
|
3397
|
-
glContext = glCanvas.getContext('webgl');
|
|
3398
|
-
|
|
3572
|
+
glContext = glCanvas.getContext('webgl', {antialias: false});
|
|
3573
|
+
glCanvas.style = styleCanvas;
|
|
3399
3574
|
glTileTexture = glCreateTexture(tileImage);
|
|
3400
3575
|
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
// some browsers are much faster without copying the gl buffer so we just overlay it instead
|
|
3404
|
-
document.body.appendChild(glCanvas);
|
|
3405
|
-
glCanvas.style = mainCanvas.style.cssText;
|
|
3406
|
-
}
|
|
3576
|
+
// some browsers are much faster without copying the gl buffer so we just overlay it instead
|
|
3577
|
+
glOverlay && document.body.appendChild(glCanvas);
|
|
3407
3578
|
|
|
3408
3579
|
// setup vertex and fragment shaders
|
|
3409
3580
|
glShader = glCreateProgram(
|
|
3410
3581
|
'precision highp float;'+ // use highp for better accuracy, lowp for better perf
|
|
3411
3582
|
'uniform mat4 m;'+ // transform matrix
|
|
3412
|
-
'attribute
|
|
3413
|
-
'attribute
|
|
3414
|
-
'attribute vec4 c,b;'+ // color, additiveColor
|
|
3583
|
+
'attribute vec2 p,t;'+ // position, uv
|
|
3584
|
+
'attribute vec4 c,a;'+ // color, additiveColor
|
|
3415
3585
|
'varying vec2 v;'+ // return uv
|
|
3416
3586
|
'varying vec4 d,e;'+ // return color, additiveColor
|
|
3417
3587
|
'void main(){'+ // shader entry point
|
|
3418
|
-
'gl_Position=m*vec4(
|
|
3419
|
-
'v=t;d=c;e=
|
|
3588
|
+
'gl_Position=m*vec4(p,1,1);'+// transform position
|
|
3589
|
+
'v=t;d=c;e=a;'+ // pass stuff to fragment shader
|
|
3420
3590
|
'}' // end of shader
|
|
3421
3591
|
,
|
|
3422
|
-
'precision
|
|
3592
|
+
'precision highp float;'+ // use highp for better accuracy, lowp for better perf
|
|
3423
3593
|
'varying vec2 v;'+ // uv
|
|
3424
3594
|
'varying vec4 d,e;'+ // color, additiveColor
|
|
3425
3595
|
'uniform sampler2D j;'+ // texture
|
|
@@ -3435,6 +3605,7 @@ function glInit()
|
|
|
3435
3605
|
glColorData = new Uint32Array(glVertexData);
|
|
3436
3606
|
|
|
3437
3607
|
// setup the vertex data array
|
|
3608
|
+
let offset = glBatchCount = 0;
|
|
3438
3609
|
const initVertexAttribArray = (name, type, typeSize, size, normalize=0)=>
|
|
3439
3610
|
{
|
|
3440
3611
|
const location = glContext.getAttribLocation(glShader, name);
|
|
@@ -3442,13 +3613,10 @@ function glInit()
|
|
|
3442
3613
|
glContext.vertexAttribPointer(location, size, type, normalize, gl_VERTEX_BYTE_STRIDE, offset);
|
|
3443
3614
|
offset += size*typeSize;
|
|
3444
3615
|
}
|
|
3445
|
-
let offset = glBatchCount = 0;
|
|
3446
|
-
initVertexAttribArray('a', gl_FLOAT, 4, 1); // angle
|
|
3447
3616
|
initVertexAttribArray('p', gl_FLOAT, 4, 2); // position
|
|
3448
|
-
initVertexAttribArray('s', gl_FLOAT, 4, 2); // size
|
|
3449
3617
|
initVertexAttribArray('t', gl_FLOAT, 4, 2); // texture coords
|
|
3450
3618
|
initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4, 1); // color
|
|
3451
|
-
initVertexAttribArray('
|
|
3619
|
+
initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
|
|
3452
3620
|
}
|
|
3453
3621
|
|
|
3454
3622
|
/** Set the WebGl blend mode, normally you should call setBlendMode instead
|
|
@@ -3469,7 +3637,8 @@ function glSetBlendMode(additive)
|
|
|
3469
3637
|
function glSetTexture(texture=glTileTexture)
|
|
3470
3638
|
{
|
|
3471
3639
|
if (!glEnable) return;
|
|
3472
|
-
|
|
3640
|
+
|
|
3641
|
+
// must flush cache with the old texture to set a new one
|
|
3473
3642
|
if (texture != glActiveTexture)
|
|
3474
3643
|
glFlush();
|
|
3475
3644
|
|
|
@@ -3562,6 +3731,7 @@ function glPreRender(width, height, cameraX, cameraY, cameraScale)
|
|
|
3562
3731
|
|
|
3563
3732
|
// clear and set to same size as main canvas
|
|
3564
3733
|
glContext.viewport(0, 0, glCanvas.width = width, glCanvas.height = height);
|
|
3734
|
+
glContext.clear(gl_COLOR_BUFFER_BIT);
|
|
3565
3735
|
|
|
3566
3736
|
// set up the shader
|
|
3567
3737
|
glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = glTileTexture);
|
|
@@ -3605,15 +3775,13 @@ function glFlush()
|
|
|
3605
3775
|
* @memberof WebGL */
|
|
3606
3776
|
function glCopyToContext(context, forceDraw)
|
|
3607
3777
|
{
|
|
3608
|
-
if (!glEnable || !glBatchCount)
|
|
3778
|
+
if (!glEnable || !glBatchCount) return;
|
|
3609
3779
|
|
|
3610
3780
|
glFlush();
|
|
3781
|
+
|
|
3782
|
+
// do not draw in overlay mode because the canvas is visible
|
|
3611
3783
|
if (!glOverlay || forceDraw)
|
|
3612
|
-
{
|
|
3613
|
-
// do not draw/clear in overlay mode because the canvas is visible
|
|
3614
3784
|
context.drawImage(glCanvas, 0, 0);
|
|
3615
|
-
glContext.clear(gl_COLOR_BUFFER_BIT);
|
|
3616
|
-
}
|
|
3617
3785
|
}
|
|
3618
3786
|
|
|
3619
3787
|
/** Add a sprite to the gl draw list, used by all gl draw functions
|
|
@@ -3636,51 +3804,49 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba=0xffffff
|
|
|
3636
3804
|
// flush if there is no room for more verts or if different blend mode
|
|
3637
3805
|
if (glBatchCount == gl_MAX_BATCH || glBatchAdditive != glAdditive)
|
|
3638
3806
|
glFlush();
|
|
3807
|
+
|
|
3808
|
+
// prepare to create the verts from size and angle
|
|
3809
|
+
const c = Math.cos(angle)/2, s = Math.sin(angle)/2;
|
|
3810
|
+
const cX = c*sizeX, cY = c*sizeY, sX = s*sizeX, sY = s*sizeY;
|
|
3639
3811
|
|
|
3640
3812
|
// setup 2 triangles to form a quad
|
|
3641
3813
|
let offset = glBatchCount++ * gl_VERTICES_PER_QUAD * gl_INDICIES_PER_VERT;
|
|
3642
3814
|
|
|
3643
3815
|
// vertex 0
|
|
3644
|
-
glPositionData[offset++] =
|
|
3645
|
-
glPositionData[offset++] =
|
|
3646
|
-
glPositionData[offset++] =
|
|
3647
|
-
|
|
3648
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3816
|
+
glPositionData[offset++] = x - cX - sY;
|
|
3817
|
+
glPositionData[offset++] = y - cY + sX;
|
|
3818
|
+
glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
|
|
3819
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3649
3820
|
|
|
3650
3821
|
// vertex 1
|
|
3651
|
-
glPositionData[offset++] =
|
|
3652
|
-
glPositionData[offset++] =
|
|
3653
|
-
glPositionData[offset++] =
|
|
3654
|
-
|
|
3655
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3822
|
+
glPositionData[offset++] = x + cX + sY;
|
|
3823
|
+
glPositionData[offset++] = y + cY - sX;
|
|
3824
|
+
glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
|
|
3825
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3656
3826
|
|
|
3657
3827
|
// vertex 2
|
|
3658
|
-
glPositionData[offset++] =
|
|
3659
|
-
glPositionData[offset++] =
|
|
3660
|
-
glPositionData[offset++] =
|
|
3661
|
-
|
|
3662
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3828
|
+
glPositionData[offset++] = x - cX + sY;
|
|
3829
|
+
glPositionData[offset++] = y + cY + sX;
|
|
3830
|
+
glPositionData[offset++] = uv0X; glPositionData[offset++] = uv0Y;
|
|
3831
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3663
3832
|
|
|
3664
3833
|
// vertex 0
|
|
3665
|
-
glPositionData[offset++] =
|
|
3666
|
-
glPositionData[offset++] =
|
|
3667
|
-
glPositionData[offset++] =
|
|
3668
|
-
|
|
3669
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3834
|
+
glPositionData[offset++] = x - cX - sY;
|
|
3835
|
+
glPositionData[offset++] = y - cY + sX;
|
|
3836
|
+
glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
|
|
3837
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3670
3838
|
|
|
3671
3839
|
// vertex 3
|
|
3672
|
-
glPositionData[offset++] =
|
|
3673
|
-
glPositionData[offset++] =
|
|
3674
|
-
glPositionData[offset++] =
|
|
3675
|
-
|
|
3676
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3840
|
+
glPositionData[offset++] = x + cX - sY;
|
|
3841
|
+
glPositionData[offset++] = y - cY - sX;
|
|
3842
|
+
glPositionData[offset++] = uv1X; glPositionData[offset++] = uv1Y;
|
|
3843
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3677
3844
|
|
|
3678
3845
|
// vertex 1
|
|
3679
|
-
glPositionData[offset++] =
|
|
3680
|
-
glPositionData[offset++] =
|
|
3681
|
-
glPositionData[offset++] =
|
|
3682
|
-
|
|
3683
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3846
|
+
glPositionData[offset++] = x + cX + sY;
|
|
3847
|
+
glPositionData[offset++] = y + cY - sX;
|
|
3848
|
+
glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
|
|
3849
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3684
3850
|
}
|
|
3685
3851
|
|
|
3686
3852
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -3712,6 +3878,6 @@ gl_LINK_STATUS = 35714,
|
|
|
3712
3878
|
|
|
3713
3879
|
// constants for batch rendering
|
|
3714
3880
|
gl_VERTICES_PER_QUAD = 6,
|
|
3715
|
-
gl_INDICIES_PER_VERT =
|
|
3881
|
+
gl_INDICIES_PER_VERT = 6,
|
|
3716
3882
|
gl_MAX_BATCH = 1<<16,
|
|
3717
|
-
gl_VERTEX_BYTE_STRIDE =
|
|
3883
|
+
gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2; // vec2 * 2 + (char * 4) * 2
|