littlejsengine 1.15.2 → 1.15.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/littlejs.d.ts +190 -63
- package/dist/littlejs.esm.js +1267 -605
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +1261 -604
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +1179 -552
- package/examples/electron/index.html +2 -2
- package/examples/index.html +4 -3
- package/examples/particles/index.html +22 -6
- package/examples/shorts/base.html +2 -1
- package/examples/shorts/box2dCar.js +1 -1
- package/examples/shorts/box2dPool.js +9 -10
- package/examples/shorts/empty.js +1 -1
- package/examples/shorts/input.js +30 -25
- package/examples/shorts/music.js +1 -0
- package/examples/shorts/musicPlayer.js +1 -0
- package/examples/shorts/sequencer.js +2 -0
- package/examples/shorts/sound.js +1 -0
- package/examples/shorts/tileLayer.js +2 -2
- package/examples/shorts/tiles.png +0 -0
- package/examples/shorts/timers.js +1 -0
- package/examples/shorts/uiSystem.js +26 -14
- package/examples/shorts/videoPlayer.js +2 -1
- package/examples/starter/index.html +3 -2
- package/examples/uiSystem/game.js +28 -15
- package/package.json +1 -1
- package/plugins/box2d.js +1 -1
- package/plugins/desktop.ini +2 -0
- package/plugins/pluginExport.js +2 -0
- package/plugins/uiSystem.js +503 -91
- package/src/engine.js +2 -169
- package/src/engineAudio.js +0 -1
- package/src/engineBuild.js +1 -0
- package/src/engineDebug.js +84 -54
- package/src/engineDraw.js +81 -40
- package/src/engineExport.js +4 -1
- package/src/engineInput.js +526 -381
- package/src/engineObject.js +27 -23
- package/src/engineSettings.js +11 -6
- package/src/engineSplash.js +173 -0
- package/src/engineTileLayer.js +3 -3
- package/src/engineUtilities.js +17 -1
package/src/engineInput.js
CHANGED
|
@@ -10,6 +10,85 @@
|
|
|
10
10
|
|
|
11
11
|
'use strict';
|
|
12
12
|
|
|
13
|
+
/** Mouse pos in world space
|
|
14
|
+
* @type {Vector2}
|
|
15
|
+
* @memberof Input */
|
|
16
|
+
let mousePos = vec2();
|
|
17
|
+
|
|
18
|
+
/** Mouse pos in screen space
|
|
19
|
+
* @type {Vector2}
|
|
20
|
+
* @memberof Input */
|
|
21
|
+
let mousePosScreen = vec2();
|
|
22
|
+
|
|
23
|
+
/** Mouse movement delta in world space
|
|
24
|
+
* @type {Vector2}
|
|
25
|
+
* @memberof Input */
|
|
26
|
+
let mouseDelta = vec2();
|
|
27
|
+
|
|
28
|
+
/** Mouse movement delta in screen space
|
|
29
|
+
* @type {Vector2}
|
|
30
|
+
* @memberof Input */
|
|
31
|
+
let mouseDeltaScreen = vec2();
|
|
32
|
+
|
|
33
|
+
/** Mouse wheel delta this frame
|
|
34
|
+
* @type {number}
|
|
35
|
+
* @memberof Input */
|
|
36
|
+
let mouseWheel = 0;
|
|
37
|
+
|
|
38
|
+
/** True if mouse was inside the document window, set to false when mouse leaves
|
|
39
|
+
* @type {boolean}
|
|
40
|
+
* @memberof Input */
|
|
41
|
+
let mouseInWindow = true;
|
|
42
|
+
|
|
43
|
+
/** Returns true if user is using gamepad (has more recently pressed a gamepad button)
|
|
44
|
+
* @type {boolean}
|
|
45
|
+
* @memberof Input */
|
|
46
|
+
let isUsingGamepad = false;
|
|
47
|
+
|
|
48
|
+
/** Prevents input continuing to the default browser handling (true by default)
|
|
49
|
+
* @type {boolean}
|
|
50
|
+
* @memberof Input */
|
|
51
|
+
let inputPreventDefault = true;
|
|
52
|
+
|
|
53
|
+
/** Primary gamepad index, automatically set to first gamepad with input
|
|
54
|
+
* @type {number}
|
|
55
|
+
* @memberof Input */
|
|
56
|
+
let gamepadPrimary = 0;
|
|
57
|
+
|
|
58
|
+
/** Prevents input continuing to the default browser handling
|
|
59
|
+
* This is useful to disable for html menus so the browser can handle input normally
|
|
60
|
+
* @param {boolean} preventDefault
|
|
61
|
+
* @memberof Input */
|
|
62
|
+
function setInputPreventDefault(preventDefault) { inputPreventDefault = preventDefault; }
|
|
63
|
+
|
|
64
|
+
/** Clears an input key state
|
|
65
|
+
* @param {string|number} key
|
|
66
|
+
* @param {number} [device]
|
|
67
|
+
* @param {boolean} [clearDown=true]
|
|
68
|
+
* @param {boolean} [clearPressed=true]
|
|
69
|
+
* @param {boolean} [clearReleased=true]
|
|
70
|
+
* @memberof Input */
|
|
71
|
+
function inputClearKey(key, device=0, clearDown=true, clearPressed=true, clearReleased=true)
|
|
72
|
+
{
|
|
73
|
+
if (!inputData[device])
|
|
74
|
+
return;
|
|
75
|
+
inputData[device][key] &= ~((clearDown?1:0)|(clearPressed?2:0)|(clearReleased?4:0));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Clears all input
|
|
79
|
+
* @memberof Input */
|
|
80
|
+
function inputClear()
|
|
81
|
+
{
|
|
82
|
+
inputData.length = 0;
|
|
83
|
+
inputData[0] = [];
|
|
84
|
+
touchGamepadButtons.length = 0;
|
|
85
|
+
touchGamepadSticks.length = 0;
|
|
86
|
+
gamepadStickData.length = 0;
|
|
87
|
+
gamepadDpadData.length = 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
91
|
+
|
|
13
92
|
/** Returns true if device key is down
|
|
14
93
|
* @param {string|number} key
|
|
15
94
|
* @param {number} [device]
|
|
@@ -17,9 +96,9 @@
|
|
|
17
96
|
* @memberof Input */
|
|
18
97
|
function keyIsDown(key, device=0)
|
|
19
98
|
{
|
|
20
|
-
ASSERT(key
|
|
99
|
+
ASSERT(isString(key), 'key must be a number or string');
|
|
21
100
|
ASSERT(device > 0 || typeof key !== 'number' || key < 3, 'use code string for keyboard');
|
|
22
|
-
return
|
|
101
|
+
return !!(inputData[device]?.[key] & 1);
|
|
23
102
|
}
|
|
24
103
|
|
|
25
104
|
/** Returns true if device key was pressed this frame
|
|
@@ -29,9 +108,9 @@ function keyIsDown(key, device=0)
|
|
|
29
108
|
* @memberof Input */
|
|
30
109
|
function keyWasPressed(key, device=0)
|
|
31
110
|
{
|
|
32
|
-
ASSERT(key
|
|
111
|
+
ASSERT(isString(key), 'key must be a number or string');
|
|
33
112
|
ASSERT(device > 0 || typeof key !== 'number' || key < 3, 'use code string for keyboard');
|
|
34
|
-
return
|
|
113
|
+
return !!(inputData[device]?.[key] & 2);
|
|
35
114
|
}
|
|
36
115
|
|
|
37
116
|
/** Returns true if device key was released this frame
|
|
@@ -41,172 +120,182 @@ function keyWasPressed(key, device=0)
|
|
|
41
120
|
* @memberof Input */
|
|
42
121
|
function keyWasReleased(key, device=0)
|
|
43
122
|
{
|
|
44
|
-
ASSERT(key
|
|
123
|
+
ASSERT(isString(key), 'key must be a number or string');
|
|
45
124
|
ASSERT(device > 0 || typeof key !== 'number' || key < 3, 'use code string for keyboard');
|
|
46
|
-
return
|
|
125
|
+
return !!(inputData[device]?.[key] & 4);
|
|
47
126
|
}
|
|
48
127
|
|
|
49
128
|
/** Returns input vector from arrow keys or WASD if enabled
|
|
129
|
+
* @param {string} [up]
|
|
130
|
+
* @param {string} [down]
|
|
131
|
+
* @param {string} [left]
|
|
132
|
+
* @param {string} [right]
|
|
50
133
|
* @return {Vector2}
|
|
51
134
|
* @memberof Input */
|
|
52
135
|
function keyDirection(up='ArrowUp', down='ArrowDown', left='ArrowLeft', right='ArrowRight')
|
|
53
136
|
{
|
|
137
|
+
ASSERT(isString(up), 'up key must be a string');
|
|
138
|
+
ASSERT(isString(down), 'down key must be a string');
|
|
139
|
+
ASSERT(isString(left), 'left key must be a string');
|
|
140
|
+
ASSERT(isString(right), 'right key must be a string');
|
|
54
141
|
const k = (key)=> keyIsDown(key) ? 1 : 0;
|
|
55
142
|
return vec2(k(right) - k(left), k(up) - k(down));
|
|
56
143
|
}
|
|
57
144
|
|
|
58
|
-
/** Clears all input
|
|
59
|
-
* @memberof Input */
|
|
60
|
-
function inputClear() { inputData = [[]]; touchGamepadButtons = []; }
|
|
61
|
-
|
|
62
|
-
/** Clears an input key state
|
|
63
|
-
* @param {string|number} key
|
|
64
|
-
* @param {number} [device]
|
|
65
|
-
* @param {boolean} [clearDown=true]
|
|
66
|
-
* @param {boolean} [clearPressed=true]
|
|
67
|
-
* @param {boolean} [clearReleased=true]
|
|
68
|
-
* @memberof Input */
|
|
69
|
-
function inputClearKey(key, device=0, clearDown=true, clearPressed=true, clearReleased=true)
|
|
70
|
-
{
|
|
71
|
-
if (!inputData[device])
|
|
72
|
-
return;
|
|
73
|
-
inputData[device][key] &= ~((clearDown?1:0)|(clearPressed?2:0)|(clearReleased?4:0));
|
|
74
|
-
}
|
|
75
|
-
|
|
76
145
|
/** Returns true if mouse button is down
|
|
77
146
|
* @function
|
|
78
147
|
* @param {number} button
|
|
79
148
|
* @return {boolean}
|
|
80
149
|
* @memberof Input */
|
|
81
|
-
function mouseIsDown(button)
|
|
150
|
+
function mouseIsDown(button)
|
|
151
|
+
{
|
|
152
|
+
ASSERT(isNumber(button), 'mouse button must be a number');
|
|
153
|
+
return keyIsDown(button);
|
|
154
|
+
}
|
|
82
155
|
|
|
83
156
|
/** Returns true if mouse button was pressed
|
|
84
157
|
* @function
|
|
85
158
|
* @param {number} button
|
|
86
159
|
* @return {boolean}
|
|
87
160
|
* @memberof Input */
|
|
88
|
-
function mouseWasPressed(button)
|
|
161
|
+
function mouseWasPressed(button)
|
|
162
|
+
{
|
|
163
|
+
ASSERT(isNumber(button), 'mouse button must be a number');
|
|
164
|
+
return keyWasPressed(button);
|
|
165
|
+
}
|
|
89
166
|
|
|
90
167
|
/** Returns true if mouse button was released
|
|
91
168
|
* @function
|
|
92
169
|
* @param {number} button
|
|
93
170
|
* @return {boolean}
|
|
94
171
|
* @memberof Input */
|
|
95
|
-
function mouseWasReleased(button)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
let mousePos = vec2();
|
|
101
|
-
|
|
102
|
-
/** Mouse pos in screen space
|
|
103
|
-
* @type {Vector2}
|
|
104
|
-
* @memberof Input */
|
|
105
|
-
let mousePosScreen = vec2();
|
|
106
|
-
|
|
107
|
-
/** Mouse movement delta in world space
|
|
108
|
-
* @type {Vector2}
|
|
109
|
-
* @memberof Input */
|
|
110
|
-
let mouseDelta = vec2();
|
|
111
|
-
|
|
112
|
-
/** Mouse movement delta in screen space
|
|
113
|
-
* @type {Vector2}
|
|
114
|
-
* @memberof Input */
|
|
115
|
-
let mouseDeltaScreen = vec2();
|
|
116
|
-
|
|
117
|
-
/** Mouse wheel delta this frame
|
|
118
|
-
* @type {number}
|
|
119
|
-
* @memberof Input */
|
|
120
|
-
let mouseWheel = 0;
|
|
121
|
-
|
|
122
|
-
/** True if mouse was inside the document window, set to false when mouse leaves
|
|
123
|
-
* @type {boolean}
|
|
124
|
-
* @memberof Input */
|
|
125
|
-
let mouseInWindow = true;
|
|
126
|
-
|
|
127
|
-
/** Returns true if user is using gamepad (has more recently pressed a gamepad button)
|
|
128
|
-
* @type {boolean}
|
|
129
|
-
* @memberof Input */
|
|
130
|
-
let isUsingGamepad = false;
|
|
131
|
-
|
|
132
|
-
/** Prevents input continuing to the default browser handling (true by default)
|
|
133
|
-
* @type {boolean}
|
|
134
|
-
* @memberof Input */
|
|
135
|
-
let inputPreventDefault = true;
|
|
136
|
-
|
|
137
|
-
/** Prevents input continuing to the default browser handling
|
|
138
|
-
* This is useful to disable for html menus so the browser can handle input normally
|
|
139
|
-
* @param {boolean} preventDefault
|
|
140
|
-
* @memberof Input */
|
|
141
|
-
function setInputPreventDefault(preventDefault) { inputPreventDefault = preventDefault; }
|
|
172
|
+
function mouseWasReleased(button)
|
|
173
|
+
{
|
|
174
|
+
ASSERT(isNumber(button), 'mouse button must be a number');
|
|
175
|
+
return keyWasReleased(button);
|
|
176
|
+
}
|
|
142
177
|
|
|
143
178
|
/** Returns true if gamepad button is down
|
|
144
179
|
* @param {number} button
|
|
145
180
|
* @param {number} [gamepad]
|
|
146
181
|
* @return {boolean}
|
|
147
182
|
* @memberof Input */
|
|
148
|
-
function gamepadIsDown(button, gamepad=
|
|
149
|
-
{
|
|
183
|
+
function gamepadIsDown(button, gamepad=gamepadPrimary)
|
|
184
|
+
{
|
|
185
|
+
ASSERT(isNumber(button), 'button must be a number');
|
|
186
|
+
ASSERT(isNumber(gamepad), 'gamepad must be a number');
|
|
187
|
+
return keyIsDown(button, gamepad+1);
|
|
188
|
+
}
|
|
150
189
|
|
|
151
190
|
/** Returns true if gamepad button was pressed
|
|
152
191
|
* @param {number} button
|
|
153
192
|
* @param {number} [gamepad]
|
|
154
193
|
* @return {boolean}
|
|
155
194
|
* @memberof Input */
|
|
156
|
-
function gamepadWasPressed(button, gamepad=
|
|
157
|
-
{
|
|
195
|
+
function gamepadWasPressed(button, gamepad=gamepadPrimary)
|
|
196
|
+
{
|
|
197
|
+
ASSERT(isNumber(button), 'button must be a number');
|
|
198
|
+
ASSERT(isNumber(gamepad), 'gamepad must be a number');
|
|
199
|
+
return keyWasPressed(button, gamepad+1);
|
|
200
|
+
}
|
|
158
201
|
|
|
159
202
|
/** Returns true if gamepad button was released
|
|
160
203
|
* @param {number} button
|
|
161
204
|
* @param {number} [gamepad]
|
|
162
205
|
* @return {boolean}
|
|
163
206
|
* @memberof Input */
|
|
164
|
-
function gamepadWasReleased(button, gamepad=
|
|
165
|
-
{
|
|
207
|
+
function gamepadWasReleased(button, gamepad=gamepadPrimary)
|
|
208
|
+
{
|
|
209
|
+
ASSERT(isNumber(button), 'button must be a number');
|
|
210
|
+
ASSERT(isNumber(gamepad), 'gamepad must be a number');
|
|
211
|
+
return keyWasReleased(button, gamepad+1);
|
|
212
|
+
}
|
|
166
213
|
|
|
167
214
|
/** Returns gamepad stick value
|
|
168
215
|
* @param {number} stick
|
|
169
216
|
* @param {number} [gamepad]
|
|
170
217
|
* @return {Vector2}
|
|
171
218
|
* @memberof Input */
|
|
172
|
-
function gamepadStick(stick, gamepad=
|
|
173
|
-
{
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
219
|
+
function gamepadStick(stick, gamepad=gamepadPrimary)
|
|
220
|
+
{
|
|
221
|
+
ASSERT(isNumber(stick), 'stick must be a number');
|
|
222
|
+
ASSERT(isNumber(gamepad), 'gamepad must be a number');
|
|
223
|
+
return gamepadStickData[gamepad]?.[stick] ?? vec2();
|
|
224
|
+
}
|
|
177
225
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
226
|
+
/** Returns gamepad dpad value
|
|
227
|
+
* @param {number} [gamepad]
|
|
228
|
+
* @return {Vector2}
|
|
229
|
+
* @memberof Input */
|
|
230
|
+
function gamepadDpad(gamepad=gamepadPrimary)
|
|
231
|
+
{
|
|
232
|
+
ASSERT(isNumber(gamepad), 'gamepad must be a number');
|
|
233
|
+
return gamepadDpadData[gamepad] ?? vec2();
|
|
234
|
+
}
|
|
181
235
|
|
|
182
|
-
|
|
236
|
+
/** Returns true if passed in gamepad is connected
|
|
237
|
+
* @param {number} [gamepad]
|
|
238
|
+
* @return {boolean}
|
|
239
|
+
* @memberof Input */
|
|
240
|
+
function gamepadConnected(gamepad=gamepadPrimary)
|
|
183
241
|
{
|
|
184
|
-
|
|
242
|
+
ASSERT(isNumber(gamepad), 'gamepad must be a number');
|
|
243
|
+
return !!inputData[gamepad+1];
|
|
244
|
+
}
|
|
185
245
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
246
|
+
/** True if a touch device has been detected
|
|
247
|
+
* @memberof Input */
|
|
248
|
+
const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
|
|
189
249
|
|
|
190
|
-
|
|
191
|
-
mousePos = screenToWorld(mousePosScreen);
|
|
192
|
-
mouseDelta = screenToWorldDelta(mouseDeltaScreen);
|
|
250
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
193
251
|
|
|
194
|
-
|
|
195
|
-
|
|
252
|
+
/** Pulse the vibration hardware if it exists
|
|
253
|
+
* @param {number|Array} [pattern] - single value in ms or vibration interval array
|
|
254
|
+
* @memberof Input */
|
|
255
|
+
function vibrate(pattern=100)
|
|
256
|
+
{
|
|
257
|
+
ASSERT(isNumber(pattern) || isArray(pattern), 'pattern must be a number or array');
|
|
258
|
+
vibrateEnable && !headlessMode && navigator && navigator.vibrate && navigator.vibrate(pattern);
|
|
196
259
|
}
|
|
197
260
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
261
|
+
/** Cancel any ongoing vibration
|
|
262
|
+
* @memberof Input */
|
|
263
|
+
function vibrateStop() { vibrate(0); }
|
|
201
264
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
265
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
266
|
+
// Pointer Lock
|
|
267
|
+
|
|
268
|
+
/** Request to lock the pointer, does not work on touch devices
|
|
269
|
+
* @memberof Input */
|
|
270
|
+
function pointerLockRequest()
|
|
271
|
+
{ !isTouchDevice && mainCanvas.requestPointerLock?.(); }
|
|
272
|
+
|
|
273
|
+
/** Request to unlock the pointer
|
|
274
|
+
* @memberof Input */
|
|
275
|
+
function pointerLockExit()
|
|
276
|
+
{ document.exitPointerLock?.(); }
|
|
277
|
+
|
|
278
|
+
/** Check if pointer is locked (true if locked)
|
|
279
|
+
* @return {boolean}
|
|
280
|
+
* @memberof Input */
|
|
281
|
+
function pointerLockIsActive()
|
|
282
|
+
{ return document.pointerLockElement === mainCanvas; }
|
|
283
|
+
|
|
284
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
285
|
+
// Input variables used by engine
|
|
286
|
+
|
|
287
|
+
// input uses bit field for each key: 1=isDown, 2=wasPressed, 4=wasReleased
|
|
288
|
+
// mouse and keyboard stored in device 0, gamepads stored in devices > 0
|
|
289
|
+
const inputData = [[]];
|
|
290
|
+
|
|
291
|
+
// gamepad internal variables
|
|
292
|
+
const gamepadStickData = [], gamepadDpadData = [], gamepadHadInput = [];
|
|
293
|
+
|
|
294
|
+
// touch gamepad internal variables
|
|
295
|
+
const touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadSticks = [];
|
|
296
|
+
|
|
297
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
298
|
+
// Input system functions used by engine
|
|
210
299
|
|
|
211
300
|
function inputInit()
|
|
212
301
|
{
|
|
@@ -236,6 +325,18 @@ function inputInit()
|
|
|
236
325
|
if (inputWASDEmulateDirection)
|
|
237
326
|
inputData[0][remapKey(e.code)] = 3;
|
|
238
327
|
}
|
|
328
|
+
|
|
329
|
+
// prevent arrow key from moving the page
|
|
330
|
+
const preventDefaultKeys =
|
|
331
|
+
[
|
|
332
|
+
'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', // scrolling
|
|
333
|
+
'Space', // page down scroll
|
|
334
|
+
'Tab', // focus navigation
|
|
335
|
+
'Backspace', // browser back
|
|
336
|
+
];
|
|
337
|
+
if (preventDefaultKeys.includes(e.code))
|
|
338
|
+
if (inputPreventDefault && document.hasFocus() && e.cancelable)
|
|
339
|
+
e.preventDefault();
|
|
239
340
|
}
|
|
240
341
|
function onKeyUp(e)
|
|
241
342
|
{
|
|
@@ -267,7 +368,9 @@ function inputInit()
|
|
|
267
368
|
const mousePosScreenLast = mousePosScreen;
|
|
268
369
|
mousePosScreen = mouseEventToScreen(vec2(e.x,e.y));
|
|
269
370
|
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
|
|
270
|
-
|
|
371
|
+
|
|
372
|
+
if (inputPreventDefault && document.hasFocus() && e.cancelable)
|
|
373
|
+
e.preventDefault();
|
|
271
374
|
}
|
|
272
375
|
function onMouseUp(e)
|
|
273
376
|
{
|
|
@@ -291,341 +394,383 @@ function inputInit()
|
|
|
291
394
|
function onMouseWheel(e) { mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY); }
|
|
292
395
|
function onContextMenu(e) { e.preventDefault(); } // prevent right click menu
|
|
293
396
|
function onBlur() { inputClear(); } // reset input when focus is lost
|
|
294
|
-
}
|
|
295
397
|
|
|
296
|
-
//
|
|
297
|
-
function
|
|
298
|
-
{
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
398
|
+
// enable touch input mouse passthrough
|
|
399
|
+
function touchInputInit()
|
|
400
|
+
{
|
|
401
|
+
// add non passive touch event listeners
|
|
402
|
+
document.addEventListener('touchstart', (e) => handleTouch(e), { passive: false });
|
|
403
|
+
document.addEventListener('touchmove', (e) => handleTouch(e), { passive: false });
|
|
404
|
+
document.addEventListener('touchend', (e) => handleTouch(e), { passive: false });
|
|
405
|
+
|
|
406
|
+
// handle all touch events the same way
|
|
407
|
+
let wasTouching;
|
|
408
|
+
function handleTouch(e)
|
|
409
|
+
{
|
|
410
|
+
if (!touchInputEnable)
|
|
411
|
+
return;
|
|
304
412
|
|
|
305
|
-
|
|
306
|
-
|
|
413
|
+
// route touch to gamepad
|
|
414
|
+
if (touchGamepadEnable)
|
|
415
|
+
handleTouchGamepad(e);
|
|
307
416
|
|
|
308
|
-
//
|
|
309
|
-
|
|
417
|
+
// fix stalled audio requiring user interaction
|
|
418
|
+
if (soundEnable && !headlessMode && audioContext && !audioIsRunning())
|
|
419
|
+
audioContext.resume();
|
|
310
420
|
|
|
311
|
-
//
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
421
|
+
// check if touching and pass to mouse events
|
|
422
|
+
const touching = e.touches.length;
|
|
423
|
+
const button = 0; // all touches are left mouse button
|
|
424
|
+
if (touching)
|
|
425
|
+
{
|
|
426
|
+
// set event pos and pass it along
|
|
427
|
+
const pos = vec2(e.touches[0].clientX, e.touches[0].clientY);
|
|
428
|
+
const mousePosScreenLast = mousePosScreen;
|
|
429
|
+
mousePosScreen = mouseEventToScreen(pos);
|
|
430
|
+
if (wasTouching)
|
|
431
|
+
{
|
|
432
|
+
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
|
|
433
|
+
isUsingGamepad = touchGamepadEnable;
|
|
434
|
+
}
|
|
435
|
+
else
|
|
436
|
+
inputData[0][button] = 3;
|
|
437
|
+
}
|
|
438
|
+
else if (wasTouching)
|
|
439
|
+
inputData[0][button] = inputData[0][button] & 2 | 4;
|
|
322
440
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
{
|
|
326
|
-
if (!touchGamepadTimer.isSet())
|
|
327
|
-
return;
|
|
441
|
+
// set was touching
|
|
442
|
+
wasTouching = touching;
|
|
328
443
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
{
|
|
336
|
-
// convert to 8 way dpad
|
|
337
|
-
sticks[0].x = round(touchGamepadStick.x);
|
|
338
|
-
sticks[0].y = -round(touchGamepadStick.y);
|
|
339
|
-
sticks[0] = sticks[0].clampLength();
|
|
444
|
+
// prevent default handling like copy, magnifier lens, and scrolling
|
|
445
|
+
if (inputPreventDefault && document.hasFocus() && e.cancelable)
|
|
446
|
+
e.preventDefault();
|
|
447
|
+
|
|
448
|
+
// must return true so the document will get focus
|
|
449
|
+
return true;
|
|
340
450
|
}
|
|
341
451
|
|
|
342
|
-
//
|
|
343
|
-
|
|
344
|
-
for (let i=10; i--;)
|
|
452
|
+
// special handling for virtual gamepad mode
|
|
453
|
+
function handleTouchGamepad(e)
|
|
345
454
|
{
|
|
346
|
-
|
|
347
|
-
|
|
455
|
+
// clear touch gamepad input
|
|
456
|
+
touchGamepadSticks.length = 0;
|
|
457
|
+
touchGamepadSticks[0] = vec2();
|
|
458
|
+
touchGamepadSticks[1] = vec2();
|
|
459
|
+
touchGamepadButtons.length = 0;
|
|
460
|
+
isUsingGamepad = true;
|
|
461
|
+
|
|
462
|
+
const touching = e.touches.length;
|
|
463
|
+
if (touching)
|
|
464
|
+
{
|
|
465
|
+
touchGamepadTimer.set();
|
|
466
|
+
if (touchGamepadCenterButton && !wasTouching && paused)
|
|
467
|
+
{
|
|
468
|
+
// touch anywhere to press start when paused
|
|
469
|
+
touchGamepadButtons[9] = 1;
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// don't process touch gamepad if paused
|
|
475
|
+
if (paused)
|
|
476
|
+
return;
|
|
477
|
+
|
|
478
|
+
// get center of left and right sides
|
|
479
|
+
const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
480
|
+
const buttonCenter = touchGamepadButtonCenter();
|
|
481
|
+
const startCenter = mainCanvasSize.scale(.5);
|
|
482
|
+
|
|
483
|
+
// check each touch point
|
|
484
|
+
for (const touch of e.touches)
|
|
485
|
+
{
|
|
486
|
+
const touchPos = mouseEventToScreen(vec2(touch.clientX, touch.clientY));
|
|
487
|
+
if (stickCenter.distance(touchPos) < touchGamepadSize)
|
|
488
|
+
{
|
|
489
|
+
// virtual analog stick
|
|
490
|
+
const delta = touchPos.subtract(stickCenter);
|
|
491
|
+
touchGamepadSticks[0] = delta.scale(2/touchGamepadSize).clampLength();
|
|
492
|
+
}
|
|
493
|
+
else if (buttonCenter.distance(touchPos) < touchGamepadSize)
|
|
494
|
+
{
|
|
495
|
+
if (touchGamepadButtonCount === 1)
|
|
496
|
+
{
|
|
497
|
+
// virtual right analog stick
|
|
498
|
+
const delta = touchPos.subtract(buttonCenter);
|
|
499
|
+
touchGamepadSticks[1] = delta.scale(2/touchGamepadSize).clampLength();
|
|
500
|
+
}
|
|
501
|
+
// virtual face buttons
|
|
502
|
+
let button = buttonCenter.subtract(touchPos).direction();
|
|
503
|
+
button = mod(button+2, 4);
|
|
504
|
+
if (touchGamepadButtonCount === 1)
|
|
505
|
+
button = 0;
|
|
506
|
+
else if (touchGamepadButtonCount === 2)
|
|
507
|
+
{
|
|
508
|
+
const delta = buttonCenter.subtract(touchPos);
|
|
509
|
+
button = -delta.x < delta.y ? 1 : 0;
|
|
510
|
+
}
|
|
511
|
+
// fix button locations (swap 2 and 3 to match gamepad layout)
|
|
512
|
+
button = button === 3 ? 2 : button === 2 ? 3 : button;
|
|
513
|
+
if (button < touchGamepadButtonCount)
|
|
514
|
+
touchGamepadButtons[button] = 1;
|
|
515
|
+
}
|
|
516
|
+
else if (touchGamepadCenterButton &&
|
|
517
|
+
startCenter.distance(touchPos) < touchGamepadSize)
|
|
518
|
+
{
|
|
519
|
+
// virtual start button in center
|
|
520
|
+
touchGamepadButtons[9] = 1;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
348
523
|
}
|
|
524
|
+
}
|
|
349
525
|
|
|
350
|
-
|
|
351
|
-
|
|
526
|
+
// convert a mouse or touch event position to screen space
|
|
527
|
+
function mouseEventToScreen(mousePos)
|
|
528
|
+
{
|
|
529
|
+
const rect = mainCanvas.getBoundingClientRect();
|
|
530
|
+
const px = percent(mousePos.x, rect.left, rect.right);
|
|
531
|
+
const py = percent(mousePos.y, rect.top, rect.bottom);
|
|
532
|
+
return vec2(px*mainCanvas.width, py*mainCanvas.height);
|
|
352
533
|
}
|
|
534
|
+
}
|
|
353
535
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
536
|
+
function inputUpdate()
|
|
537
|
+
{
|
|
538
|
+
if (headlessMode) return;
|
|
357
539
|
|
|
358
|
-
//
|
|
359
|
-
if (!
|
|
360
|
-
|
|
540
|
+
// clear input when lost focus (prevent stuck keys)
|
|
541
|
+
if (!(touchInputEnable && isTouchDevice) && !document.hasFocus())
|
|
542
|
+
inputClear();
|
|
543
|
+
|
|
544
|
+
// update mouse world space position and delta
|
|
545
|
+
mousePos = screenToWorld(mousePosScreen);
|
|
546
|
+
mouseDelta = screenToWorldDelta(mouseDeltaScreen);
|
|
361
547
|
|
|
362
|
-
//
|
|
363
|
-
|
|
364
|
-
|
|
548
|
+
// update gamepads if enabled
|
|
549
|
+
gamepadsUpdate();
|
|
550
|
+
|
|
551
|
+
// gamepads are updated by engine every frame automatically
|
|
552
|
+
function gamepadsUpdate()
|
|
365
553
|
{
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
554
|
+
const applyDeadZones = (v)=>
|
|
555
|
+
{
|
|
556
|
+
const min=.3, max=.8;
|
|
557
|
+
const deadZone = (v)=>
|
|
558
|
+
v > min ? percent(v, min, max) :
|
|
559
|
+
v < -min ? -percent(-v, min, max) : 0;
|
|
560
|
+
return vec2(deadZone(v.x), deadZone(-v.y)).clampLength();
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// update touch gamepad if enabled
|
|
564
|
+
if (touchGamepadEnable && isTouchDevice)
|
|
565
|
+
{
|
|
566
|
+
if (!touchGamepadTimer.isSet())
|
|
567
|
+
return;
|
|
568
|
+
|
|
569
|
+
// read virtual analog stick
|
|
570
|
+
gamepadPrimary = 0; // touch gamepad uses index 0
|
|
571
|
+
const sticks = gamepadStickData[0] ?? (gamepadStickData[0] = []);
|
|
572
|
+
const dpad = gamepadDpadData[0] ?? (gamepadDpadData[0] = vec2());
|
|
573
|
+
sticks[0] = vec2();
|
|
574
|
+
dpad.set();
|
|
575
|
+
const leftTouchStick = touchGamepadSticks[0] ?? vec2();
|
|
576
|
+
if (touchGamepadAnalog)
|
|
577
|
+
sticks[0] = applyDeadZones(leftTouchStick);
|
|
578
|
+
else if (leftTouchStick.lengthSquared() > .3)
|
|
579
|
+
{
|
|
580
|
+
// convert to 8 way dpad
|
|
581
|
+
const x = clamp(round(leftTouchStick.x), -1, 1);
|
|
582
|
+
const y = clamp(round(leftTouchStick.y), -1, 1);
|
|
583
|
+
dpad.set(x, -y);
|
|
584
|
+
sticks[0] = dpad.clampLength(); // clamp to circle
|
|
585
|
+
}
|
|
586
|
+
const rightTouchStick = touchGamepadSticks[1] ?? vec2();
|
|
587
|
+
if (touchGamepadButtonCount === 1)
|
|
588
|
+
sticks[1] = applyDeadZones(rightTouchStick);
|
|
589
|
+
|
|
590
|
+
// read virtual gamepad buttons
|
|
591
|
+
const data = inputData[1] ?? (inputData[1] = []);
|
|
592
|
+
for (let i=10; i--;)
|
|
593
|
+
{
|
|
594
|
+
const wasDown = gamepadIsDown(i,0);
|
|
595
|
+
data[i] = touchGamepadButtons[i] ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
|
|
596
|
+
}
|
|
370
597
|
|
|
371
|
-
|
|
598
|
+
// disable normal gamepads when touch gamepad is active
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// return if gamepads are disabled or not supported
|
|
603
|
+
if (!gamepadsEnable || !navigator || !navigator.getGamepads)
|
|
604
|
+
return;
|
|
605
|
+
|
|
606
|
+
// only poll gamepads when focused or in debug mode
|
|
607
|
+
if (!debug && !document.hasFocus())
|
|
608
|
+
return;
|
|
609
|
+
|
|
610
|
+
// poll gamepads
|
|
611
|
+
const maxGamepads = 8;
|
|
612
|
+
const gamepads = navigator.getGamepads();
|
|
613
|
+
const gamepadCount = min(maxGamepads, gamepads.length)
|
|
614
|
+
for (let i=0; i<gamepadCount; ++i)
|
|
372
615
|
{
|
|
616
|
+
// get or create gamepad data
|
|
617
|
+
const gamepad = gamepads[i];
|
|
618
|
+
if (!gamepad)
|
|
619
|
+
{
|
|
620
|
+
// clear gamepad data if not connected
|
|
621
|
+
inputData[i+1] = undefined;
|
|
622
|
+
gamepadStickData[i] = undefined;
|
|
623
|
+
gamepadDpadData[i] = undefined;
|
|
624
|
+
gamepadHadInput[i] = undefined;
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const data = inputData[i+1] ?? (inputData[i+1] = []);
|
|
629
|
+
const sticks = gamepadStickData[i] ?? (gamepadStickData[i] = []);
|
|
630
|
+
const dpad = gamepadDpadData[i] ?? (gamepadDpadData[i] = vec2());
|
|
631
|
+
|
|
373
632
|
// read analog sticks
|
|
374
633
|
for (let j = 0; j < gamepad.axes.length-1; j+=2)
|
|
375
634
|
sticks[j>>1] = applyDeadZones(vec2(gamepad.axes[j],gamepad.axes[j+1]));
|
|
376
635
|
|
|
377
636
|
// read buttons
|
|
637
|
+
let hadInput = false;
|
|
378
638
|
for (let j = gamepad.buttons.length; j--;)
|
|
379
639
|
{
|
|
380
640
|
const button = gamepad.buttons[j];
|
|
381
641
|
const wasDown = gamepadIsDown(j,i);
|
|
382
642
|
data[j] = button.pressed ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
643
|
+
|
|
644
|
+
// check for any input on this gamepad, analog must be full press
|
|
645
|
+
if (button.pressed)
|
|
646
|
+
if (!button.value || button.value > .9)
|
|
647
|
+
hadInput = true;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// set new primary gamepad if current is not connected
|
|
651
|
+
if (hadInput)
|
|
652
|
+
{
|
|
653
|
+
gamepadHadInput[i] = true;
|
|
654
|
+
if (!gamepadHadInput[gamepadPrimary])
|
|
655
|
+
gamepadPrimary = i;
|
|
656
|
+
isUsingGamepad ||= (gamepadPrimary === i);
|
|
386
657
|
}
|
|
387
658
|
|
|
388
|
-
if (
|
|
659
|
+
if (gamepad.mapping === 'standard')
|
|
389
660
|
{
|
|
390
|
-
//
|
|
391
|
-
|
|
661
|
+
// get dpad buttons (standard mapping)
|
|
662
|
+
dpad.set(
|
|
392
663
|
(gamepadIsDown(15,i)&&1) - (gamepadIsDown(14,i)&&1),
|
|
393
664
|
(gamepadIsDown(12,i)&&1) - (gamepadIsDown(13,i)&&1));
|
|
394
|
-
|
|
395
|
-
|
|
665
|
+
}
|
|
666
|
+
else if (gamepad.axes && gamepad.axes.length >= 2)
|
|
667
|
+
{
|
|
668
|
+
// digital style dpad from axes
|
|
669
|
+
const x = clamp(round(gamepad.axes[0]), -1, 1);
|
|
670
|
+
const y = clamp(round(gamepad.axes[1]), -1, 1);
|
|
671
|
+
dpad.set(x, -y);
|
|
396
672
|
}
|
|
397
673
|
|
|
398
|
-
//
|
|
399
|
-
|
|
674
|
+
// copy dpad to left analog stick when pressed
|
|
675
|
+
if (gamepadDirectionEmulateStick && !dpad.isZero())
|
|
676
|
+
sticks[0] = dpad.clampLength();
|
|
400
677
|
}
|
|
678
|
+
|
|
679
|
+
// disable touch gamepad if using real gamepad
|
|
680
|
+
touchGamepadEnable && isUsingGamepad && touchGamepadTimer.unset();
|
|
401
681
|
}
|
|
402
682
|
}
|
|
403
683
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
/** Pulse the vibration hardware if it exists
|
|
407
|
-
* @param {number|Array} [pattern] - single value in ms or vibration interval array
|
|
408
|
-
* @memberof Input */
|
|
409
|
-
function vibrate(pattern=100)
|
|
410
|
-
{ vibrateEnable && !headlessMode && navigator && navigator.vibrate && navigator.vibrate(pattern); }
|
|
411
|
-
|
|
412
|
-
/** Cancel any ongoing vibration
|
|
413
|
-
* @memberof Input */
|
|
414
|
-
function vibrateStop() { vibrate(0); }
|
|
415
|
-
|
|
416
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
417
|
-
// Touch input & virtual on screen gamepad
|
|
418
|
-
|
|
419
|
-
/** True if a touch device has been detected
|
|
420
|
-
* @memberof Input */
|
|
421
|
-
const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
|
|
422
|
-
|
|
423
|
-
// touch gamepad internal variables
|
|
424
|
-
let touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadStick = vec2();
|
|
425
|
-
|
|
426
|
-
function touchGamepadButtonCenter()
|
|
684
|
+
function inputUpdatePost()
|
|
427
685
|
{
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
686
|
+
if (headlessMode) return;
|
|
687
|
+
|
|
688
|
+
// clear input to prepare for next frame
|
|
689
|
+
for (const deviceInputData of inputData)
|
|
690
|
+
for (const i in deviceInputData)
|
|
691
|
+
deviceInputData[i] &= 1;
|
|
692
|
+
mouseWheel = 0;
|
|
693
|
+
mouseDelta = vec2();
|
|
694
|
+
mouseDeltaScreen = vec2();
|
|
433
695
|
}
|
|
434
696
|
|
|
435
|
-
|
|
436
|
-
function touchInputInit()
|
|
697
|
+
function inputRender()
|
|
437
698
|
{
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
document.addEventListener('touchend', (e) => handleTouch(e), { passive: false });
|
|
442
|
-
|
|
443
|
-
// handle all touch events the same way
|
|
444
|
-
let wasTouching;
|
|
445
|
-
function handleTouch(e)
|
|
699
|
+
touchGamepadRender();
|
|
700
|
+
|
|
701
|
+
function touchGamepadRender()
|
|
446
702
|
{
|
|
447
|
-
if (!touchInputEnable)
|
|
703
|
+
if (!touchInputEnable || !isTouchDevice || headlessMode) return;
|
|
704
|
+
if (!touchGamepadEnable || !touchGamepadTimer.isSet())
|
|
448
705
|
return;
|
|
449
706
|
|
|
450
|
-
//
|
|
451
|
-
|
|
452
|
-
|
|
707
|
+
// fade off when not touching or paused
|
|
708
|
+
const alpha = percent(touchGamepadTimer.get(), 4, 3);
|
|
709
|
+
if (!alpha || paused)
|
|
710
|
+
return;
|
|
453
711
|
|
|
454
|
-
//
|
|
455
|
-
|
|
456
|
-
|
|
712
|
+
// setup the canvas
|
|
713
|
+
const context = overlayContext;
|
|
714
|
+
context.save();
|
|
715
|
+
context.globalAlpha = alpha*touchGamepadAlpha;
|
|
716
|
+
context.strokeStyle = '#fff';
|
|
717
|
+
context.lineWidth = 3;
|
|
457
718
|
|
|
458
|
-
//
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
|
|
719
|
+
// draw left analog stick
|
|
720
|
+
const leftTouchStick = touchGamepadSticks[0] ?? vec2();
|
|
721
|
+
context.fillStyle = leftTouchStick.lengthSquared() > 0 ? '#fff' : '#000';
|
|
722
|
+
context.beginPath();
|
|
723
|
+
const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
724
|
+
if (touchGamepadAnalog)
|
|
462
725
|
{
|
|
463
|
-
//
|
|
464
|
-
|
|
465
|
-
const mousePosScreenLast = mousePosScreen;
|
|
466
|
-
mousePosScreen = mouseEventToScreen(pos);
|
|
467
|
-
if (wasTouching)
|
|
468
|
-
{
|
|
469
|
-
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
|
|
470
|
-
isUsingGamepad = touchGamepadEnable;
|
|
471
|
-
}
|
|
472
|
-
else
|
|
473
|
-
inputData[0][button] = 3;
|
|
726
|
+
// draw circle shaped gamepad
|
|
727
|
+
context.arc(stickCenter.x, stickCenter.y, touchGamepadSize/2, 0, 9);
|
|
474
728
|
}
|
|
475
|
-
else
|
|
476
|
-
inputData[0][button] = inputData[0][button] & 2 | 4;
|
|
477
|
-
|
|
478
|
-
// set was touching
|
|
479
|
-
wasTouching = touching;
|
|
480
|
-
|
|
481
|
-
// prevent default handling like copy, magnifier lens, and scrolling
|
|
482
|
-
if (inputPreventDefault && document.hasFocus() && e.cancelable)
|
|
483
|
-
e.preventDefault();
|
|
484
|
-
|
|
485
|
-
// must return true so the document will get focus
|
|
486
|
-
return true;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
// special handling for virtual gamepad mode
|
|
490
|
-
function handleTouchGamepad(e)
|
|
491
|
-
{
|
|
492
|
-
// clear touch gamepad input
|
|
493
|
-
touchGamepadStick = vec2();
|
|
494
|
-
touchGamepadButtons = [];
|
|
495
|
-
isUsingGamepad = true;
|
|
496
|
-
|
|
497
|
-
const touching = e.touches.length;
|
|
498
|
-
if (touching)
|
|
729
|
+
else
|
|
499
730
|
{
|
|
500
|
-
|
|
501
|
-
|
|
731
|
+
// draw cross shaped gamepad
|
|
732
|
+
for (let i=10; --i;)
|
|
502
733
|
{
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
734
|
+
const angle = i*PI/4;
|
|
735
|
+
context.arc(stickCenter.x, stickCenter.y,touchGamepadSize*.6, angle + PI/8, angle + PI/8);
|
|
736
|
+
i%2 && context.arc(stickCenter.x, stickCenter.y, touchGamepadSize*.33, angle, angle);
|
|
506
737
|
}
|
|
507
738
|
}
|
|
739
|
+
context.fill();
|
|
740
|
+
context.stroke();
|
|
508
741
|
|
|
509
|
-
//
|
|
510
|
-
const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
511
|
-
const buttonCenter = touchGamepadButtonCenter();
|
|
512
|
-
const startCenter = mainCanvasSize.scale(.5);
|
|
513
|
-
|
|
514
|
-
// check each touch point
|
|
515
|
-
for (const touch of e.touches)
|
|
742
|
+
// draw right face buttons
|
|
516
743
|
{
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
touchGamepadStick = touchPos.subtract(stickCenter).scale(2/touchGamepadSize).clampLength();
|
|
522
|
-
}
|
|
523
|
-
else if (buttonCenter.distance(touchPos) < touchGamepadSize)
|
|
744
|
+
const buttonCenter = touchGamepadButtonCenter();
|
|
745
|
+
const buttonSize = touchGamepadButtonCount > 1 ?
|
|
746
|
+
touchGamepadSize/4 : touchGamepadSize/2;
|
|
747
|
+
for (let i=0; i<touchGamepadButtonCount; i++)
|
|
524
748
|
{
|
|
525
|
-
|
|
526
|
-
let button =
|
|
527
|
-
|
|
528
|
-
if (touchGamepadButtonCount === 1)
|
|
529
|
-
button = 0;
|
|
530
|
-
else if (touchGamepadButtonCount === 2)
|
|
531
|
-
{
|
|
532
|
-
const delta = buttonCenter.subtract(touchPos);
|
|
533
|
-
button = -delta.x < delta.y ? 1 : 0;
|
|
534
|
-
}
|
|
749
|
+
const j = mod(i-1, 4);
|
|
750
|
+
let button = touchGamepadButtonCount > 2 ?
|
|
751
|
+
j : min(j, touchGamepadButtonCount-1);
|
|
535
752
|
// fix button locations (swap 2 and 3 to match gamepad layout)
|
|
536
753
|
button = button === 3 ? 2 : button === 2 ? 3 : button;
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
touchGamepadButtons[9] = 1;
|
|
754
|
+
const pos = touchGamepadButtonCount < 2 ? buttonCenter :
|
|
755
|
+
buttonCenter.add(vec2().setDirection(j, touchGamepadSize/2));
|
|
756
|
+
context.fillStyle = touchGamepadButtons[button] ? '#fff' : '#000';
|
|
757
|
+
context.beginPath();
|
|
758
|
+
context.arc(pos.x, pos.y, buttonSize, 0,9);
|
|
759
|
+
context.fill();
|
|
760
|
+
context.stroke();
|
|
545
761
|
}
|
|
546
762
|
}
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
// render the touch gamepad, called automatically by the engine
|
|
551
|
-
function touchGamepadRender()
|
|
552
|
-
{
|
|
553
|
-
if (!touchInputEnable || !isTouchDevice || headlessMode) return;
|
|
554
|
-
if (!touchGamepadEnable || !touchGamepadTimer.isSet())
|
|
555
|
-
return;
|
|
556
|
-
|
|
557
|
-
// fade off when not touching or paused
|
|
558
|
-
const alpha = percent(touchGamepadTimer.get(), 4, 3);
|
|
559
|
-
if (!alpha || paused)
|
|
560
|
-
return;
|
|
561
|
-
|
|
562
|
-
// setup the canvas
|
|
563
|
-
const context = overlayContext;
|
|
564
|
-
context.save();
|
|
565
|
-
context.globalAlpha = alpha*touchGamepadAlpha;
|
|
566
|
-
context.strokeStyle = '#fff';
|
|
567
|
-
context.lineWidth = 3;
|
|
568
|
-
|
|
569
|
-
// draw left analog stick
|
|
570
|
-
context.fillStyle = touchGamepadStick.lengthSquared() > 0 ? '#fff' : '#000';
|
|
571
|
-
context.beginPath();
|
|
572
|
-
const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
573
|
-
if (touchGamepadAnalog) // draw circle shaped gamepad
|
|
574
|
-
{
|
|
575
|
-
context.arc(stickCenter.x, stickCenter.y, touchGamepadSize/2, 0, 9);
|
|
576
|
-
context.fill();
|
|
577
|
-
context.stroke();
|
|
578
|
-
}
|
|
579
|
-
else // draw cross shaped gamepad
|
|
580
|
-
{
|
|
581
|
-
for (let i=10; i--;)
|
|
582
|
-
{
|
|
583
|
-
const angle = i*PI/4;
|
|
584
|
-
context.arc(stickCenter.x, stickCenter.y,touchGamepadSize*.6, angle + PI/8, angle + PI/8);
|
|
585
|
-
i%2 && context.arc(stickCenter.x, stickCenter.y, touchGamepadSize*.33, angle, angle);
|
|
586
|
-
i===1 && context.fill();
|
|
587
|
-
}
|
|
588
|
-
context.stroke();
|
|
589
|
-
}
|
|
590
763
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
const buttonSize = touchGamepadButtonCount > 1 ? touchGamepadSize/4 : touchGamepadSize/2;
|
|
594
|
-
for (let i=0; i<touchGamepadButtonCount; i++)
|
|
595
|
-
{
|
|
596
|
-
const j = mod(i-1, 4);
|
|
597
|
-
let button = touchGamepadButtonCount > 2 ?
|
|
598
|
-
j : min(j, touchGamepadButtonCount-1);
|
|
599
|
-
// fix button locations (swap 2 and 3 to match gamepad layout)
|
|
600
|
-
button = button === 3 ? 2 : button === 2 ? 3 : button;
|
|
601
|
-
const pos = buttonCenter.add(vec2().setDirection(j, touchGamepadSize/2));
|
|
602
|
-
context.fillStyle = touchGamepadButtons[button] ? '#fff' : '#000';
|
|
603
|
-
context.beginPath();
|
|
604
|
-
context.arc(pos.x, pos.y, buttonSize, 0,9);
|
|
605
|
-
context.fill();
|
|
606
|
-
context.stroke();
|
|
764
|
+
// set canvas back to normal
|
|
765
|
+
context.restore();
|
|
607
766
|
}
|
|
608
|
-
|
|
609
|
-
// set canvas back to normal
|
|
610
|
-
context.restore();
|
|
611
767
|
}
|
|
612
768
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
/** Request to lock the pointer, does not work on touch devices
|
|
617
|
-
* @memberof Input */
|
|
618
|
-
function pointerLockRequest()
|
|
769
|
+
// center position for right tocuh pad face buttons
|
|
770
|
+
function touchGamepadButtonCenter()
|
|
619
771
|
{
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
* @memberof Input */
|
|
626
|
-
function pointerLockExit() { document.exitPointerLock && document.exitPointerLock(); }
|
|
627
|
-
|
|
628
|
-
/** Check if pointer is locked (true if locked)
|
|
629
|
-
* @return {boolean}
|
|
630
|
-
* @memberof Input */
|
|
631
|
-
function pointerLockIsActive() { return document.pointerLockElement === mainCanvas; }
|
|
772
|
+
const center = mainCanvasSize.subtract(vec2(touchGamepadSize));
|
|
773
|
+
if (touchGamepadButtonCount === 2)
|
|
774
|
+
center.x += touchGamepadSize/2;
|
|
775
|
+
return center;
|
|
776
|
+
}
|