littlejsengine 1.1.2 → 1.1.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 (94) hide show
  1. package/README.md +55 -0
  2. package/build.bat +2 -1
  3. package/engine/engine.all.js +1320 -930
  4. package/engine/engine.all.min.js +1 -1
  5. package/engine/engine.all.release.js +965 -601
  6. package/engine/engine.js +71 -60
  7. package/engine/engineAudio.js +80 -22
  8. package/engine/engineBuild.bat +2 -2
  9. package/engine/engineDebug.js +65 -36
  10. package/engine/engineDraw.js +71 -56
  11. package/engine/engineInput.js +178 -28
  12. package/engine/engineMedals.js +66 -58
  13. package/engine/engineObject.js +86 -71
  14. package/engine/engineParticles.js +101 -63
  15. package/engine/engineRelease.js +4 -1
  16. package/engine/engineSettings.js +62 -53
  17. package/engine/engineTileLayer.js +99 -83
  18. package/engine/engineUtilities.js +95 -59
  19. package/engine/engineWebGL.js +53 -48
  20. package/examples/breakout/game.js +1 -1
  21. package/examples/breakout/gameObjects.js +2 -2
  22. package/examples/breakout/index.html +7 -5
  23. package/examples/platformer/game.js +7 -4
  24. package/examples/platformer/gameEffects.js +8 -11
  25. package/examples/platformer/gameLevel.js +15 -8
  26. package/examples/platformer/gameObjects.js +2 -2
  27. package/examples/platformer/gamePlayer.js +6 -6
  28. package/examples/platformer/index.html +10 -8
  29. package/examples/puzzle/game.js +3 -3
  30. package/examples/puzzle/index.html +6 -4
  31. package/examples/stress/index.html +7 -5
  32. package/game.js +10 -10
  33. package/index.html +17 -15
  34. package/package.json +4 -7
  35. package/tiles.png +0 -0
  36. package/LittleJS.zip +0 -0
  37. package/docs/Audio.html +0 -2264
  38. package/docs/Color.html +0 -2506
  39. package/docs/Debug.html +0 -2929
  40. package/docs/Draw.html +0 -3854
  41. package/docs/EngineObject.html +0 -2506
  42. package/docs/Input.html +0 -2478
  43. package/docs/Medal.html +0 -992
  44. package/docs/Medals.html +0 -624
  45. package/docs/Music.html +0 -569
  46. package/docs/Newgrounds.html +0 -1384
  47. package/docs/Particle.html +0 -567
  48. package/docs/ParticleEmitter.html +0 -1537
  49. package/docs/Random.html +0 -1770
  50. package/docs/Settings.html +0 -2885
  51. package/docs/Sound.html +0 -998
  52. package/docs/TileLayer.html +0 -3211
  53. package/docs/TileLayerData.html +0 -569
  54. package/docs/Timer.html +0 -1168
  55. package/docs/Utilities.html +0 -3266
  56. package/docs/Vector2.html +0 -3956
  57. package/docs/WebGL.html +0 -1842
  58. package/docs/engine.js.html +0 -428
  59. package/docs/engineAudio.js.html +0 -567
  60. package/docs/engineDebug.js.html +0 -721
  61. package/docs/engineDraw.js.html +0 -409
  62. package/docs/engineInput.js.html +0 -397
  63. package/docs/engineMedals.js.html +0 -445
  64. package/docs/engineObject.js.html +0 -492
  65. package/docs/engineParticles.js.html +0 -399
  66. package/docs/engineSettings.js.html +0 -358
  67. package/docs/engineTileLayer.js.html +0 -482
  68. package/docs/engineUtilities.js.html +0 -600
  69. package/docs/engineWebGL.js.html +0 -507
  70. package/docs/fonts/MavenPro-Regular.ttf +0 -0
  71. package/docs/fonts/Montserrat-Regular.ttf +0 -0
  72. package/docs/fonts/Muli-Black.ttf +0 -0
  73. package/docs/fonts/OFL-hind.txt +0 -93
  74. package/docs/fonts/OFL-montserrat.txt +0 -93
  75. package/docs/global.html +0 -1635
  76. package/docs/index.html +0 -243
  77. package/docs/scripts/fix-code-block.js +0 -53
  78. package/docs/scripts/fix-navbar.js +0 -25
  79. package/docs/scripts/linenumber.js +0 -25
  80. package/docs/scripts/misc.js +0 -217
  81. package/docs/scripts/resize.js +0 -85
  82. package/docs/scripts/search.js +0 -83
  83. package/docs/scripts/third-party/Apache-License-2.0.txt +0 -202
  84. package/docs/scripts/third-party/fuse.js +0 -9
  85. package/docs/scripts/third-party/lang-css.js +0 -2
  86. package/docs/scripts/third-party/prettify.js +0 -28
  87. package/docs/static/favicon.png +0 -0
  88. package/docs/styles/clean-jsdoc-theme-base.css +0 -393
  89. package/docs/styles/clean-jsdoc-theme-dark.css +0 -324
  90. package/docs/styles/clean-jsdoc-theme-light.css +0 -319
  91. package/docs/styles/reset.css +0 -346
  92. package/docs/styles/third-party/ionicons.min.css +0 -11
  93. package/docs/styles/third-party/prettify-jsdoc.css +0 -111
  94. package/docs/styles/third-party/prettify-tomorrow.css +0 -5
