littlejsengine 1.1.18 → 1.2.3
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 +17 -11
- package/build.bat +1 -0
- package/engine/engine.all.js +495 -504
- package/engine/engine.all.min.js +1 -1
- package/engine/engine.all.release.js +418 -259
- package/engine/engine.js +31 -23
- package/engine/engineAudio.js +21 -7
- package/engine/engineBuild.bat +9 -1
- package/engine/engineBuildSetup.bat +2 -1
- package/engine/engineDebug.js +81 -247
- package/engine/engineDraw.js +109 -21
- package/engine/engineFont.png +0 -0
- package/engine/engineInput.js +103 -50
- package/engine/engineMedals.js +17 -20
- package/engine/engineObject.js +14 -23
- package/engine/engineParticles.js +8 -6
- package/engine/engineRelease.js +3 -1
- package/engine/engineSettings.js +20 -10
- package/engine/engineTileLayer.js +19 -18
- package/engine/engineUtilities.js +14 -12
- package/engine/engineWebGL.js +59 -68
- package/engine/index.d.ts +1617 -0
- package/examples/breakout/game.js +9 -7
- package/examples/breakout/gameObjects.js +3 -3
- package/examples/breakout/index.html +6 -4
- package/examples/platformer/game.js +5 -5
- package/examples/platformer/gameEffects.js +11 -14
- package/examples/platformer/gameLevel.js +14 -7
- package/examples/platformer/gameObjects.js +3 -3
- 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 +2 -2
- package/examples/puzzle/index.html +5 -3
- package/examples/screenshot.jpg +0 -0
- package/examples/stress/index.html +7 -5
- package/game.js +9 -8
- 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
|
|
@@ -552,7 +556,7 @@ let tileSizeDefault = vec2(16);
|
|
|
552
556
|
/** Prevent tile bleeding from neighbors in pixels
|
|
553
557
|
* @default
|
|
554
558
|
* @memberof Settings */
|
|
555
|
-
let
|
|
559
|
+
let tileFixBleedScale = .3;
|
|
556
560
|
|
|
557
561
|
///////////////////////////////////////////////////////////////////////////////
|
|
558
562
|
// Object settings
|
|
@@ -598,6 +602,11 @@ let objectMaxSpeed = 1;
|
|
|
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
|
|
|
@@ -643,11 +652,6 @@ let gamepadDirectionEmulateStick = 1;
|
|
|
643
652
|
* @memberof Settings */
|
|
644
653
|
let inputWASDEmulateDirection = 1;
|
|
645
654
|
|
|
646
|
-
/** If true touch input is routed to mouse functions
|
|
647
|
-
* @default
|
|
648
|
-
* @memberof Settings */
|
|
649
|
-
let touchMouseEnable = 1;
|
|
650
|
-
|
|
651
655
|
/** True if touch gamepad should appear on mobile devices
|
|
652
656
|
* <br> - Supports left analog stick, 4 face buttons and start button (button 9)
|
|
653
657
|
* <br> - Must be set by end of gameInit to be activated
|
|
@@ -655,6 +659,11 @@ let touchMouseEnable = 1;
|
|
|
655
659
|
* @memberof Settings */
|
|
656
660
|
let touchGamepadEnable = 0;
|
|
657
661
|
|
|
662
|
+
/** True if touch gamepad should be analog stick or false to use if 8 way dpad
|
|
663
|
+
* @default
|
|
664
|
+
* @memberof Settings */
|
|
665
|
+
let touchGamepadAnalog = 1;
|
|
666
|
+
|
|
658
667
|
/** Size of virutal gamepad for touch devices in pixels
|
|
659
668
|
* @default
|
|
660
669
|
* @memberof Settings */
|
|
@@ -663,7 +672,12 @@ let touchGamepadSize = 80;
|
|
|
663
672
|
/** Transparency of touch gamepad overlay
|
|
664
673
|
* @default
|
|
665
674
|
* @memberof Settings */
|
|
666
|
-
let touchGamepadAlpha = .
|
|
675
|
+
let touchGamepadAlpha = .3;
|
|
676
|
+
|
|
677
|
+
/** Allow vibration hardware if it exists
|
|
678
|
+
* @default
|
|
679
|
+
* @memberof Settings */
|
|
680
|
+
let vibrateEnable = 1;
|
|
667
681
|
|
|
668
682
|
///////////////////////////////////////////////////////////////////////////////
|
|
669
683
|
// Audio settings
|
|
@@ -676,7 +690,7 @@ let soundVolume = .5;
|
|
|
676
690
|
/** All audio code can be disabled and removed from build
|
|
677
691
|
* @default
|
|
678
692
|
* @memberof Settings */
|
|
679
|
-
let soundEnable =
|
|
693
|
+
let soundEnable = 1;
|
|
680
694
|
|
|
681
695
|
/** Default range where sound no longer plays
|
|
682
696
|
* @default
|
|
@@ -709,12 +723,12 @@ let medalDisplayWidth = 640;
|
|
|
709
723
|
/** Height of medal display
|
|
710
724
|
* @default
|
|
711
725
|
* @memberof Settings */
|
|
712
|
-
let medalDisplayHeight =
|
|
726
|
+
let medalDisplayHeight = 80;
|
|
713
727
|
|
|
714
728
|
/** Size of icon in medal display
|
|
715
729
|
* @default
|
|
716
730
|
* @memberof Settings */
|
|
717
|
-
let medalDisplayIconSize =
|
|
731
|
+
let medalDisplayIconSize = 50;
|
|
718
732
|
/*
|
|
719
733
|
LittleJS - The Tiny JavaScript Game Engine That Can!
|
|
720
734
|
MIT License - Copyright 2021 Frank Force
|
|
@@ -739,7 +753,7 @@ let medalDisplayIconSize = 80;
|
|
|
739
753
|
const engineName = 'LittleJS';
|
|
740
754
|
|
|
741
755
|
/** Version of engine */
|
|
742
|
-
const engineVersion = '1.
|
|
756
|
+
const engineVersion = '1.2.3';
|
|
743
757
|
|
|
744
758
|
/** Frames per second to update objects
|
|
745
759
|
* @default */
|
|
@@ -752,7 +766,7 @@ const timeDelta = 1/frameRate;
|
|
|
752
766
|
/** Array containing all engine objects */
|
|
753
767
|
let engineObjects = [];
|
|
754
768
|
|
|
755
|
-
/** Array containing only objects that are set to collide with other objects (for optimization) */
|
|
769
|
+
/** Array containing only objects that are set to collide with other objects this frame (for optimization) */
|
|
756
770
|
let engineObjectsCollide = [];
|
|
757
771
|
|
|
758
772
|
/** Current update frame, used to calculate time */
|
|
@@ -768,8 +782,15 @@ let timeReal = 0;
|
|
|
768
782
|
let paused = 0;
|
|
769
783
|
|
|
770
784
|
// Engine internal variables not exposed to documentation
|
|
771
|
-
let frameTimeLastMS = 0, frameTimeBufferMS = 0,
|
|
772
|
-
|
|
785
|
+
let frameTimeLastMS = 0, frameTimeBufferMS = 0, tileImageSize, tileImageFixBleed;
|
|
786
|
+
|
|
787
|
+
// Engine stat tracking, if showWatermark is true
|
|
788
|
+
let averageFPS, drawCount;
|
|
789
|
+
|
|
790
|
+
// css text used for elements created by engine
|
|
791
|
+
const styleBody = 'margin:0;overflow:hidden;background:#000' +
|
|
792
|
+
';touch-action:none;user-select:none;-webkit-user-select:none;-moz-user-select:none';
|
|
793
|
+
const styleCanvas = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
|
|
773
794
|
|
|
774
795
|
///////////////////////////////////////////////////////////////////////////////
|
|
775
796
|
|
|
@@ -787,17 +808,14 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
787
808
|
tileImage.onerror = tileImage.onload = ()=>
|
|
788
809
|
{
|
|
789
810
|
// save tile image info
|
|
790
|
-
|
|
811
|
+
tileImageFixBleed = vec2(tileFixBleedScale).divide(tileImageSize = vec2(tileImage.width, tileImage.height));
|
|
791
812
|
debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
|
|
792
|
-
shrinkTilesX = tileBleedShrinkFix/tileImageSize.x;
|
|
793
|
-
shrinkTilesY = tileBleedShrinkFix/tileImageSize.y;
|
|
794
813
|
|
|
795
814
|
// setup html
|
|
815
|
+
document.body.style = styleBody;
|
|
796
816
|
document.body.appendChild(mainCanvas = document.createElement('canvas'));
|
|
797
|
-
document.body.style = 'margin:0;overflow:hidden;background:#000' +
|
|
798
|
-
(touchMouseEnable?';touch-action:none;user-select:none;-webkit-user-select:none;-moz-user-select:none':'');
|
|
799
|
-
mainCanvas.style = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
|
|
800
817
|
mainContext = mainCanvas.getContext('2d');
|
|
818
|
+
mainCanvas.style = styleCanvas;
|
|
801
819
|
|
|
802
820
|
// init stuff and start engine
|
|
803
821
|
debugInit();
|
|
@@ -805,8 +823,8 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
805
823
|
|
|
806
824
|
// create overlay canvas for hud to appear above gl canvas
|
|
807
825
|
document.body.appendChild(overlayCanvas = document.createElement('canvas'));
|
|
808
|
-
overlayCanvas.style = mainCanvas.style.cssText;
|
|
809
826
|
overlayContext = overlayCanvas.getContext('2d');
|
|
827
|
+
overlayCanvas.style = styleCanvas;
|
|
810
828
|
|
|
811
829
|
gameInit();
|
|
812
830
|
touchGamepadCreate();
|
|
@@ -822,7 +840,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
822
840
|
let frameTimeDeltaMS = frameTimeMS - frameTimeLastMS;
|
|
823
841
|
frameTimeLastMS = frameTimeMS;
|
|
824
842
|
if (debug || showWatermark)
|
|
825
|
-
|
|
843
|
+
averageFPS = lerp(.05, averageFPS || 0, 1e3/(frameTimeDeltaMS||1));
|
|
826
844
|
if (debug)
|
|
827
845
|
frameTimeDeltaMS *= keyIsDown(107) ? 5 : keyIsDown(109) ? .2 : 1; // +/- to speed/slow time
|
|
828
846
|
timeReal += frameTimeDeltaMS / 1e3;
|
|
@@ -909,7 +927,8 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
909
927
|
overlayContext.font = '1em monospace';
|
|
910
928
|
overlayContext.fillStyle = '#000';
|
|
911
929
|
const text = engineName + ' ' + 'v' + engineVersion + ' / '
|
|
912
|
-
+ drawCount + ' / ' + engineObjects.length + ' / ' +
|
|
930
|
+
+ drawCount + ' / ' + engineObjects.length + ' / ' + averageFPS.toFixed(1)
|
|
931
|
+
+ ' ' + (glEnable ? 'GL' : '2D') ;
|
|
913
932
|
overlayContext.fillText(text, mainCanvas.width-3, 3);
|
|
914
933
|
overlayContext.fillStyle = '#fff';
|
|
915
934
|
overlayContext.fillText(text, mainCanvas.width-2, 2);
|
|
@@ -926,7 +945,11 @@ function enginePreRender()
|
|
|
926
945
|
{
|
|
927
946
|
// save canvas size and clear canvases
|
|
928
947
|
mainCanvasSize = vec2(overlayCanvas.width = mainCanvas.width, overlayCanvas.height = mainCanvas.height);
|
|
929
|
-
|
|
948
|
+
|
|
949
|
+
// disable smoothing for pixel art
|
|
950
|
+
mainContext.imageSmoothingEnabled = !cavasPixelated;
|
|
951
|
+
|
|
952
|
+
// setup gl rendering if enabled
|
|
930
953
|
glPreRender(mainCanvas.width, mainCanvas.height, cameraPos.x, cameraPos.y, cameraScale);
|
|
931
954
|
}
|
|
932
955
|
|
|
@@ -935,6 +958,9 @@ function enginePreRender()
|
|
|
935
958
|
/** Calls update on each engine object (recursively if child), removes destroyed objects, and updated time */
|
|
936
959
|
function engineObjectsUpdate()
|
|
937
960
|
{
|
|
961
|
+
// get list of solid objects for physics optimzation
|
|
962
|
+
engineObjectsCollide = engineObjects.filter(o=>o.collideSolidObjects);
|
|
963
|
+
|
|
938
964
|
// recursive object update
|
|
939
965
|
const updateObject = (o)=>
|
|
940
966
|
{
|
|
@@ -950,17 +976,16 @@ function engineObjectsUpdate()
|
|
|
950
976
|
|
|
951
977
|
// remove destroyed objects
|
|
952
978
|
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
953
|
-
engineObjectsCollide = engineObjectsCollide.filter(o=>!o.destroyed);
|
|
954
979
|
|
|
955
980
|
// increment frame and update time
|
|
956
981
|
time = ++frame / frameRate;
|
|
957
982
|
}
|
|
958
983
|
|
|
959
|
-
/**
|
|
984
|
+
/** Destroy and remove all objects */
|
|
960
985
|
function engineObjectsDestroy()
|
|
961
986
|
{
|
|
962
987
|
for (const o of engineObjects)
|
|
963
|
-
o.
|
|
988
|
+
o.parent || o.destroy();
|
|
964
989
|
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
965
990
|
}
|
|
966
991
|
|
|
@@ -971,21 +996,18 @@ function engineObjectsDestroy()
|
|
|
971
996
|
* @param {Array} [objects=engineObjects] - List of objects to check */
|
|
972
997
|
function engineObjectsCallback(pos, size, callbackFunction, objects=engineObjects)
|
|
973
998
|
{
|
|
974
|
-
if (!pos)
|
|
999
|
+
if (!pos) // all objects
|
|
975
1000
|
{
|
|
976
|
-
// all objects
|
|
977
1001
|
for (const o of objects)
|
|
978
1002
|
callbackFunction(o);
|
|
979
1003
|
}
|
|
980
|
-
else if (size.x != undefined)
|
|
1004
|
+
else if (size.x != undefined) // bounding box test
|
|
981
1005
|
{
|
|
982
|
-
// aabb test
|
|
983
1006
|
for (const o of objects)
|
|
984
1007
|
isOverlapping(pos, size, o.pos, o.size) && callbackFunction(o);
|
|
985
1008
|
}
|
|
986
|
-
else
|
|
1009
|
+
else // circle test
|
|
987
1010
|
{
|
|
988
|
-
// circle test
|
|
989
1011
|
const sizeSquared = size*size;
|
|
990
1012
|
for (const o of objects)
|
|
991
1013
|
pos.distanceSquared(o.pos) < sizeSquared && callbackFunction(o);
|
|
@@ -1025,7 +1047,7 @@ function engineObjectsCallback(pos, size, callbackFunction, objects=engineObject
|
|
|
1025
1047
|
class EngineObject
|
|
1026
1048
|
{
|
|
1027
1049
|
/** Create an engine object and adds it to the list of objects
|
|
1028
|
-
* @param {Vector2} [position=new Vector2(
|
|
1050
|
+
* @param {Vector2} [position=new Vector2()] - World space position of the object
|
|
1029
1051
|
* @param {Vector2} [size=objectDefaultSize] - World space size of the object
|
|
1030
1052
|
* @param {Number} [tileIndex=-1] - Tile to use to render object, untextured if -1
|
|
1031
1053
|
* @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
|
|
@@ -1070,12 +1092,15 @@ class EngineObject
|
|
|
1070
1092
|
this.gravityScale = 1;
|
|
1071
1093
|
/** @property {Number} [renderOrder=0] - Objects are sorted by render order */
|
|
1072
1094
|
this.renderOrder = renderOrder;
|
|
1095
|
+
/** @property {Vector2} [velocity=new Vector2()] - Velocity of the object */
|
|
1096
|
+
this.velocity = new Vector2();
|
|
1097
|
+
/** @property {Number} [angleVelocity=0] - Angular velocity of the object */
|
|
1098
|
+
this.angleVelocity = 0;
|
|
1073
1099
|
|
|
1074
1100
|
// init other internal object stuff
|
|
1075
1101
|
this.spawnTime = time;
|
|
1076
|
-
this.velocity = vec2(this.collideSolidObjects = this.angleVelocity = 0);
|
|
1077
|
-
this.collideTiles = 1;
|
|
1078
1102
|
this.children = [];
|
|
1103
|
+
this.collideTiles = 1;
|
|
1079
1104
|
|
|
1080
1105
|
// add to list of objects
|
|
1081
1106
|
engineObjects.push(this);
|
|
@@ -1094,8 +1119,8 @@ class EngineObject
|
|
|
1094
1119
|
}
|
|
1095
1120
|
|
|
1096
1121
|
// limit max speed to prevent missing collisions
|
|
1097
|
-
this.velocity.x = clamp(this.velocity.x, objectMaxSpeed,
|
|
1098
|
-
this.velocity.y = clamp(this.velocity.y, objectMaxSpeed,
|
|
1122
|
+
this.velocity.x = clamp(this.velocity.x, -objectMaxSpeed, objectMaxSpeed);
|
|
1123
|
+
this.velocity.y = clamp(this.velocity.y, -objectMaxSpeed, objectMaxSpeed);
|
|
1099
1124
|
|
|
1100
1125
|
// apply physics
|
|
1101
1126
|
const oldPos = this.pos.copy();
|
|
@@ -1143,7 +1168,7 @@ class EngineObject
|
|
|
1143
1168
|
// if already was touching, try to push away
|
|
1144
1169
|
const deltaPos = oldPos.subtract(o.pos);
|
|
1145
1170
|
const length = deltaPos.length();
|
|
1146
|
-
const pushAwayAccel = .001; // push away if
|
|
1171
|
+
const pushAwayAccel = .001; // push away if already overlapping
|
|
1147
1172
|
const velocity = length < .01 ? randVector(pushAwayAccel) : deltaPos.scale(pushAwayAccel/length);
|
|
1148
1173
|
this.velocity = this.velocity.add(velocity);
|
|
1149
1174
|
if (o.mass) // push away if not fixed
|
|
@@ -1185,8 +1210,8 @@ class EngineObject
|
|
|
1185
1210
|
|
|
1186
1211
|
// lerp betwen elastic or inelastic based on elasticity
|
|
1187
1212
|
const elasticity = max(this.elasticity, o.elasticity);
|
|
1188
|
-
this.velocity.y = lerp(elasticity,
|
|
1189
|
-
o.velocity.y = lerp(elasticity,
|
|
1213
|
+
this.velocity.y = lerp(elasticity, inelastic, elastic0);
|
|
1214
|
+
o.velocity.y = lerp(elasticity, inelastic, elastic1);
|
|
1190
1215
|
}
|
|
1191
1216
|
}
|
|
1192
1217
|
if (!smallStepUp && (isBlockedX || !isBlockedY)) // resolve x collision
|
|
@@ -1206,8 +1231,8 @@ class EngineObject
|
|
|
1206
1231
|
|
|
1207
1232
|
// lerp betwen elastic or inelastic based on elasticity
|
|
1208
1233
|
const elasticity = max(this.elasticity, o.elasticity);
|
|
1209
|
-
this.velocity.x = lerp(elasticity,
|
|
1210
|
-
o.velocity.x = lerp(elasticity,
|
|
1234
|
+
this.velocity.x = lerp(elasticity, inelastic, elastic0);
|
|
1235
|
+
o.velocity.x = lerp(elasticity, inelastic, elastic1);
|
|
1211
1236
|
}
|
|
1212
1237
|
else // bounce if other object is fixed
|
|
1213
1238
|
this.velocity.x *= -this.elasticity;
|
|
@@ -1237,7 +1262,7 @@ class EngineObject
|
|
|
1237
1262
|
|
|
1238
1263
|
// adjust next velocity to settle on ground
|
|
1239
1264
|
const o = (oldPos.y - this.size.y/2|0) - (oldPos.y - this.size.y/2);
|
|
1240
|
-
if (o < 0 && o >
|
|
1265
|
+
if (o < 0 && o > this.damping * this.velocity.y + gravity * this.gravityScale)
|
|
1241
1266
|
this.velocity.y = this.damping ? (o - gravity * this.gravityScale) / this.damping : 0;
|
|
1242
1267
|
|
|
1243
1268
|
// move to previous position
|
|
@@ -1338,18 +1363,6 @@ class EngineObject
|
|
|
1338
1363
|
{
|
|
1339
1364
|
ASSERT(collideSolidObjects || !isSolid); // solid objects must be set to collide
|
|
1340
1365
|
|
|
1341
|
-
// track collidable objects in separate list
|
|
1342
|
-
if (collideSolidObjects && !this.collideSolidObjects)
|
|
1343
|
-
{
|
|
1344
|
-
ASSERT(!engineObjectsCollide.includes(this));
|
|
1345
|
-
engineObjectsCollide.push(this);
|
|
1346
|
-
}
|
|
1347
|
-
else if (!collideSolidObjects && this.collideSolidObjects)
|
|
1348
|
-
{
|
|
1349
|
-
ASSERT(engineObjectsCollide.includes(this))
|
|
1350
|
-
engineObjectsCollide.splice(engineObjectsCollide.indexOf(this), 1);
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
1366
|
this.collideSolidObjects = collideSolidObjects;
|
|
1354
1367
|
this.isSolid = isSolid;
|
|
1355
1368
|
this.collideTiles = collideTiles;
|
|
@@ -1449,17 +1462,13 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, col
|
|
|
1449
1462
|
{
|
|
1450
1463
|
// calculate uvs and render
|
|
1451
1464
|
const cols = tileImageSize.x / tileSize.x |0;
|
|
1452
|
-
const uvSizeX = tileSize.x
|
|
1453
|
-
const uvSizeY = tileSize.y
|
|
1465
|
+
const uvSizeX = tileSize.x / tileImageSize.x;
|
|
1466
|
+
const uvSizeY = tileSize.y / tileImageSize.y;
|
|
1454
1467
|
const uvX = (tileIndex%cols)*uvSizeX, uvY = (tileIndex/cols|0)*uvSizeY;
|
|
1455
|
-
|
|
1456
|
-
// shrink uvs to prevent bleeding
|
|
1457
|
-
const shrinkTilesX = tileBleedShrinkFix * tileImageSizeInverse.x;
|
|
1458
|
-
const shrinkTilesY = tileBleedShrinkFix * tileImageSizeInverse.y;
|
|
1459
1468
|
|
|
1460
1469
|
glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
|
|
1461
|
-
uvX +
|
|
1462
|
-
uvX -
|
|
1470
|
+
uvX + tileImageFixBleed.x, uvY + tileImageFixBleed.y,
|
|
1471
|
+
uvX - tileImageFixBleed.x + uvSizeX, uvY - tileImageFixBleed.y + uvSizeY,
|
|
1463
1472
|
color.rgbaInt(), additiveColor.rgbaInt());
|
|
1464
1473
|
}
|
|
1465
1474
|
}
|
|
@@ -1478,10 +1487,10 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, col
|
|
|
1478
1487
|
{
|
|
1479
1488
|
// calculate uvs and render
|
|
1480
1489
|
const cols = tileImageSize.x / tileSize.x |0;
|
|
1481
|
-
const sX = (tileIndex%cols)*tileSize.x +
|
|
1482
|
-
const sY = (tileIndex/cols|0)*tileSize.y +
|
|
1483
|
-
const sWidth = tileSize.x - 2*
|
|
1484
|
-
const sHeight = tileSize.y - 2*
|
|
1490
|
+
const sX = (tileIndex%cols)*tileSize.x + tileFixBleedScale;
|
|
1491
|
+
const sY = (tileIndex/cols|0)*tileSize.y + tileFixBleedScale;
|
|
1492
|
+
const sWidth = tileSize.x - 2*tileFixBleedScale;
|
|
1493
|
+
const sHeight = tileSize.y - 2*tileFixBleedScale;
|
|
1485
1494
|
context.globalAlpha = color.a; // only alpha is supported
|
|
1486
1495
|
context.drawImage(tileImage, sX, sY, sWidth, sHeight, -.5, -.5, 1, 1);
|
|
1487
1496
|
}
|
|
@@ -1564,6 +1573,18 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainCont
|
|
|
1564
1573
|
context.restore();
|
|
1565
1574
|
}
|
|
1566
1575
|
|
|
1576
|
+
/** Enable normal or additive blend mode
|
|
1577
|
+
* @param {Boolean} [additive=0]
|
|
1578
|
+
* @param {Boolean} [useWebGL=glEnable]
|
|
1579
|
+
* @memberof Draw */
|
|
1580
|
+
function setBlendMode(additive, useWebGL=glEnable)
|
|
1581
|
+
{
|
|
1582
|
+
if (glEnable && useWebGL)
|
|
1583
|
+
glSetBlendMode(additive);
|
|
1584
|
+
else
|
|
1585
|
+
mainContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1567
1588
|
/** Draw text on overlay canvas in world space
|
|
1568
1589
|
* @param {String} text
|
|
1569
1590
|
* @param {Vector2} pos
|
|
@@ -1589,16 +1610,96 @@ function drawText(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new
|
|
|
1589
1610
|
overlayContext.fillText(text, pos.x, pos.y);
|
|
1590
1611
|
}
|
|
1591
1612
|
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
*
|
|
1596
|
-
|
|
1613
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1614
|
+
|
|
1615
|
+
/**
|
|
1616
|
+
* Font Image Object - Draw text on a 2D canvas by using characters in an image
|
|
1617
|
+
* <br> - 96 characters (from space to tilde) are stored in an image
|
|
1618
|
+
* <br> - Uses a default 8x8 font if none is supplied
|
|
1619
|
+
* <br> - You can also use fonts from the main tile sheet
|
|
1620
|
+
* @example
|
|
1621
|
+
* // use built in font
|
|
1622
|
+
* const font = new ImageFont;
|
|
1623
|
+
*
|
|
1624
|
+
* // draw text
|
|
1625
|
+
* font.drawTextScreen("LittleJS\nHello World!", vec2(200, 50));
|
|
1626
|
+
*/
|
|
1627
|
+
|
|
1628
|
+
let engineFontImage;
|
|
1629
|
+
|
|
1630
|
+
class FontImage
|
|
1597
1631
|
{
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1632
|
+
/** Create an image font
|
|
1633
|
+
* @param {Image} [image] - The image the font is stored in, if undefined the default font is used
|
|
1634
|
+
* @param {Vector2} [tileSize=vec2(8)] - The size of the font source tiles
|
|
1635
|
+
* @param {Vector2} [paddingSize=vec2(0,1)] - How much extra space to add between characters
|
|
1636
|
+
* @param {Number} [startTileIndex=0] - Tile index in image where font starts
|
|
1637
|
+
* @param {CanvasRenderingContext2D} [context=overlayContext] - context to draw to
|
|
1638
|
+
*/
|
|
1639
|
+
constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), startTileIndex=0, context=overlayContext)
|
|
1640
|
+
{
|
|
1641
|
+
if (!image && !engineFontImage)
|
|
1642
|
+
{
|
|
1643
|
+
// load default font image
|
|
1644
|
+
engineFontImage = new Image();
|
|
1645
|
+
engineFontImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
this.image = image || engineFontImage;
|
|
1649
|
+
this.tileSize = tileSize;
|
|
1650
|
+
this.paddingSize = paddingSize;
|
|
1651
|
+
this.startTileIndex = startTileIndex;
|
|
1652
|
+
this.context = context;
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
/** Draw text in screen space using the image font
|
|
1656
|
+
* @param {String} text
|
|
1657
|
+
* @param {Vector2} pos
|
|
1658
|
+
* @param {Number} [scale=4]
|
|
1659
|
+
* @param {Boolean} [center]
|
|
1660
|
+
*/
|
|
1661
|
+
drawTextScreen(text, pos, scale=4, center)
|
|
1662
|
+
{
|
|
1663
|
+
const context = this.context;
|
|
1664
|
+
context.save();
|
|
1665
|
+
context.imageSmoothingEnabled = !cavasPixelated;
|
|
1666
|
+
|
|
1667
|
+
const size = this.tileSize;
|
|
1668
|
+
const drawSize = size.add(this.paddingSize).scale(scale);
|
|
1669
|
+
const cols = this.image.width / this.tileSize.x |0;
|
|
1670
|
+
text.split('\n').forEach((line, i)=>
|
|
1671
|
+
{
|
|
1672
|
+
const centerOffset = center ? line.length * size.x * scale / 2 |0 : 0;
|
|
1673
|
+
for(let j=line.length; j--;)
|
|
1674
|
+
{
|
|
1675
|
+
// draw each character
|
|
1676
|
+
let charCode = line[j].charCodeAt();
|
|
1677
|
+
if (charCode < 32 || charCode > 127)
|
|
1678
|
+
charCode = 127; // unknown character
|
|
1679
|
+
|
|
1680
|
+
// get the character source location and draw it
|
|
1681
|
+
const tile = this.startTileIndex + charCode - 32;
|
|
1682
|
+
const x = tile % cols;
|
|
1683
|
+
const y = tile / cols |0;
|
|
1684
|
+
const drawPos = pos.add(vec2(j,i).multiply(drawSize));
|
|
1685
|
+
context.drawImage(this.image, x * size.x, y * size.y, size.x, size.y,
|
|
1686
|
+
drawPos.x - centerOffset, drawPos.y, size.x * scale, size.y * scale);
|
|
1687
|
+
}
|
|
1688
|
+
});
|
|
1689
|
+
|
|
1690
|
+
context.restore();
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
/** Draw text in world space using the image font
|
|
1694
|
+
* @param {String} text
|
|
1695
|
+
* @param {Vector2} pos
|
|
1696
|
+
* @param {Number} [scale=.25]
|
|
1697
|
+
* @param {Boolean} [center]
|
|
1698
|
+
*/
|
|
1699
|
+
drawText(text, pos, scale=1, center)
|
|
1700
|
+
{
|
|
1701
|
+
this.drawTextScreen(text, worldToScreen(pos).floor(), scale*cameraScale|0, center);
|
|
1702
|
+
}
|
|
1602
1703
|
}
|
|
1603
1704
|
|
|
1604
1705
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -1662,7 +1763,7 @@ const keyWasReleased = (key, device=0)=> inputData[device] && inputData[device][
|
|
|
1662
1763
|
|
|
1663
1764
|
/** Clears all input
|
|
1664
1765
|
* @memberof Input */
|
|
1665
|
-
const clearInput = ()=> inputData
|
|
1766
|
+
const clearInput = ()=> inputData = [[]];
|
|
1666
1767
|
|
|
1667
1768
|
/** Returns true if mouse button is down
|
|
1668
1769
|
* @param {Number} button
|
|
@@ -1731,7 +1832,9 @@ const gamepadStick = (stick, gamepad=0)=> stickData[gamepad] ? stickData[gamepa
|
|
|
1731
1832
|
///////////////////////////////////////////////////////////////////////////////
|
|
1732
1833
|
// Input update called by engine
|
|
1733
1834
|
|
|
1734
|
-
|
|
1835
|
+
// store input as a bit field for each key: 1 = isDown, 2 = wasPressed, 4 = wasReleased
|
|
1836
|
+
// mouse and keyboard are stored together in device 0, gamepads are in devices > 0
|
|
1837
|
+
let inputData = [[]];
|
|
1735
1838
|
|
|
1736
1839
|
function inputUpdate()
|
|
1737
1840
|
{
|
|
@@ -1757,36 +1860,38 @@ function inputUpdatePost()
|
|
|
1757
1860
|
///////////////////////////////////////////////////////////////////////////////
|
|
1758
1861
|
// Keyboard event handlers
|
|
1759
1862
|
|
|
1760
|
-
onkeydown = e=>
|
|
1863
|
+
onkeydown = (e)=>
|
|
1761
1864
|
{
|
|
1762
1865
|
if (debug && e.target != document.body) return;
|
|
1763
1866
|
e.repeat || (inputData[isUsingGamepad = 0][remapKeyCode(e.keyCode)] = 3);
|
|
1764
1867
|
debug || e.preventDefault();
|
|
1765
1868
|
}
|
|
1766
|
-
onkeyup = e=>
|
|
1869
|
+
onkeyup = (e)=>
|
|
1767
1870
|
{
|
|
1768
1871
|
if (debug && e.target != document.body) return;
|
|
1769
1872
|
inputData[0][remapKeyCode(e.keyCode)] = 4;
|
|
1770
1873
|
}
|
|
1771
|
-
const remapKeyCode = c=> inputWASDEmulateDirection ? c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
|
|
1874
|
+
const remapKeyCode = (c)=> inputWASDEmulateDirection ? c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
|
|
1772
1875
|
|
|
1773
1876
|
///////////////////////////////////////////////////////////////////////////////
|
|
1774
1877
|
// Mouse event handlers
|
|
1775
1878
|
|
|
1776
|
-
onmousedown = e=> {inputData[isUsingGamepad = 0][e.button] = 3; onmousemove(e); e.button && e.preventDefault();}
|
|
1777
|
-
onmouseup = e=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
|
|
1778
|
-
onmousemove = e=>
|
|
1879
|
+
onmousedown = (e)=> {inputData[isUsingGamepad = 0][e.button] = 3; onmousemove(e); e.button && e.preventDefault();}
|
|
1880
|
+
onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
|
|
1881
|
+
onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
|
|
1882
|
+
onwheel = (e)=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
|
|
1883
|
+
oncontextmenu = (e)=> !1; // prevent right click menu
|
|
1884
|
+
|
|
1885
|
+
// convert a mouse or touch event position to screen space
|
|
1886
|
+
const mouseToScreen = (mousePos)=>
|
|
1779
1887
|
{
|
|
1780
1888
|
if (!mainCanvas)
|
|
1781
|
-
return; // fix bug that can occur if user clicks before page loads
|
|
1889
|
+
return vec2(); // fix bug that can occur if user clicks before page loads
|
|
1782
1890
|
|
|
1783
|
-
// convert mouse pos to canvas space
|
|
1784
1891
|
const rect = mainCanvas.getBoundingClientRect();
|
|
1785
|
-
|
|
1786
|
-
vec2(percent(
|
|
1892
|
+
return mainCanvasSize.multiply(
|
|
1893
|
+
vec2(percent(mousePos.x, rect.left, rect.right), percent(mousePos.y, rect.top, rect.bottom)));
|
|
1787
1894
|
}
|
|
1788
|
-
onwheel = e=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
|
|
1789
|
-
oncontextmenu = e=> !1; // prevent right click menu
|
|
1790
1895
|
|
|
1791
1896
|
///////////////////////////////////////////////////////////////////////////////
|
|
1792
1897
|
// Gamepad input
|
|
@@ -1794,6 +1899,21 @@ oncontextmenu = e=> !1; // prevent right click menu
|
|
|
1794
1899
|
const stickData = [];
|
|
1795
1900
|
function gamepadsUpdate()
|
|
1796
1901
|
{
|
|
1902
|
+
if (touchGamepadEnable && touchGamepadTimer.isSet())
|
|
1903
|
+
{
|
|
1904
|
+
// read virtual analog stick
|
|
1905
|
+
const sticks = stickData[0] || (stickData[0] = []);
|
|
1906
|
+
sticks[0] = vec2(touchGamepadStick.x, -touchGamepadStick.y); // flip vertical
|
|
1907
|
+
|
|
1908
|
+
// read virtual gamepad buttons
|
|
1909
|
+
const data = inputData[1] || (inputData[1] = []);
|
|
1910
|
+
for (let i=10; i--;)
|
|
1911
|
+
{
|
|
1912
|
+
const j = i == 3 ? 2 : i == 2 ? 3 : i; // fix button locations
|
|
1913
|
+
data[j] = touchGamepadButtons[i] ? 1 + 2*!gamepadIsDown(j,0) : 4*gamepadIsDown(j,0);
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1797
1917
|
if (!gamepadsEnable || !navigator.getGamepads || !document.hasFocus() && !debug)
|
|
1798
1918
|
return;
|
|
1799
1919
|
|
|
@@ -1811,8 +1931,8 @@ function gamepadsUpdate()
|
|
|
1811
1931
|
// read clamp dead zone of analog sticks
|
|
1812
1932
|
const deadZone = .3, deadZoneMax = .8;
|
|
1813
1933
|
const applyDeadZone = (v)=>
|
|
1814
|
-
v > deadZone ? percent( v,
|
|
1815
|
-
v < -deadZone ? -percent(-v,
|
|
1934
|
+
v > deadZone ? percent( v, deadZone, deadZoneMax) :
|
|
1935
|
+
v < -deadZone ? -percent(-v, deadZone, deadZoneMax) : 0;
|
|
1816
1936
|
|
|
1817
1937
|
// read analog sticks
|
|
1818
1938
|
for (let j = 0; j < gamepad.axes.length-1; j+=2)
|
|
@@ -1824,6 +1944,7 @@ function gamepadsUpdate()
|
|
|
1824
1944
|
const button = gamepad.buttons[j];
|
|
1825
1945
|
data[j] = button.pressed ? 1 + 2*!gamepadIsDown(j,i) : 4*gamepadIsDown(j,i);
|
|
1826
1946
|
isUsingGamepad |= !i && button.pressed;
|
|
1947
|
+
touchGamepadEnable && touchGamepadTimer.unset(); // disable touch gamepad if using real gamepad
|
|
1827
1948
|
}
|
|
1828
1949
|
|
|
1829
1950
|
if (gamepadDirectionEmulateStick)
|
|
@@ -1837,6 +1958,17 @@ function gamepadsUpdate()
|
|
|
1837
1958
|
}
|
|
1838
1959
|
}
|
|
1839
1960
|
|
|
1961
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1962
|
+
|
|
1963
|
+
/** Pulse the vibration hardware if it exists
|
|
1964
|
+
* @param {Number} [pattern=100] - a single value in miliseconds or vibration interval array
|
|
1965
|
+
* @memberof Input */
|
|
1966
|
+
const vibrate = (pattern)=> vibrateEnable && Navigator.vibrate && Navigator.vibrate(pattern);
|
|
1967
|
+
|
|
1968
|
+
/** Cancel any ongoing vibration
|
|
1969
|
+
* @memberof Input */
|
|
1970
|
+
const vibrateStop = ()=> vibrate(0);
|
|
1971
|
+
|
|
1840
1972
|
///////////////////////////////////////////////////////////////////////////////
|
|
1841
1973
|
// Touch input
|
|
1842
1974
|
|
|
@@ -1844,7 +1976,9 @@ function gamepadsUpdate()
|
|
|
1844
1976
|
* @const {boolean}
|
|
1845
1977
|
* @memberof Input */
|
|
1846
1978
|
const isTouchDevice = window.ontouchstart !== undefined;
|
|
1847
|
-
|
|
1979
|
+
|
|
1980
|
+
// try to enable touch mouse
|
|
1981
|
+
if (isTouchDevice)
|
|
1848
1982
|
{
|
|
1849
1983
|
// handle all touch events the same way
|
|
1850
1984
|
let wasTouching, hadTouchInput;
|
|
@@ -1875,10 +2009,12 @@ if (touchMouseEnable && isTouchDevice)
|
|
|
1875
2009
|
}
|
|
1876
2010
|
|
|
1877
2011
|
///////////////////////////////////////////////////////////////////////////////
|
|
1878
|
-
//
|
|
2012
|
+
// touch gamepad, virtual on screen gamepad emulator for touch devices
|
|
2013
|
+
|
|
2014
|
+
// touch input internal variables
|
|
2015
|
+
let touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadStick = vec2();
|
|
1879
2016
|
|
|
1880
2017
|
// create the touch gamepad, called automatically by the engine
|
|
1881
|
-
const touchGamepadTimer = new Timer;
|
|
1882
2018
|
function touchGamepadCreate()
|
|
1883
2019
|
{
|
|
1884
2020
|
if (!touchGamepadEnable || !isTouchDevice)
|
|
@@ -1888,6 +2024,10 @@ function touchGamepadCreate()
|
|
|
1888
2024
|
{
|
|
1889
2025
|
if (!touchGamepadEnable)
|
|
1890
2026
|
return;
|
|
2027
|
+
|
|
2028
|
+
// clear touch gamepad input
|
|
2029
|
+
touchGamepadStick = vec2();
|
|
2030
|
+
touchGamepadButtons = [];
|
|
1891
2031
|
|
|
1892
2032
|
const touching = e.touches.length;
|
|
1893
2033
|
if (touching)
|
|
@@ -1897,6 +2037,13 @@ function touchGamepadCreate()
|
|
|
1897
2037
|
// set that gamepad is active
|
|
1898
2038
|
isUsingGamepad = 1;
|
|
1899
2039
|
touchGamepadTimer.set();
|
|
2040
|
+
|
|
2041
|
+
if (paused)
|
|
2042
|
+
{
|
|
2043
|
+
// touch anywhere to press start when paused
|
|
2044
|
+
touchGamepadButtons[9] = 1;
|
|
2045
|
+
return;
|
|
2046
|
+
}
|
|
1900
2047
|
}
|
|
1901
2048
|
|
|
1902
2049
|
// get center of left and right sides
|
|
@@ -1904,38 +2051,34 @@ function touchGamepadCreate()
|
|
|
1904
2051
|
const buttonCenter = mainCanvasSize.subtract(vec2(touchGamepadSize, touchGamepadSize));
|
|
1905
2052
|
const startCenter = mainCanvasSize.scale(.5);
|
|
1906
2053
|
|
|
1907
|
-
// update virtual gamepad
|
|
1908
|
-
const data = inputData[1] || (inputData[1] = []);
|
|
1909
|
-
const sticks = stickData[0] || (stickData[0] = []);
|
|
1910
|
-
sticks[0] = vec2();
|
|
1911
|
-
|
|
1912
2054
|
// check each touch point
|
|
1913
|
-
const buttons = [];
|
|
1914
2055
|
for (const touch of e.touches)
|
|
1915
2056
|
{
|
|
1916
|
-
const touchPos = vec2(touch.clientX, touch.clientY);
|
|
2057
|
+
const touchPos = mouseToScreen(vec2(touch.clientX, touch.clientY));
|
|
1917
2058
|
if (touchPos.distance(stickCenter) < touchGamepadSize)
|
|
1918
2059
|
{
|
|
1919
2060
|
// virtual analog stick
|
|
1920
|
-
|
|
1921
|
-
|
|
2061
|
+
if (touchGamepadAnalog)
|
|
2062
|
+
touchGamepadStick = touchPos.subtract(stickCenter).scale(2/touchGamepadSize).clampLength();
|
|
2063
|
+
else
|
|
2064
|
+
{
|
|
2065
|
+
// 8 way dpad
|
|
2066
|
+
const angle = touchPos.subtract(stickCenter).angle();
|
|
2067
|
+
touchGamepadStick.setAngle((angle * 4 / PI + 8.5 | 0) * PI / 4);
|
|
2068
|
+
}
|
|
1922
2069
|
}
|
|
1923
2070
|
else if (touchPos.distance(buttonCenter) < touchGamepadSize)
|
|
1924
2071
|
{
|
|
1925
2072
|
// virtual face buttons
|
|
1926
2073
|
const button = touchPos.subtract(buttonCenter).direction();
|
|
1927
|
-
|
|
2074
|
+
touchGamepadButtons[button] = 1;
|
|
1928
2075
|
}
|
|
1929
2076
|
else if (touchPos.distance(startCenter) < touchGamepadSize)
|
|
1930
2077
|
{
|
|
1931
|
-
// virtual start button
|
|
1932
|
-
|
|
2078
|
+
// virtual start button in center
|
|
2079
|
+
touchGamepadButtons[9] = 1;
|
|
1933
2080
|
}
|
|
1934
2081
|
}
|
|
1935
|
-
|
|
1936
|
-
// read virtual buttons
|
|
1937
|
-
for (let j=10; j--;)
|
|
1938
|
-
data[j] = buttons[j] ? 1 + 2*!gamepadIsDown(j,0) : 4*gamepadIsDown(j,0);
|
|
1939
2082
|
}
|
|
1940
2083
|
}
|
|
1941
2084
|
|
|
@@ -1945,37 +2088,48 @@ function touchGamepadRender()
|
|
|
1945
2088
|
if (!touchGamepadEnable || !touchGamepadTimer.isSet())
|
|
1946
2089
|
return;
|
|
1947
2090
|
|
|
1948
|
-
// fade off when not touching
|
|
1949
|
-
const alpha = percent(touchGamepadTimer.get(), 4,
|
|
1950
|
-
if (!alpha)
|
|
2091
|
+
// fade off when not touching or paused
|
|
2092
|
+
const alpha = percent(touchGamepadTimer.get(), 4, 3);
|
|
2093
|
+
if (!alpha || paused)
|
|
1951
2094
|
return;
|
|
1952
2095
|
|
|
1953
2096
|
// setup the canvas
|
|
1954
2097
|
overlayContext.save();
|
|
1955
2098
|
overlayContext.globalAlpha = alpha*touchGamepadAlpha;
|
|
1956
|
-
overlayContext.fillStyle = '#fff6';
|
|
1957
2099
|
overlayContext.strokeStyle = '#fff';
|
|
1958
2100
|
overlayContext.lineWidth = 3;
|
|
1959
2101
|
|
|
1960
|
-
// draw start button
|
|
1961
|
-
//overlayContext.beginPath();
|
|
1962
|
-
//overlayContext.arc(mainCanvasSize.x/2, mainCanvasSize.y/2, touchGamepadSize*.9, 0,9);
|
|
1963
|
-
//overlayContext.fill();
|
|
1964
|
-
//overlayContext.stroke();
|
|
1965
|
-
|
|
1966
2102
|
// draw left analog stick
|
|
2103
|
+
overlayContext.fillStyle = touchGamepadStick.lengthSquared() > 0 ? '#fff' : '#000';
|
|
1967
2104
|
overlayContext.beginPath();
|
|
1968
|
-
overlayContext.arc(touchGamepadSize, mainCanvasSize.y-touchGamepadSize, touchGamepadSize*.9, 0,9);
|
|
1969
|
-
overlayContext.fill();
|
|
1970
|
-
overlayContext.stroke();
|
|
1971
2105
|
|
|
2106
|
+
const leftCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
2107
|
+
if (touchGamepadAnalog)
|
|
2108
|
+
{
|
|
2109
|
+
overlayContext.arc(leftCenter.x, leftCenter.y, touchGamepadSize/2, 0, 9);
|
|
2110
|
+
overlayContext.fill();
|
|
2111
|
+
overlayContext.stroke();
|
|
2112
|
+
}
|
|
2113
|
+
else // draw cross shaped gamepad
|
|
2114
|
+
{
|
|
2115
|
+
for(let i=10; i--;)
|
|
2116
|
+
{
|
|
2117
|
+
const angle = i*PI/4;
|
|
2118
|
+
overlayContext.arc(leftCenter.x, leftCenter.y,touchGamepadSize*.6, angle + PI/8, angle + PI/8);
|
|
2119
|
+
i%2 && overlayContext.arc(leftCenter.x, leftCenter.y, touchGamepadSize*.33, angle, angle);
|
|
2120
|
+
i==1 && overlayContext.fill();
|
|
2121
|
+
}
|
|
2122
|
+
overlayContext.stroke();
|
|
2123
|
+
}
|
|
2124
|
+
|
|
1972
2125
|
// draw right face buttons
|
|
1973
2126
|
const rightCenter = vec2(mainCanvasSize.x-touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
1974
2127
|
for (let i=4; i--;)
|
|
1975
2128
|
{
|
|
1976
|
-
const pos = rightCenter.add((new Vector2).setAngle(i*PI/2, touchGamepadSize
|
|
2129
|
+
const pos = rightCenter.add((new Vector2).setAngle(i*PI/2, touchGamepadSize/2));
|
|
2130
|
+
overlayContext.fillStyle = touchGamepadButtons[i] ? '#fff' : '#000';
|
|
1977
2131
|
overlayContext.beginPath();
|
|
1978
|
-
overlayContext.arc(pos.x, pos.y, touchGamepadSize
|
|
2132
|
+
overlayContext.arc(pos.x, pos.y, touchGamepadSize/4, 0,9);
|
|
1979
2133
|
overlayContext.fill();
|
|
1980
2134
|
overlayContext.stroke();
|
|
1981
2135
|
}
|
|
@@ -2054,7 +2208,7 @@ class Sound
|
|
|
2054
2208
|
return; // out of range
|
|
2055
2209
|
|
|
2056
2210
|
// attenuate volume by distance
|
|
2057
|
-
volume *= percent(lengthSquared**.5, range*this.taper
|
|
2211
|
+
volume *= percent(lengthSquared**.5, range, range*this.taper);
|
|
2058
2212
|
}
|
|
2059
2213
|
|
|
2060
2214
|
// get pan from screen space coords
|
|
@@ -2180,7 +2334,7 @@ function speak(text, language='', volume=1, rate=1, pitch=1)
|
|
|
2180
2334
|
|
|
2181
2335
|
/** Stop all queued speech
|
|
2182
2336
|
* @memberof Audio */
|
|
2183
|
-
const
|
|
2337
|
+
const speakStop = ()=> speechSynthesis && speechSynthesis.cancel();
|
|
2184
2338
|
|
|
2185
2339
|
/** Get frequency of a note on a musical scale
|
|
2186
2340
|
* @param {Number} semitoneOffset - How many semitones away from the root note
|
|
@@ -2211,6 +2365,13 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
|
|
|
2211
2365
|
if (!audioContext)
|
|
2212
2366
|
audioContext = new (window.AudioContext||webkitAudioContext);
|
|
2213
2367
|
|
|
2368
|
+
// fix stalled audio
|
|
2369
|
+
audioContext.resume();
|
|
2370
|
+
|
|
2371
|
+
// prevent sounds from building up if they can't be played
|
|
2372
|
+
if (audioContext.state != 'running')
|
|
2373
|
+
return;
|
|
2374
|
+
|
|
2214
2375
|
// create buffer and source
|
|
2215
2376
|
const buffer = audioContext.createBuffer(sampleChannels.length, sampleChannels[0].length, zzfxR),
|
|
2216
2377
|
source = audioContext.createBufferSource();
|
|
@@ -2221,11 +2382,18 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
|
|
|
2221
2382
|
source.playbackRate.value = rate;
|
|
2222
2383
|
source.loop = loop;
|
|
2223
2384
|
|
|
2224
|
-
// create
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2385
|
+
// create and connect gain node (createGain is more widley spported then GainNode construtor)
|
|
2386
|
+
const gainNode = audioContext.createGain();
|
|
2387
|
+
gainNode.gain.value = soundVolume*volume;
|
|
2388
|
+
gainNode.connect(audioContext.destination);
|
|
2389
|
+
|
|
2390
|
+
// connect source to gain
|
|
2391
|
+
(
|
|
2392
|
+
window.StereoPannerNode ? // create pan node if possible
|
|
2393
|
+
source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)}))
|
|
2394
|
+
: source
|
|
2395
|
+
)
|
|
2396
|
+
.connect(gainNode);
|
|
2229
2397
|
|
|
2230
2398
|
// play and return sound
|
|
2231
2399
|
source.start();
|
|
@@ -2583,8 +2751,9 @@ class TileLayerData
|
|
|
2583
2751
|
/**
|
|
2584
2752
|
* Tile layer object - cached rendering system for tile layers
|
|
2585
2753
|
* <br> - Each Tile layer is rendered to an off screen canvas
|
|
2586
|
-
* <br> -
|
|
2587
|
-
* <br> -
|
|
2754
|
+
* <br> - To allow dynamic modifications, layers are rendered using canvas 2d
|
|
2755
|
+
* <br> - Some devices like mobile phones are limited to 4k texture resolution
|
|
2756
|
+
* <br> - So with 16x16 tiles this limits layers to 256x256 on mobile devices
|
|
2588
2757
|
* @extends EngineObject
|
|
2589
2758
|
* @example
|
|
2590
2759
|
* // create tile collision and visible tile layer
|
|
@@ -2594,11 +2763,11 @@ class TileLayerData
|
|
|
2594
2763
|
class TileLayer extends EngineObject
|
|
2595
2764
|
{
|
|
2596
2765
|
/** Create a tile layer object
|
|
2597
|
-
* @param {Vector2} [position=new Vector2(
|
|
2598
|
-
* @param {Vector2} [size=objectDefaultSize]
|
|
2599
|
-
* @param {Vector2} [tileSize=tileSizeDefault]
|
|
2600
|
-
* @param {Vector2} [scale=new Vector2(1,1)]
|
|
2601
|
-
* @param {Number} [renderOrder=0]
|
|
2766
|
+
* @param {Vector2} [position=new Vector2()] - World space position
|
|
2767
|
+
* @param {Vector2} [size=objectDefaultSize] - World space size
|
|
2768
|
+
* @param {Vector2} [tileSize=tileSizeDefault] - Size of tiles in source pixels
|
|
2769
|
+
* @param {Vector2} [scale=new Vector2(1,1)] - How much to scale this layer when rendered
|
|
2770
|
+
* @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
|
|
2602
2771
|
*/
|
|
2603
2772
|
constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1), renderOrder=0)
|
|
2604
2773
|
{
|
|
@@ -2649,7 +2818,7 @@ class TileLayer extends EngineObject
|
|
|
2649
2818
|
// flush and copy gl canvas because tile canvas does not use webgl
|
|
2650
2819
|
glEnable && !glOverlay && !this.isOverlay && glCopyToContext(mainContext);
|
|
2651
2820
|
|
|
2652
|
-
// draw the entire cached level onto the
|
|
2821
|
+
// draw the entire cached level onto the canvas
|
|
2653
2822
|
const pos = worldToScreen(this.pos.add(vec2(0,this.size.y*this.scale.y)));
|
|
2654
2823
|
(this.isOverlay ? overlayContext : mainContext).drawImage
|
|
2655
2824
|
(
|
|
@@ -2658,25 +2827,25 @@ class TileLayer extends EngineObject
|
|
|
2658
2827
|
);
|
|
2659
2828
|
}
|
|
2660
2829
|
|
|
2661
|
-
/** Draw all the tile data to an offscreen canvas
|
|
2830
|
+
/** Draw all the tile data to an offscreen canvas
|
|
2831
|
+
* - This may be slow in some browsers
|
|
2832
|
+
*/
|
|
2662
2833
|
redraw()
|
|
2663
2834
|
{
|
|
2664
|
-
this.redrawStart();
|
|
2835
|
+
this.redrawStart(1);
|
|
2665
2836
|
this.drawAllTileData();
|
|
2666
2837
|
this.redrawEnd();
|
|
2667
2838
|
}
|
|
2668
2839
|
|
|
2669
2840
|
/** Call to start the redraw process
|
|
2670
|
-
* @param {Boolean} [clear=
|
|
2671
|
-
redrawStart(clear =
|
|
2841
|
+
* @param {Boolean} [clear=0] - Should it clear the canvas before drawing */
|
|
2842
|
+
redrawStart(clear = 0)
|
|
2672
2843
|
{
|
|
2673
|
-
// clear and set size
|
|
2674
|
-
const width = this.size.x * this.tileSize.x;
|
|
2675
|
-
const height = this.size.y * this.tileSize.y;
|
|
2676
2844
|
if (clear)
|
|
2677
2845
|
{
|
|
2678
|
-
|
|
2679
|
-
this.canvas.
|
|
2846
|
+
// clear and set size
|
|
2847
|
+
this.canvas.width = this.size.x * this.tileSize.x;
|
|
2848
|
+
this.canvas.height = this.size.y * this.tileSize.y;
|
|
2680
2849
|
}
|
|
2681
2850
|
|
|
2682
2851
|
// save current render settings
|
|
@@ -2766,7 +2935,7 @@ class TileLayer extends EngineObject
|
|
|
2766
2935
|
else
|
|
2767
2936
|
{
|
|
2768
2937
|
const cols = tileImage.width/tileSize.x;
|
|
2769
|
-
context.globalAlpha = color.a; //
|
|
2938
|
+
context.globalAlpha = color.a; // only alpha, no color, is supported in this mode
|
|
2770
2939
|
context.drawImage(tileImage,
|
|
2771
2940
|
(tileIndex%cols)*tileSize.x, (tileIndex/cols|0)*tileSize.x,
|
|
2772
2941
|
tileSize.x, tileSize.y, -.5, -.5, 1, 1);
|
|
@@ -2798,9 +2967,9 @@ class TileLayer extends EngineObject
|
|
|
2798
2967
|
* let pos = vec2(2,3);
|
|
2799
2968
|
* let particleEmiter = new ParticleEmitter
|
|
2800
2969
|
* (
|
|
2801
|
-
* pos, 1, 0, 500, PI, // pos, emitSize, emitTime, emitRate, emiteCone
|
|
2970
|
+
* pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emiteCone
|
|
2802
2971
|
* 0, vec2(16), // tileIndex, tileSize
|
|
2803
|
-
* new Color,
|
|
2972
|
+
* new Color(1,1,1), new Color(0,0,0), // colorStartA, colorStartB
|
|
2804
2973
|
* new Color(1,1,1,0), new Color(0,0,0,0), // colorEndA, colorEndB
|
|
2805
2974
|
* 2, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
|
|
2806
2975
|
* .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate,
|
|
@@ -2811,6 +2980,7 @@ class ParticleEmitter extends EngineObject
|
|
|
2811
2980
|
{
|
|
2812
2981
|
/** Create a particle system with the given settings
|
|
2813
2982
|
* @param {Vector2} position - World space position of the emitter
|
|
2983
|
+
* @param {Number} [angle=0] - Angle to rotate the object
|
|
2814
2984
|
* @param {Number} [emitSize=0] - World space size of the emitter (float for circle diameter, vec2 for rect)
|
|
2815
2985
|
* @param {Number} [emitTime=0] - How long to stay alive (0 is forever)
|
|
2816
2986
|
* @param {Number} [emitRate=100] - How many particles per second to spawn, does not emit if 0
|
|
@@ -2834,12 +3004,13 @@ class ParticleEmitter extends EngineObject
|
|
|
2834
3004
|
* @param {Number} [randomness=.2] - Apply extra randomness percent
|
|
2835
3005
|
* @param {Boolean} [collideTiles=0] - Do particles collide against tiles
|
|
2836
3006
|
* @param {Boolean} [additive=0] - Should particles use addtive blend
|
|
2837
|
-
* @param {Boolean} [randomColorLinear=
|
|
3007
|
+
* @param {Boolean} [randomColorLinear=1] - Should color be randomized linearly or across each component
|
|
2838
3008
|
* @param {Number} [renderOrder=0] - Render order for particles (additive is above other stuff by default)
|
|
2839
3009
|
*/
|
|
2840
3010
|
constructor
|
|
2841
3011
|
(
|
|
2842
3012
|
pos,
|
|
3013
|
+
angle,
|
|
2843
3014
|
emitSize = 0,
|
|
2844
3015
|
emitTime = 0,
|
|
2845
3016
|
emitRate = 100,
|
|
@@ -2867,7 +3038,7 @@ class ParticleEmitter extends EngineObject
|
|
|
2867
3038
|
renderOrder = additive ? 1e9 : 0
|
|
2868
3039
|
)
|
|
2869
3040
|
{
|
|
2870
|
-
super(pos, new Vector2, tileIndex, tileSize,
|
|
3041
|
+
super(pos, new Vector2, tileIndex, tileSize, angle, undefined, renderOrder);
|
|
2871
3042
|
|
|
2872
3043
|
// emitter settings
|
|
2873
3044
|
/** @property {Number} - World space size of the emitter (float for circle diameter, vec2 for rect) */
|
|
@@ -2935,9 +3106,9 @@ class ParticleEmitter extends EngineObject
|
|
|
2935
3106
|
if (!this.emitTime || this.getAliveTime() <= this.emitTime)
|
|
2936
3107
|
{
|
|
2937
3108
|
// emit particles
|
|
2938
|
-
if (this.emitRate)
|
|
3109
|
+
if (this.emitRate * particleEmitRateScale)
|
|
2939
3110
|
{
|
|
2940
|
-
const rate = 1/this.emitRate;
|
|
3111
|
+
const rate = 1/this.emitRate/particleEmitRateScale;
|
|
2941
3112
|
for (this.emitTimeBuffer += timeDelta; this.emitTimeBuffer > 0; this.emitTimeBuffer -= rate)
|
|
2942
3113
|
this.emitParticle();
|
|
2943
3114
|
}
|
|
@@ -3100,7 +3271,7 @@ function medalsInit(saveName)
|
|
|
3100
3271
|
{
|
|
3101
3272
|
// check if medals are unlocked
|
|
3102
3273
|
medalsSaveName = saveName;
|
|
3103
|
-
debugMedals || medals.forEach(medal=> medal.unlocked = localStorage[medal.storageKey()]);
|
|
3274
|
+
debugMedals || medals.forEach(medal=> medal.unlocked = 1 || localStorage[medal.storageKey()]);
|
|
3104
3275
|
}
|
|
3105
3276
|
|
|
3106
3277
|
/**
|
|
@@ -3133,13 +3304,9 @@ class Medal
|
|
|
3133
3304
|
this.name = name;
|
|
3134
3305
|
this.description = description;
|
|
3135
3306
|
this.icon = icon;
|
|
3136
|
-
|
|
3307
|
+
this.image = new Image();
|
|
3137
3308
|
if (src)
|
|
3138
|
-
{
|
|
3139
|
-
// load image
|
|
3140
|
-
this.image = new Image();
|
|
3141
3309
|
this.image.src = src;
|
|
3142
|
-
}
|
|
3143
3310
|
}
|
|
3144
3311
|
|
|
3145
3312
|
/** Unlocks a medal if not already unlocked */
|
|
@@ -3169,21 +3336,23 @@ class Medal
|
|
|
3169
3336
|
const y = -medalDisplayHeight*hidePercent;
|
|
3170
3337
|
|
|
3171
3338
|
// draw containing rect and clip to that region
|
|
3172
|
-
context.
|
|
3339
|
+
context.save();
|
|
3340
|
+
context.beginPath();
|
|
3173
3341
|
context.fillStyle = '#ddd'
|
|
3174
3342
|
context.fill(context.rect(x, y, width, medalDisplayHeight));
|
|
3175
|
-
context.strokeStyle =
|
|
3176
|
-
context.lineWidth =
|
|
3343
|
+
context.strokeStyle = '#000';
|
|
3344
|
+
context.lineWidth = 3;
|
|
3177
3345
|
context.stroke();
|
|
3178
3346
|
context.clip();
|
|
3179
3347
|
|
|
3180
3348
|
// draw the icon and text
|
|
3181
3349
|
this.renderIcon(x+15+medalDisplayIconSize/2, y+medalDisplayHeight/2);
|
|
3182
3350
|
context.textAlign = 'left';
|
|
3183
|
-
context.font = '
|
|
3184
|
-
context.fillText(this.name, x+medalDisplayIconSize+
|
|
3185
|
-
context.font = '
|
|
3186
|
-
context.
|
|
3351
|
+
context.font = '38px '+ fontDefault;
|
|
3352
|
+
context.fillText(this.name, x+medalDisplayIconSize+30, y+28);
|
|
3353
|
+
context.font = '24px '+ fontDefault;
|
|
3354
|
+
context.fillText(this.description, x+medalDisplayIconSize+30, y+60);
|
|
3355
|
+
context.restore();
|
|
3187
3356
|
}
|
|
3188
3357
|
|
|
3189
3358
|
/** Render the icon for a medal
|
|
@@ -3195,11 +3364,11 @@ class Medal
|
|
|
3195
3364
|
{
|
|
3196
3365
|
// draw the image or icon
|
|
3197
3366
|
const context = overlayContext;
|
|
3367
|
+
context.fillStyle = '#000';
|
|
3198
3368
|
context.textAlign = 'center';
|
|
3199
3369
|
context.textBaseline = 'middle';
|
|
3200
|
-
context.font = size*.
|
|
3201
|
-
|
|
3202
|
-
if (this.image)
|
|
3370
|
+
context.font = size*.7 + 'px '+ fontDefault;
|
|
3371
|
+
if (this.image.src)
|
|
3203
3372
|
context.drawImage(this.image, x-size/2, y-size/2, size, size);
|
|
3204
3373
|
else
|
|
3205
3374
|
context.fillText(this.icon, x, y); // show icon if there is no image
|
|
@@ -3275,7 +3444,6 @@ class Newgrounds
|
|
|
3275
3444
|
if (medal)
|
|
3276
3445
|
{
|
|
3277
3446
|
// copy newgrounds medal data
|
|
3278
|
-
medal.image = new Image();
|
|
3279
3447
|
medal.image.src = newgroundsMedal['icon'];
|
|
3280
3448
|
medal.name = newgroundsMedal['name'];
|
|
3281
3449
|
medal.description = newgroundsMedal['description'];
|
|
@@ -3303,9 +3471,6 @@ class Newgrounds
|
|
|
3303
3471
|
* @param {Number} value - The score value */
|
|
3304
3472
|
postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, 1); }
|
|
3305
3473
|
|
|
3306
|
-
/** Send message to log a view */
|
|
3307
|
-
logView() { return this.call('App.logView', {'host':this.host}, 1); }
|
|
3308
|
-
|
|
3309
3474
|
/** Get scores from a scoreboard
|
|
3310
3475
|
* @param {Number} id - The scoreboard id
|
|
3311
3476
|
* @param {String} [user=0] - A user's id or name
|
|
@@ -3317,6 +3482,9 @@ class Newgrounds
|
|
|
3317
3482
|
getScores(id, user=0, social=0, skip=0, limit=10)
|
|
3318
3483
|
{ return this.call('ScoreBoard.getScores', {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit}); }
|
|
3319
3484
|
|
|
3485
|
+
/** Send message to log a view */
|
|
3486
|
+
logView() { return this.call('App.logView', {'host':this.host}, 1); }
|
|
3487
|
+
|
|
3320
3488
|
/** Send a message to call a component of the Newgrounds API
|
|
3321
3489
|
* @param {String} component - Name of the component
|
|
3322
3490
|
* @param {Object} [parameters=0] - Parameters to use for call
|
|
@@ -3404,38 +3572,33 @@ function glInit()
|
|
|
3404
3572
|
|
|
3405
3573
|
// create the canvas and tile texture
|
|
3406
3574
|
glCanvas = document.createElement('canvas');
|
|
3407
|
-
glContext = glCanvas.getContext('webgl');
|
|
3408
|
-
|
|
3575
|
+
glContext = glCanvas.getContext('webgl', {antialias: false});
|
|
3576
|
+
glCanvas.style = styleCanvas;
|
|
3409
3577
|
glTileTexture = glCreateTexture(tileImage);
|
|
3410
3578
|
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
// some browsers are much faster without copying the gl buffer so we just overlay it instead
|
|
3414
|
-
document.body.appendChild(glCanvas);
|
|
3415
|
-
glCanvas.style = mainCanvas.style.cssText;
|
|
3416
|
-
}
|
|
3579
|
+
// some browsers are much faster without copying the gl buffer so we just overlay it instead
|
|
3580
|
+
glOverlay && document.body.appendChild(glCanvas);
|
|
3417
3581
|
|
|
3418
3582
|
// setup vertex and fragment shaders
|
|
3419
3583
|
glShader = glCreateProgram(
|
|
3420
|
-
'precision highp float;'+
|
|
3421
|
-
'uniform mat4 m;'+
|
|
3422
|
-
'attribute
|
|
3423
|
-
'attribute
|
|
3424
|
-
'
|
|
3425
|
-
'varying
|
|
3426
|
-
'
|
|
3427
|
-
'
|
|
3428
|
-
'
|
|
3429
|
-
'
|
|
3430
|
-
'}' // end of shader
|
|
3584
|
+
'precision highp float;'+ // use highp for better accuracy, lowp for better perf
|
|
3585
|
+
'uniform mat4 m;'+ // transform matrix
|
|
3586
|
+
'attribute vec2 p,t;'+ // position, uv
|
|
3587
|
+
'attribute vec4 c,a;'+ // color, additiveColor
|
|
3588
|
+
'varying vec2 v;'+ // return uv
|
|
3589
|
+
'varying vec4 d,e;'+ // return color, additiveColor
|
|
3590
|
+
'void main(){'+ // shader entry point
|
|
3591
|
+
'gl_Position=m*vec4(p,1,1);'+ // transform position
|
|
3592
|
+
'v=t;d=c;e=a;'+ // pass stuff to fragment shader
|
|
3593
|
+
'}' // end of shader
|
|
3431
3594
|
,
|
|
3432
|
-
'precision
|
|
3433
|
-
'varying vec2 v;'+
|
|
3434
|
-
'varying vec4 d,e;'+
|
|
3435
|
-
'uniform sampler2D
|
|
3436
|
-
'void main(){'+
|
|
3437
|
-
'gl_FragColor=texture2D(
|
|
3438
|
-
'}'
|
|
3595
|
+
'precision highp float;'+ // use highp for better accuracy, lowp for better perf
|
|
3596
|
+
'varying vec2 v;'+ // uv
|
|
3597
|
+
'varying vec4 d,e;'+ // color, additiveColor
|
|
3598
|
+
'uniform sampler2D s;'+ // texture
|
|
3599
|
+
'void main(){'+ // shader entry point
|
|
3600
|
+
'gl_FragColor=texture2D(s,v)*d+e;'+ // modulate texture by color plus additive
|
|
3601
|
+
'}' // end of shader
|
|
3439
3602
|
);
|
|
3440
3603
|
|
|
3441
3604
|
// init buffers
|
|
@@ -3445,6 +3608,7 @@ function glInit()
|
|
|
3445
3608
|
glColorData = new Uint32Array(glVertexData);
|
|
3446
3609
|
|
|
3447
3610
|
// setup the vertex data array
|
|
3611
|
+
let offset = glBatchCount = 0;
|
|
3448
3612
|
const initVertexAttribArray = (name, type, typeSize, size, normalize=0)=>
|
|
3449
3613
|
{
|
|
3450
3614
|
const location = glContext.getAttribLocation(glShader, name);
|
|
@@ -3452,13 +3616,10 @@ function glInit()
|
|
|
3452
3616
|
glContext.vertexAttribPointer(location, size, type, normalize, gl_VERTEX_BYTE_STRIDE, offset);
|
|
3453
3617
|
offset += size*typeSize;
|
|
3454
3618
|
}
|
|
3455
|
-
let offset = glBatchCount = 0;
|
|
3456
|
-
initVertexAttribArray('a', gl_FLOAT, 4, 1); // angle
|
|
3457
3619
|
initVertexAttribArray('p', gl_FLOAT, 4, 2); // position
|
|
3458
|
-
initVertexAttribArray('s', gl_FLOAT, 4, 2); // size
|
|
3459
3620
|
initVertexAttribArray('t', gl_FLOAT, 4, 2); // texture coords
|
|
3460
3621
|
initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4, 1); // color
|
|
3461
|
-
initVertexAttribArray('
|
|
3622
|
+
initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
|
|
3462
3623
|
}
|
|
3463
3624
|
|
|
3464
3625
|
/** Set the WebGl blend mode, normally you should call setBlendMode instead
|
|
@@ -3479,7 +3640,8 @@ function glSetBlendMode(additive)
|
|
|
3479
3640
|
function glSetTexture(texture=glTileTexture)
|
|
3480
3641
|
{
|
|
3481
3642
|
if (!glEnable) return;
|
|
3482
|
-
|
|
3643
|
+
|
|
3644
|
+
// must flush cache with the old texture to set a new one
|
|
3483
3645
|
if (texture != glActiveTexture)
|
|
3484
3646
|
glFlush();
|
|
3485
3647
|
|
|
@@ -3572,6 +3734,7 @@ function glPreRender(width, height, cameraX, cameraY, cameraScale)
|
|
|
3572
3734
|
|
|
3573
3735
|
// clear and set to same size as main canvas
|
|
3574
3736
|
glContext.viewport(0, 0, glCanvas.width = width, glCanvas.height = height);
|
|
3737
|
+
glContext.clear(gl_COLOR_BUFFER_BIT);
|
|
3575
3738
|
|
|
3576
3739
|
// set up the shader
|
|
3577
3740
|
glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = glTileTexture);
|
|
@@ -3615,15 +3778,13 @@ function glFlush()
|
|
|
3615
3778
|
* @memberof WebGL */
|
|
3616
3779
|
function glCopyToContext(context, forceDraw)
|
|
3617
3780
|
{
|
|
3618
|
-
if (!glEnable || !glBatchCount)
|
|
3781
|
+
if (!glEnable || !glBatchCount) return;
|
|
3619
3782
|
|
|
3620
3783
|
glFlush();
|
|
3784
|
+
|
|
3785
|
+
// do not draw in overlay mode because the canvas is visible
|
|
3621
3786
|
if (!glOverlay || forceDraw)
|
|
3622
|
-
{
|
|
3623
|
-
// do not draw/clear in overlay mode because the canvas is visible
|
|
3624
3787
|
context.drawImage(glCanvas, 0, 0);
|
|
3625
|
-
glContext.clear(gl_COLOR_BUFFER_BIT);
|
|
3626
|
-
}
|
|
3627
3788
|
}
|
|
3628
3789
|
|
|
3629
3790
|
/** Add a sprite to the gl draw list, used by all gl draw functions
|
|
@@ -3646,51 +3807,49 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba=0xffffff
|
|
|
3646
3807
|
// flush if there is no room for more verts or if different blend mode
|
|
3647
3808
|
if (glBatchCount == gl_MAX_BATCH || glBatchAdditive != glAdditive)
|
|
3648
3809
|
glFlush();
|
|
3810
|
+
|
|
3811
|
+
// prepare to create the verts from size and angle
|
|
3812
|
+
const c = Math.cos(angle)/2, s = Math.sin(angle)/2;
|
|
3813
|
+
const cx = c*sizeX, cy = c*sizeY, sx = s*sizeX, sy = s*sizeY;
|
|
3649
3814
|
|
|
3650
3815
|
// setup 2 triangles to form a quad
|
|
3651
3816
|
let offset = glBatchCount++ * gl_VERTICES_PER_QUAD * gl_INDICIES_PER_VERT;
|
|
3652
3817
|
|
|
3653
3818
|
// vertex 0
|
|
3654
|
-
glPositionData[offset++] =
|
|
3655
|
-
glPositionData[offset++] =
|
|
3656
|
-
glPositionData[offset++] =
|
|
3657
|
-
|
|
3658
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3819
|
+
glPositionData[offset++] = x - cx - sy;
|
|
3820
|
+
glPositionData[offset++] = y - cy + sx;
|
|
3821
|
+
glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
|
|
3822
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3659
3823
|
|
|
3660
3824
|
// vertex 1
|
|
3661
|
-
glPositionData[offset++] =
|
|
3662
|
-
glPositionData[offset++] =
|
|
3663
|
-
glPositionData[offset++] =
|
|
3664
|
-
|
|
3665
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3825
|
+
glPositionData[offset++] = x + cx + sy;
|
|
3826
|
+
glPositionData[offset++] = y + cy - sx;
|
|
3827
|
+
glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
|
|
3828
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3666
3829
|
|
|
3667
3830
|
// vertex 2
|
|
3668
|
-
glPositionData[offset++] =
|
|
3669
|
-
glPositionData[offset++] =
|
|
3670
|
-
glPositionData[offset++] =
|
|
3671
|
-
|
|
3672
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3831
|
+
glPositionData[offset++] = x - cx + sy;
|
|
3832
|
+
glPositionData[offset++] = y + cy + sx;
|
|
3833
|
+
glPositionData[offset++] = uv0X; glPositionData[offset++] = uv0Y;
|
|
3834
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3673
3835
|
|
|
3674
3836
|
// vertex 0
|
|
3675
|
-
glPositionData[offset++] =
|
|
3676
|
-
glPositionData[offset++] =
|
|
3677
|
-
glPositionData[offset++] =
|
|
3678
|
-
|
|
3679
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3837
|
+
glPositionData[offset++] = x - cx - sy;
|
|
3838
|
+
glPositionData[offset++] = y - cy + sx;
|
|
3839
|
+
glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
|
|
3840
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3680
3841
|
|
|
3681
3842
|
// vertex 3
|
|
3682
|
-
glPositionData[offset++] =
|
|
3683
|
-
glPositionData[offset++] =
|
|
3684
|
-
glPositionData[offset++] =
|
|
3685
|
-
|
|
3686
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3843
|
+
glPositionData[offset++] = x + cx - sy;
|
|
3844
|
+
glPositionData[offset++] = y - cy - sx;
|
|
3845
|
+
glPositionData[offset++] = uv1X; glPositionData[offset++] = uv1Y;
|
|
3846
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3687
3847
|
|
|
3688
3848
|
// vertex 1
|
|
3689
|
-
glPositionData[offset++] =
|
|
3690
|
-
glPositionData[offset++] =
|
|
3691
|
-
glPositionData[offset++] =
|
|
3692
|
-
|
|
3693
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3849
|
+
glPositionData[offset++] = x + cx + sy;
|
|
3850
|
+
glPositionData[offset++] = y + cy - sx;
|
|
3851
|
+
glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
|
|
3852
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3694
3853
|
}
|
|
3695
3854
|
|
|
3696
3855
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -3722,6 +3881,6 @@ gl_LINK_STATUS = 35714,
|
|
|
3722
3881
|
|
|
3723
3882
|
// constants for batch rendering
|
|
3724
3883
|
gl_VERTICES_PER_QUAD = 6,
|
|
3725
|
-
gl_INDICIES_PER_VERT =
|
|
3884
|
+
gl_INDICIES_PER_VERT = 6,
|
|
3726
3885
|
gl_MAX_BATCH = 1<<16,
|
|
3727
|
-
gl_VERTEX_BYTE_STRIDE =
|
|
3886
|
+
gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2; // vec2 * 2 + (char * 4) * 2
|