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.
Files changed (39) hide show
  1. package/README.md +17 -11
  2. package/build.bat +1 -0
  3. package/engine/engine.all.js +495 -504
  4. package/engine/engine.all.min.js +1 -1
  5. package/engine/engine.all.release.js +418 -259
  6. package/engine/engine.js +31 -23
  7. package/engine/engineAudio.js +21 -7
  8. package/engine/engineBuild.bat +9 -1
  9. package/engine/engineBuildSetup.bat +2 -1
  10. package/engine/engineDebug.js +81 -247
  11. package/engine/engineDraw.js +109 -21
  12. package/engine/engineFont.png +0 -0
  13. package/engine/engineInput.js +103 -50
  14. package/engine/engineMedals.js +17 -20
  15. package/engine/engineObject.js +14 -23
  16. package/engine/engineParticles.js +8 -6
  17. package/engine/engineRelease.js +3 -1
  18. package/engine/engineSettings.js +20 -10
  19. package/engine/engineTileLayer.js +19 -18
  20. package/engine/engineUtilities.js +14 -12
  21. package/engine/engineWebGL.js +59 -68
  22. package/engine/index.d.ts +1617 -0
  23. package/examples/breakout/game.js +9 -7
  24. package/examples/breakout/gameObjects.js +3 -3
  25. package/examples/breakout/index.html +6 -4
  26. package/examples/platformer/game.js +5 -5
  27. package/examples/platformer/gameEffects.js +11 -14
  28. package/examples/platformer/gameLevel.js +14 -7
  29. package/examples/platformer/gameObjects.js +3 -3
  30. package/examples/platformer/gamePlayer.js +13 -8
  31. package/examples/platformer/index.html +9 -7
  32. package/examples/platformer/tiles.png +0 -0
  33. package/examples/puzzle/game.js +2 -2
  34. package/examples/puzzle/index.html +5 -3
  35. package/examples/screenshot.jpg +0 -0
  36. package/examples/stress/index.html +7 -5
  37. package/game.js +9 -8
  38. package/index.html +16 -14
  39. package/package.json +1 -1
@@ -1,5 +1,7 @@
1
1
  /*
2
- LittleJS - Release build include file
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, max=1, min=0)=> (ASSERT(max > min), v < min ? min : v > max ? max : 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, max=1, min=0)=> max-min ? clamp((v-min) / (max-min)) : 0;
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, max=1, min=0)=> min + clamp(p) * (max-min);
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 - Center of box A
119
- * @param {Vector2} sizeA - Size of box A
120
- * @param {Vector2} pointB - Center of box B
121
- * @param {Vector2} sizeB - Size of box B
122
- * @return {Boolean} - True if overlapping
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, 0, this.setTime) : 0; }
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 tileBleedShrinkFix = .3;
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 = .5;
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 = 0;
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 = 99;
726
+ let medalDisplayHeight = 80;
713
727
 
714
728
  /** Size of icon in medal display
715
729
  * @default
716
730
  * @memberof Settings */
717
- let medalDisplayIconSize = 80;
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.1.18';
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, debugFPS = 0,
772
- shrinkTilesX, shrinkTilesY, drawCount, tileImageSize, tileImageSizeInverse;
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
- tileImageSizeInverse = vec2(1).divide(tileImageSize = vec2(tileImage.width, tileImage.height));
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
- debugFPS = lerp(.05, 1e3/(frameTimeDeltaMS||1), debugFPS);
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 + ' / ' + debugFPS.toFixed(1);
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
- mainContext.imageSmoothingEnabled = !cavasPixelated; // disable smoothing for pixel art
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
- /** Detroy and remove all objects that are not persistent or descendants of a persistent object */
984
+ /** Destroy and remove all objects */
960
985
  function engineObjectsDestroy()
961
986
  {
962
987
  for (const o of engineObjects)
963
- o.persistent || o.parent || o.destroy();
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(0,0)] - World space position of the object
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, -objectMaxSpeed);
1098
- this.velocity.y = clamp(this.velocity.y, objectMaxSpeed, -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 alread overlapping
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, elastic0, inelastic);
1189
- o.velocity.y = lerp(elasticity, elastic1, inelastic);
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, elastic0, inelastic);
1210
- o.velocity.x = lerp(elasticity, elastic1, inelastic);
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 > -1 && o > this.damping * this.velocity.y + gravity * this.gravityScale)
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 * tileImageSizeInverse.x;
1453
- const uvSizeY = tileSize.y * tileImageSizeInverse.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 + shrinkTilesX, uvY + shrinkTilesY,
1462
- uvX - shrinkTilesX + uvSizeX, uvY - shrinkTilesX + uvSizeY,
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 + tileBleedShrinkFix;
1482
- const sY = (tileIndex/cols|0)*tileSize.y + tileBleedShrinkFix;
1483
- const sWidth = tileSize.x - 2*tileBleedShrinkFix;
1484
- const sHeight = tileSize.y - 2*tileBleedShrinkFix;
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
- /** Enable normal or additive blend mode
1593
- * @param {Boolean} [additive=0]
1594
- * @param {Boolean} [useWebGL=glEnable]
1595
- * @memberof Draw */
1596
- function setBlendMode(additive, useWebGL=glEnable)
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
- if (glEnable && useWebGL)
1599
- glSetBlendMode(additive);
1600
- else
1601
- mainContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
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[0] = [];
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
- const inputData = [[]];
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
- mousePosScreen = mainCanvasSize.multiply(
1786
- vec2(percent(e.x, rect.right, rect.left), percent(e.y, rect.bottom, rect.top)));
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, deadZoneMax, deadZone) :
1815
- v < -deadZone ? -percent(-v, deadZoneMax, deadZone) : 0;
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
- if (touchMouseEnable && isTouchDevice)
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
- // Touch gamepad - on screen virtual gamepad
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
- sticks[0] = touchPos.subtract(stickCenter).clampLength();
1921
- sticks[0].y = -sticks[0].y; // flip vertical
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
- buttons[button > 2 ? 2 : button > 1 ? 3 : button] = 1; // fix button locations
2074
+ touchGamepadButtons[button] = 1;
1928
2075
  }