@@ -1,15 +1,28 @@
1
1
  /**
2
- * LittleJS Drawing System
3
- * <br> - Hybrid with both Canvas2D and WebGL available
4
- * <br> - Super fast tile sheet rendering with WebGL
5
- * <br> - Can apply rotation, mirror, color and additive color
6
- * <br> - Many useful utility functions
7
- * @namespace Draw
2
+ * LittleJS Drawing System
3
+ * <br> - Hybrid with both Canvas2D and WebGL available
4
+ * <br> - Super fast tile sheet rendering with WebGL
5
+ * <br> - Can apply rotation, mirror, color and additive color
6
+ * <br> - Many useful utility functions
7
+ * <br>
8
+ * <br>LittleJS uses a hybrid rendering solution with the best of both Canvas2D and WebGL.
9
+ * <br>There are 3 canvas/contexts available to draw to...
10
+ * <br> - mainCanvas - 2D background canvas, non WebGL stuff like tile layers are drawn here.
11
+ * <br> - glCanvas - Used by the accelerated WebGL batch rendering system.
12
+ * <br> - overlayCanvas - Another 2D canvas that appears on top of the other 2 canvases.
13
+ * <br>
14
+ * <br>The WebGL rendering system is very fast with some caveats...
15
+ * <br> - The default setup supports only 1 tile sheet, to support more call glCreateTexture and glSetTexture
16
+ * <br> - Switching blend modes (additive) or textures causes another draw call which is expensive in excess
17
+ * <br> - Group additive rendering together using renderOrder to mitigate this issue
18
+ * <br>
19
+ * <br>The LittleJS rendering solution is intentionally simple, feel free to adjust it for your needs!
20
+ * @namespace Draw
8
21
  */
9
22
 
10
23
  'use strict';
11
24
 
12
- /** Main tilesheet to use for batch rendering system
25
+ /** Tile sheet for batch rendering system
13
26
  * @type {Image}
14
27
  * @memberof Draw */
15
28
  const tileImage = new Image();
@@ -34,7 +47,7 @@ let overlayCanvas;
34
47
  * @memberof Draw */
35
48
  let overlayContext;
36
49
 
37
- /** The size of the main canvas (and other secondary canvases: overlayCanvas and glCanvas)
50
+ /** The size of the main canvas (and other secondary canvases)
38
51
  * @type {Vector2}
39
52
  * @memberof Draw */
40
53
  let mainCanvasSize = vec2();
@@ -53,42 +66,39 @@ const screenToWorld = (screenPos)=>
53
66
  const worldToScreen = (worldPos)=>
54
67
  worldPos.subtract(cameraPos).multiply(vec2(cameraScale,-cameraScale)).add(mainCanvasSize.scale(.5)).subtract(vec2(.5));
55
68
 
56
- /** Draw textured tile centered on pos
57
- * @param {Vector2} pos - Center of the tile
58
- * @param {Vector2} [size=new Vector2(1,1)] - Size of the tile
59
- * @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured
60
- * @param {Vector2} [tileSize=defaultTileSize] - Tile size in source pixels
61
- * @param {Color} [color=new Color(1,1,1)]
62
- * @param {Number} [angle=0]
63
- * @param {Boolean} [mirror=0]
64
- * @param {Color} [additiveColor=new Color(0,0,0,0)]
69
+ /** Draw textured tile centered in world space, with color applied if using WebGL
70
+ * @param {Vector2} pos - Center of the tile in world space
71
+ * @param {Vector2} [size=new Vector2(1,1)] - Size of the tile in world space, width and height
72
+ * @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured
73
+ * @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels
74
+ * @param {Color} [color=new Color(1,1,1)] - Color to modulate with
75
+ * @param {Number} [angle=0] - Angle to rotate by
76
+ * @param {Boolean} [mirror=0] - If true image is flipped along the Y axis
77
+ * @param {Color} [additiveColor=new Color(0,0,0,0)] - Additive color to be applied
78
+ * @param {Boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
65
79
  * @memberof Draw */
66
- function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=defaultTileSize, color=new Color, angle=0, mirror,
67
- additiveColor=new Color(0,0,0,0))
80
+ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, color=new Color, angle=0, mirror,
81
+ additiveColor=new Color(0,0,0,0), useWebGL=glEnable)
68
82
  {
69
83
  showWatermark && ++drawCount;
70
- if (glEnable)
84
+ if (glEnable && useWebGL)
71
85
  {
72
- if (tileIndex < 0)
86
+ if (tileIndex < 0 || !tileImage.width)
73
87
  {
74
- // if negative tile index, force untextured
88
+ // if negative tile index or image not found, force untextured
75
89
  glDraw(pos.x, pos.y, size.x, size.y, angle, 0, 0, 0, 0, 0, color.rgbaInt());
76
90
  }
77
91
  else
78
92
  {
79
93
  // calculate uvs and render
80
- const cols = tileImage.width / tileSize.x |0;
81
- const uvSizeX = tileSize.x * tileImageSizeInverse.x;
82
- const uvSizeY = tileSize.y * tileImageSizeInverse.y;
94
+ const cols = tileImageSize.x / tileSize.x |0;
95
+ const uvSizeX = tileSize.x / tileImageSize.x;
96
+ const uvSizeY = tileSize.y / tileImageSize.y;
83
97
  const uvX = (tileIndex%cols)*uvSizeX, uvY = (tileIndex/cols|0)*uvSizeY;
84
-
85
- // shrink uvs to prevent bleeding
86
- const shrinkTilesX = tileBleedShrinkFix * tileImageSizeInverse.x;
87
- const shrinkTilesY = tileBleedShrinkFix * tileImageSizeInverse.y;
88
98
 
89
99
  glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
90
- uvX + shrinkTilesX, uvY + shrinkTilesY,
91
- uvX - shrinkTilesX + uvSizeX, uvY - shrinkTilesX + uvSizeY,
100
+ uvX + tileImageFixBleed.x, uvY + tileImageFixBleed.y,
101
+ uvX - tileImageFixBleed.x + uvSizeX, uvY - tileImageFixBleed.y + uvSizeY,
92
102
  color.rgbaInt(), additiveColor.rgbaInt());
93
103
  }
94
104
  }
@@ -106,11 +116,11 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=defaultTileSize, col
106
116
  else