1929
2076
  else if (touchPos.distance(startCenter) < touchGamepadSize)
1930
2077
  {
1931
- // virtual start button
1932
- buttons[9] = 1;
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, 5);
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*.6));
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*.3, 0,9);
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, range);
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 stopSpeech = ()=> speechSynthesis && speechSynthesis.cancel();
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 pan and gain nodes
2225
- source
2226
- .connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, 1, -1)}))
2227
- .connect(new GainNode(audioContext, {'gain':soundVolume*volume}))
2228
- .connect(audioContext.destination);
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> - Tile layers are not rendered using WebGL to allow modifications at run time
2587
- * <br> - Tile layers are sorted
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(0,0)] - World space position
2598
- * @param {Vector2} [size=objectDefaultSize] - World space size
2599
- * @param {Vector2} [tileSize=tileSizeDefault] - Size of tiles in source pixels
2600
- * @param {Vector2} [scale=new Vector2(1,1)] - How much to scale this layer when rendered
2601
- * @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
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 main canvas
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 using webgl if possible */
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=1] - Should it clear the canvas before drawing */
2671
- redrawStart(clear = 1)
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
- this.canvas.width = width;
2679
- this.canvas.height = height;
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; // full color not supported in this mode
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, new Color(0,0,0), // colorStartA, colorStartB
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=0] - Should color be randomized linearly or across each component
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, 0, undefined, renderOrder);
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.beginPath(context.save());
3339
+ context.save();
3340
+ context.beginPath();
3173
3341
  context.fillStyle = '#ddd'
3174
3342
  context.fill(context.rect(x, y, width, medalDisplayHeight));
3175
- context.strokeStyle = context.fillStyle = '#000';
3176
- context.lineWidth = 2;
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 = '40px '+ fontDefault;
3184
- context.fillText(this.name, x+medalDisplayIconSize+25, y+35);
3185
- context.font = '25px '+ fontDefault;
3186
- context.restore(context.fillText(this.description, x+medalDisplayIconSize+25, y+70));
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*.6 + 'px '+ fontDefault;
3201
- context.fillStyle = '#000';
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
- if (glOverlay)
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;'+ // use highp for better accuracy, lowp for better perf
3421
- 'uniform mat4 m;'+ // transform matrix
3422
- 'attribute float a;'+ // angle
3423
- 'attribute vec2 p,s,t;'+ // position, size, uv
3424
- 'attribute vec4 c,b;'+ // color, additiveColor
3425
- 'varying vec2 v;'+ // return uv
3426
- 'varying vec4 d,e;'+ // return color, additiveColor
3427
- 'void main(){'+ // shader entry point
3428
- 'gl_Position=m*vec4((s*cos(a)-vec2(-s.y,s)*sin(a))*.5+p,1,1);'+// transform position
3429
- 'v=t;d=c;e=b;'+ // pass stuff to fragment shader
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 lowp float;'+ // use highp for better accuracy, lowp for better perf
3433
- 'varying vec2 v;'+ // uv
3434
- 'varying vec4 d,e;'+ // color, additiveColor
3435
- 'uniform sampler2D j;'+ // texture
3436
- 'void main(){'+ // shader entry point
3437
- 'gl_FragColor=texture2D(j,v)*d+e;'+ // modulate texture by color plus additive
3438
- '}' // end of shader
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('b', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
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) return;
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++] = angle;
3655
- glPositionData[offset++] = x; glPositionData[offset++] = y;
3656
- glPositionData[offset++] = -sizeX; glPositionData[offset++] = -sizeY;
3657
- glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
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++] = angle;
3662
- glPositionData[offset++] = x; glPositionData[offset++] = y;
3663
- glPositionData[offset++] = sizeX; glPositionData[offset++] = sizeY;
3664
- glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
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++] = angle;
3669
- glPositionData[offset++] = x; glPositionData[offset++] = y;
3670
- glPositionData[offset++] = -sizeX; glPositionData[offset++] = sizeY;
3671
- glPositionData[offset++] = uv0X; glPositionData[offset++] = uv0Y;
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++] = angle;
3676
- glPositionData[offset++] = x; glPositionData[offset++] = y;
3677
- glPositionData[offset++] = -sizeX; glPositionData[offset++] = -sizeY;
3678
- glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
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++] = angle;
3683
- glPositionData[offset++] = x; glPositionData[offset++] = y;
3684
- glPositionData[offset++] = sizeX; glPositionData[offset++] = -sizeY;
3685
- glPositionData[offset++] = uv1X; glPositionData[offset++] = uv1Y;
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++] = angle;
3690
- glPositionData[offset++] = x; glPositionData[offset++] = y;
3691
- glPositionData[offset++] = sizeX; glPositionData[offset++] = sizeY;
3692
- glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
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 = 9,
3884
+ gl_INDICIES_PER_VERT = 6,
3726
3885
  gl_MAX_BATCH = 1<<16,
3727
- gl_VERTEX_BYTE_STRIDE = 4 + (4 * 2) * 3 + (4) * 2; // float + vec2 * 3 + (char * 4) * 2
3886
+ gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2; // vec2 * 2 + (char * 4) * 2