107
117
  {
108
118
  // calculate uvs and render
109
- const cols = tileImage.width / tileSize.x |0;
110
- const sX = (tileIndex%cols)*tileSize.x + tileBleedShrinkFix;
111
- const sY = (tileIndex/cols|0)*tileSize.y + tileBleedShrinkFix;
112
- const sWidth = tileSize.x - 2*tileBleedShrinkFix;
113
- const sHeight = tileSize.y - 2*tileBleedShrinkFix;
119
+ const cols = tileImageSize.x / tileSize.x |0;
120
+ const sX = (tileIndex%cols)*tileSize.x + tileFixBleedScale;
121
+ const sY = (tileIndex/cols|0)*tileSize.y + tileFixBleedScale;
122
+ const sWidth = tileSize.x - 2*tileFixBleedScale;
123
+ const sHeight = tileSize.y - 2*tileFixBleedScale;
114
124
  context.globalAlpha = color.a; // only alpha is supported
115
125
  context.drawImage(tileImage, sX, sY, sWidth, sHeight, -.5, -.5, 1, 1);
116
126
  }
@@ -118,41 +128,44 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=defaultTileSize, col
118
128
  }
119
129
  }
120
130
 
121
- /** Draw colored untextured rect centered on pos
131
+ /** Draw colored rect centered on pos
122
132
  * @param {Vector2} pos
123
133
  * @param {Vector2} [size=new Vector2(1,1)]
124
134
  * @param {Color} [color=new Color(1,1,1)]
125
135
  * @param {Number} [angle=0]
136
+ * @param {Boolean} [useWebGL=glEnable]
126
137
  * @memberof Draw */
127
- function drawRect(pos, size, color, angle)
138
+ function drawRect(pos, size, color, angle, useWebGL)
128
139
  {
129
- drawTile(pos, size, -1, defaultTileSize, color, angle);
140
+ drawTile(pos, size, -1, tileSizeDefault, color, angle, 0, 0, useWebGL);
130
141
  }
131
142
 
132
143
  /** Draw textured tile centered on pos in screen space
133
- * @param {Vector2} pos - Center of the tile
134
- * @param {Vector2} [size=new Vector2(1,1)] - Size of the tile
135
- * @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured
136
- * @param {Vector2} [tileSize=defaultTileSize] - Tile size in source pixels
144
+ * @param {Vector2} pos - Center of the tile
145
+ * @param {Vector2} [size=new Vector2(1,1)] - Size of the tile
146
+ * @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured
147
+ * @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels
137
148
  * @param {Color} [color=new Color]
138
149
  * @param {Number} [angle=0]
139
150
  * @param {Boolean} [mirror=0]
140
151
  * @param {Color} [additiveColor=new Color(0,0,0,0)]
152
+ * @param {Boolean} [useWebGL=glEnable]
141
153
  * @memberof Draw */
142
- function drawTileScreenSpace(pos, size=vec2(1), tileIndex, tileSize, color, angle, mirror, additiveColor)
154
+ function drawTileScreenSpace(pos, size=vec2(1), tileIndex, tileSize, color, angle, mirror, additiveColor, useWebGL)
143
155
  {
144
- drawTile(screenToWorld(pos), size.scale(1/cameraScale), tileIndex, tileSize, color, angle, mirror, additiveColor);
156
+ drawTile(screenToWorld(pos), size.scale(1/cameraScale), tileIndex, tileSize, color, angle, mirror, additiveColor, useWebGL);
145
157
  }
146
158
 
147
- /** Draw colored untextured rectangle in screen space
159
+ /** Draw colored rectangle in screen space
148
160
  * @param {Vector2} pos
149
161
  * @param {Vector2} [size=new Vector2(1,1)]
150
162
  * @param {Color} [color=new Color(1,1,1)]
151
163
  * @param {Number} [angle=0]
164
+ * @param {Boolean} [useWebGL=glEnable]
152
165
  * @memberof Draw */
153
- function drawRectScreenSpace(pos, size, color, angle)
166
+ function drawRectScreenSpace(pos, size, color, angle, useWebGL)
154
167
  {
155
- drawTileScreenSpace(pos, size, -1, defaultTileSize, color, angle);
168
+ drawTileSrceenSpace(pos, size, -1, tileSizeDefault, color, angle, 0, 0, useWebGL);
156
169
  }
157
170
 
158
171
  /** Draw colored line between two points
@@ -160,15 +173,16 @@ function drawRectScreenSpace(pos, size, color, angle)
160
173
  * @param {Vector2} posB
161
174
  * @param {Number} [thickness=.1]
162
175
  * @param {Color} [color=new Color(1,1,1)]
176
+ * @param {Boolean} [useWebGL=glEnable]
163
177
  * @memberof Draw */
164
- function drawLine(posA, posB, thickness=.1, color)
178
+ function drawLine(posA, posB, thickness=.1, color, useWebGL)
165
179
  {
166
180
  const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
167
181
  const size = vec2(thickness, halfDelta.length()*2);
168
- drawRect(posA.add(halfDelta), size, color, halfDelta.angle());
182
+ drawRect(posA.add(halfDelta), size, color, halfDelta.angle(), 0, 0, useWebGL);
169
183
  }
170
184
 
171
- /** Draw directly to a 2d canvas context in world space (bipass webgl)
185
+ /** Draw directly to a 2d canvas context in world space
172
186
  * @param {Vector2} pos
173
187
  * @param {Vector2} size
174
188
  * @param {Number} angle
@@ -184,7 +198,7 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainCont
184
198
  context.save();
185
199
  context.translate(pos.x+.5|0, pos.y-.5|0);
186
200
  context.rotate(angle);
187
- context.scale(mirror?-size.x:size.x, size.y);
201
+ context.scale(mirror ? -size.x : size.x, size.y);
188
202
  drawFunction(context);
189
203
  context.restore();
190
204
  }
@@ -198,7 +212,7 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainCont
198
212
  * @param {Color} [lineColor=new Color(0,0,0)]
199
213
  * @param {String} [textAlign='center']
200
214
  * @memberof Draw */
201
- function drawText(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=defaultFont)
215
+ function drawText(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault)
202
216
  {
203
217
  pos = worldToScreen(pos);
204
218
  overlayContext.font = size*cameraScale + 'px '+ font;
@@ -214,12 +228,13 @@ function drawText(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new
214
228
  overlayContext.fillText(text, pos.x, pos.y);
215
229
  }
216
230
 
217
- /** Enable additive or regular blend mode
231
+ /** Enable normal or additive blend mode
218
232
  * @param {Boolean} [additive=0]
233
+ * @param {Boolean} [useWebGL=glEnable]
219
234
  * @memberof Draw */
220
- function setBlendMode(additive)
235
+ function setBlendMode(additive, useWebGL=glEnable)
221
236
  {
222
- if (glEnable)
237
+ if (glEnable && useWebGL)
223
238
  glSetBlendMode(additive);
224
239
  else
225
240
  mainContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
@@ -1,9 +1,10 @@
1
1
  /**
2
- * LittleJS Input System
3
- * <br> - Tracks key down, pressed, and released
4
- * <br> - Also tracks mouse buttons, position, and wheel
5
- * <br> - Supports multiple gamepads
6
- * @namespace Input
2
+ * LittleJS Input System
3
+ * <br> - Tracks key down, pressed, and released
4
+ * <br> - Also tracks mouse buttons, position, and wheel
5
+ * <br> - Supports multiple gamepads
6
+ * <br> - Virtual gamepad for touch devices with touchGamepadSize
7
+ * @namespace Input
7
8
  */
8
9
 
9
10
  'use strict';
@@ -31,7 +32,7 @@ const keyWasReleased = (key, device=0)=> inputData[device] && inputData[device][
31
32
 
32
33
  /** Clears all input
33
34
  * @memberof Input */
34
- const clearInput = ()=> inputData[0] = [];
35
+ const clearInput = ()=> inputData = [[]];
35
36
 
36
37
  /** Returns true if mouse button is down
37
38
  * @param {Number} button
@@ -67,7 +68,7 @@ let mouseWheel = 0;
67
68
 
68
69
  /** Returns true if user is using gamepad (has more recently pressed a gamepad button)
69
70
  * @memberof Input */
70
- let usingGamepad = 0;
71
+ let isUsingGamepad = 0;
71
72
 
72
73
  /** Returns true if gamepad button is down
73
74
  * @param {Number} button
@@ -100,7 +101,9 @@ const gamepadStick = (stick, gamepad=0)=> stickData[gamepad] ? stickData[gamepa
100
101
  ///////////////////////////////////////////////////////////////////////////////
101
102
  // Input update called by engine
102
103
 
103
- const inputData = [[]];
104
+ // store input as a bit field for each key: 1 = isDown, 2 = wasPressed, 4 = wasReleased
105
+ // mouse and keyboard are stored together in device 0, gamepads are in devices > 0
106
+ let inputData = [[]];
104
107
 
105
108
  function inputUpdate()
106
109
  {
@@ -126,34 +129,38 @@ function inputUpdatePost()
126
129
  ///////////////////////////////////////////////////////////////////////////////
127
130
  // Keyboard event handlers
128
131
 
129
- onkeydown = e=>
132
+ onkeydown = (e)=>
130
133
  {
131
134
  if (debug && e.target != document.body) return;
132
- e.repeat || (inputData[usingGamepad = 0][remapKeyCode(e.keyCode)] = 3);
135
+ e.repeat || (inputData[isUsingGamepad = 0][remapKeyCode(e.keyCode)] = 3);
133
136
  debug || e.preventDefault();
134
137
  }
135
- onkeyup = e=>
138
+ onkeyup = (e)=>
136
139
  {
137
140
  if (debug && e.target != document.body) return;
138
141
  inputData[0][remapKeyCode(e.keyCode)] = 4;
139
142
  }
140
- const remapKeyCode = c=> copyWASDToDpad ? c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
143
+ const remapKeyCode = (c)=> inputWASDEmulateDirection ? c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
141
144
 
142
145
  ///////////////////////////////////////////////////////////////////////////////
143
146
  // Mouse event handlers
144
147
 
145
- onmousedown = e=> {inputData[usingGamepad = 0][e.button] = 3; onmousemove(e); e.button && e.preventDefault();}
146
- onmouseup = e=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
147
- onmousemove = e=>
148
+ onmousedown = (e)=> {inputData[isUsingGamepad = 0][e.button] = 3; onmousemove(e); e.button && e.preventDefault();}
149
+ onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
150
+ onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
151
+ onwheel = (e)=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
152
+ oncontextmenu = (e)=> !1; // prevent right click menu
153
+
154
+ // convert a mouse or touch event position to screen space
155
+ const mouseToScreen = (mousePos)=>
148
156
  {
149
- // convert mouse pos to canvas space
150
- if (!mainCanvas) return;
157
+ if (!mainCanvas)
158
+ return vec2(); // fix bug that can occur if user clicks before page loads
159
+
151
160
  const rect = mainCanvas.getBoundingClientRect();
152
- mousePosScreen.x = mainCanvasSize.x * percent(e.x, rect.right, rect.left);
153
- mousePosScreen.y = mainCanvasSize.y * percent(e.y, rect.bottom, rect.top);
161
+ return mainCanvasSize.multiply(
162
+ vec2(percent(mousePos.x, rect.left, rect.right), percent(mousePos.y, rect.top, rect.bottom)));
154
163
  }
155
- onwheel = e=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
156
- oncontextmenu = e=> !1; // prevent right click menu
157
164
 
158
165
  ///////////////////////////////////////////////////////////////////////////////
159
166
  // Gamepad input
@@ -161,6 +168,21 @@ oncontextmenu = e=> !1; // prevent right click menu
161
168
  const stickData = [];
162
169
  function gamepadsUpdate()
163
170
  {
171
+ if (touchGamepadEnable && touchGamepadTimer.isSet())
172
+ {
173
+ // read virtual analog stick
174
+ const sticks = stickData[0] || (stickData[0] = []);
175
+ sticks[0] = vec2(touchGamepadStick.x, -touchGamepadStick.y); // flip vertical
176
+
177
+ // read virtual gamepad buttons
178
+ const data = inputData[1] || (inputData[1] = []);
179
+ for (let i=10; i--;)
180
+ {
181
+ const j = i == 3 ? 2 : i == 2 ? 3 : i; // fix button locations
182
+ data[j] = touchGamepadButtons[i] ? 1 + 2*!gamepadIsDown(j,0) : 4*gamepadIsDown(j,0);
183
+ }
184
+ }
185
+
164
186
  if (!gamepadsEnable || !navigator.getGamepads || !document.hasFocus() && !debug)
165
187
  return;
166
188
 
@@ -178,8 +200,8 @@ function gamepadsUpdate()
178
200
  // read clamp dead zone of analog sticks
179
201
  const deadZone = .3, deadZoneMax = .8;
180
202
  const applyDeadZone = (v)=>
181
- v > deadZone ? percent( v, deadZoneMax, deadZone) :
182
- v < -deadZone ? -percent(-v, deadZoneMax, deadZone) : 0;
203
+ v > deadZone ? percent( v, deadZone, deadZoneMax) :
204
+ v < -deadZone ? -percent(-v, deadZone, deadZoneMax) : 0;
183
205
 
184
206
  // read analog sticks
185
207
  for (let j = 0; j < gamepad.axes.length-1; j+=2)
@@ -190,10 +212,11 @@ function gamepadsUpdate()
190
212
  {
191
213
  const button = gamepad.buttons[j];
192
214
  data[j] = button.pressed ? 1 + 2*!gamepadIsDown(j,i) : 4*gamepadIsDown(j,i);
193
- usingGamepad |= !i && button.pressed;
215
+ isUsingGamepad |= !i && button.pressed;
216
+ touchGamepadEnable && touchGamepadTimer.unset(); // disable touch gamepad if using real gamepad
194
217
  }
195
-
196
- if (copyGamepadDirectionToStick)
218
+
219
+ if (gamepadDirectionEmulateStick)
197
220
  {
198
221
  // copy dpad to left analog stick when pressed
199
222
  const dpad = vec2(gamepadIsDown(15,i) - gamepadIsDown(14,i), gamepadIsDown(12,i) - gamepadIsDown(13,i));
@@ -204,18 +227,31 @@ function gamepadsUpdate()
204
227
  }
205
228
  }
206
229
 
230
+ ///////////////////////////////////////////////////////////////////////////////
231
+
232
+ /** Pulse the vibration hardware if it exists
233
+ * @param {Number} [pattern=100] - a single value in miliseconds or vibration interval array
234
+ * @memberof Input */
235
+ const vibrate = (pattern)=> vibrateEnable && Navigator.vibrate && Navigator.vibrate(pattern);
236
+
237
+ /** Cancel any ongoing vibration
238
+ * @memberof Input */
239
+ const vibrateStop = ()=> vibrate(0);
240
+
207
241
  ///////////////////////////////////////////////////////////////////////////////
208
242
  // Touch input
209
243
 
210
244
  /** True if a touch device has been detected
211
245
  * @const {boolean}
212
246
  * @memberof Input */
213
- const isTouchDevice = touchInputEnable && window.ontouchstart !== undefined;
247
+ const isTouchDevice = window.ontouchstart !== undefined;
248
+
249
+ // try to enable touch mouse
214
250
  if (isTouchDevice)
215
251
  {
216
252
  // handle all touch events the same way
217
253
  let wasTouching, hadTouchInput;
218
- ontouchstart = ontouchmove = ontouchend = e=>
254
+ ontouchstart = ontouchmove = ontouchend = (e)=>
219
255
  {
220
256
  e.button = 0; // all touches are left click
221
257
 
@@ -239,4 +275,118 @@ if (isTouchDevice)
239
275
  // prevent normal mouse events from being called
240
276
  return !e.cancelable;
241
277
  }
278
+ }
279
+
280
+ ///////////////////////////////////////////////////////////////////////////////
281
+ // touch gamepad, virtual on screen gamepad emulator for touch devices
282
+
283
+ // touch input internal variables
284
+ let touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadStick = vec2(), touchGamepadAnalog = 1;
285
+
286
+ // create the touch gamepad, called automatically by the engine
287
+ function touchGamepadCreate()
288
+ {
289
+ if (!touchGamepadEnable || !isTouchDevice)
290
+ return;
291
+
292
+ ontouchstart = ontouchmove = ontouchend = (e)=>
293
+ {
294
+ if (!touchGamepadEnable)
295
+ return;
296
+
297
+ // clear touch gamepad input
298
+ touchGamepadStick = vec2();
299
+ touchGamepadButtons = [];
300
+
301
+ const touching = e.touches.length;
302
+ if (touching)
303
+ {
304
+ touchGamepadTimer.isSet() || zzfx(0) ; // fix mobile audio, force it to play a sound the first time
305
+
306
+ // set that gamepad is active
307
+ isUsingGamepad = 1;
308
+ touchGamepadTimer.set();
309
+
310
+ if (paused)
311
+ {
312
+ // touch anywhere to press start when paused
313
+ touchGamepadButtons[9] = 1;
314
+ return;
315
+ }
316
+ }
317
+
318
+ // get center of left and right sides
319
+ const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
320
+ const buttonCenter = mainCanvasSize.subtract(vec2(touchGamepadSize, touchGamepadSize));
321
+ const startCenter = mainCanvasSize.scale(.5);
322
+
323
+ // check each touch point
324
+ for (const touch of e.touches)
325
+ {
326
+ const touchPos = mouseToScreen(vec2(touch.clientX, touch.clientY));
327
+ if (touchPos.distance(stickCenter) < touchGamepadSize)
328
+ {
329
+ // virtual analog stick
330
+ if (touchGamepadAnalog)
331
+ touchGamepadStick = touchPos.subtract(stickCenter).clampLength(touchGamepadSize/2).scale(2/touchGamepadSize);
332
+ else
333
+ {
334
+ // 8 way dpad
335
+ const angle = touchPos.subtract(stickCenter).angle();
336
+ touchGamepadStick.setAngle((angle * 4 / PI + 8.5 | 0) * PI / 4);
337
+ }
338
+ }
339
+ else if (touchPos.distance(buttonCenter) < touchGamepadSize)
340
+ {
341
+ // virtual face buttons
342
+ const button = touchPos.subtract(buttonCenter).direction();
343
+ touchGamepadButtons[button] = 1;
344
+ }
345
+ else if (touchPos.distance(startCenter) < touchGamepadSize)
346
+ {
347
+ // virtual start button in center
348
+ touchGamepadButtons[9] = 1;
349
+ }
350
+ }
351
+ }
352
+ }
353
+
354
+ // render the touch gamepad, called automatically by the engine
355
+ function touchGamepadRender()
356
+ {
357
+ if (!touchGamepadEnable || !touchGamepadTimer.isSet())
358
+ return;
359
+
360
+ // fade off when not touching or paused
361
+ const alpha = percent(touchGamepadTimer.get(), 4, 3);
362
+ if (!alpha || paused)
363
+ return;
364
+
365
+ // setup the canvas
366
+ overlayContext.save();
367
+ overlayContext.globalAlpha = alpha*touchGamepadAlpha;
368
+ overlayContext.strokeStyle = '#fff';
369
+ overlayContext.lineWidth = 3;
370
+
371
+ // draw left analog stick
372
+ overlayContext.fillStyle = gamepadStick(0).length() > 0 ? '#fff' : '#000';
373
+ overlayContext.beginPath();
374
+ overlayContext.arc(touchGamepadSize, mainCanvasSize.y-touchGamepadSize, touchGamepadSize/2, 0,9);
375
+ overlayContext.fill();
376
+ overlayContext.stroke();
377
+
378
+ // draw right face buttons
379
+ const rightCenter = vec2(mainCanvasSize.x-touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
380
+ for (let i=4; i--;)
381
+ {
382
+ const pos = rightCenter.add((new Vector2).setAngle(i*PI/2, touchGamepadSize/2));
383
+ overlayContext.fillStyle = touchGamepadButtons[i] ? '#fff' : '#000';
384
+ overlayContext.beginPath();
385
+ overlayContext.arc(pos.x, pos.y, touchGamepadSize/4, 0,9);
386
+ overlayContext.fill();
387
+ overlayContext.stroke();
388
+ }
389
+
390
+ // set canvas back to normal
391
+ overlayContext.restore();
242
392
  }