littlejsengine 1.15.3 → 1.15.9
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 +178 -55
- package/dist/littlejs.esm.js +1400 -746
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +1390 -745
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +1343 -714
- package/examples/box2d/game.js +1 -1
- package/examples/electron/index.html +2 -2
- package/examples/index.html +4 -3
- package/examples/particles/index.html +22 -6
- package/examples/platformer/game.js +7 -0
- package/examples/shorts/base.html +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/tileLayer.js +2 -2
- package/examples/shorts/tiles.png +0 -0
- package/examples/shorts/uiSystem.js +26 -15
- package/examples/starter/index.html +2 -2
- package/examples/uiSystem/game.js +26 -17
- 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 +476 -91
- package/src/engine.js +215 -193
- package/src/engineDebug.js +48 -32
- package/src/engineDraw.js +25 -8
- package/src/engineExport.js +8 -1
- package/src/engineInput.js +537 -380
- package/src/engineObject.js +24 -22
- package/src/engineParticles.js +8 -6
- package/src/engineSettings.js +35 -6
- package/src/engineTileLayer.js +3 -4
- 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,192 @@ 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
|
-
{
|
|
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
|
+
}
|
|
174
225
|
|
|
175
|
-
|
|
176
|
-
|
|
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
|
+
}
|
|
177
235
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
236
|
+
/** Returns true if passed in gamepad is connected
|
|
237
|
+
* @param {number} [gamepad]
|
|
238
|
+
* @return {boolean}
|
|
239
|
+
* @memberof Input */
|
|
240
|
+
function gamepadConnected(gamepad=gamepadPrimary)
|
|
241
|
+
{
|
|
242
|
+
ASSERT(isNumber(gamepad), 'gamepad must be a number');
|
|
243
|
+
return !!inputData[gamepad+1];
|
|
244
|
+
}
|
|
181
245
|
|
|
182
|
-
|
|
246
|
+
/** Returns how many control sticks the passed in gamepad has
|
|
247
|
+
* @param {number} [gamepad]
|
|
248
|
+
* @return {number}
|
|
249
|
+
* @memberof Input */
|
|
250
|
+
function gamepadStickCount(gamepad=gamepadPrimary)
|
|
183
251
|
{
|
|
184
|
-
|
|
252
|
+
ASSERT(isNumber(gamepad), 'gamepad must be a number');
|
|
253
|
+
return gamepadStickData[gamepad]?.length ?? 0;
|
|
254
|
+
}
|
|
185
255
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
256
|
+
/** True if a touch device has been detected
|
|
257
|
+
* @memberof Input */
|
|
258
|
+
const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
|
|
189
259
|
|
|
190
|
-
|
|
191
|
-
mousePos = screenToWorld(mousePosScreen);
|
|
192
|
-
mouseDelta = screenToWorldDelta(mouseDeltaScreen);
|
|
260
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
193
261
|
|
|
194
|
-
|
|
195
|
-
|
|
262
|
+
/** Pulse the vibration hardware if it exists
|
|
263
|
+
* @param {number|Array} [pattern] - single value in ms or vibration interval array
|
|
264
|
+
* @memberof Input */
|
|
265
|
+
function vibrate(pattern=100)
|
|
266
|
+
{
|
|
267
|
+
ASSERT(isNumber(pattern) || isArray(pattern), 'pattern must be a number or array');
|
|
268
|
+
vibrateEnable && !headlessMode && navigator && navigator.vibrate && navigator.vibrate(pattern);
|
|
196
269
|
}
|
|
197
270
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
271
|
+
/** Cancel any ongoing vibration
|
|
272
|
+
* @memberof Input */
|
|
273
|
+
function vibrateStop() { vibrate(0); }
|
|
201
274
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
275
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
276
|
+
// Pointer Lock
|
|
277
|
+
|
|
278
|
+
/** Request to lock the pointer, does not work on touch devices
|
|
279
|
+
* @memberof Input */
|
|
280
|
+
function pointerLockRequest()
|
|
281
|
+
{ !isTouchDevice && mainCanvas.requestPointerLock?.(); }
|
|
282
|
+
|
|
283
|
+
/** Request to unlock the pointer
|
|
284
|
+
* @memberof Input */
|
|
285
|
+
function pointerLockExit()
|
|
286
|
+
{ document.exitPointerLock?.(); }
|
|
287
|
+
|
|
288
|
+
/** Check if pointer is locked (true if locked)
|
|
289
|
+
* @return {boolean}
|
|
290
|
+
* @memberof Input */
|
|
291
|
+
function pointerLockIsActive()
|
|
292
|
+
{ return document.pointerLockElement === mainCanvas; }
|
|
293
|
+
|
|
294
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
295
|
+
// Input variables used by engine
|
|
296
|
+
|
|
297
|
+
// input uses bit field for each key: 1=isDown, 2=wasPressed, 4=wasReleased
|
|
298
|
+
// mouse and keyboard stored in device 0, gamepads stored in devices > 0
|
|
299
|
+
const inputData = [[]];
|
|
300
|
+
|
|
301
|
+
// gamepad internal variables
|
|
302
|
+
const gamepadStickData = [], gamepadDpadData = [], gamepadHadInput = [];
|
|
303
|
+
|
|
304
|
+
// touch gamepad internal variables
|
|
305
|
+
const touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadSticks = [];
|
|
306
|
+
|
|
307
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
308
|
+
// Input system functions used by engine
|
|
210
309
|
|
|
211
310
|
function inputInit()
|
|
212
311
|
{
|
|
@@ -236,6 +335,18 @@ function inputInit()
|
|
|
236
335
|
if (inputWASDEmulateDirection)
|
|
237
336
|
inputData[0][remapKey(e.code)] = 3;
|
|
238
337
|
}
|
|
338
|
+
|
|
339
|
+
// prevent arrow key from moving the page
|
|
340
|
+
const preventDefaultKeys =
|
|
341
|
+
[
|
|
342
|
+
'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', // scrolling
|
|
343
|
+
'Space', // page down scroll
|
|
344
|
+
'Tab', // focus navigation
|
|
345
|
+
'Backspace', // browser back
|
|
346
|
+
];
|
|
347
|
+
if (preventDefaultKeys.includes(e.code))
|
|
348
|
+
if (inputPreventDefault && document.hasFocus() && e.cancelable)
|
|
349
|
+
e.preventDefault();
|
|
239
350
|
}
|
|
240
351
|
function onKeyUp(e)
|
|
241
352
|
{
|
|
@@ -267,7 +378,9 @@ function inputInit()
|
|
|
267
378
|
const mousePosScreenLast = mousePosScreen;
|
|
268
379
|
mousePosScreen = mouseEventToScreen(vec2(e.x,e.y));
|
|
269
380
|
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
|
|
270
|
-
|
|
381
|
+
|
|
382
|
+
if (inputPreventDefault && document.hasFocus() && e.cancelable)
|
|
383
|
+
e.preventDefault();
|
|
271
384
|
}
|
|
272
385
|
function onMouseUp(e)
|
|
273
386
|
{
|
|
@@ -291,341 +404,385 @@ function inputInit()
|
|
|
291
404
|
function onMouseWheel(e) { mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY); }
|
|
292
405
|
function onContextMenu(e) { e.preventDefault(); } // prevent right click menu
|
|
293
406
|
function onBlur() { inputClear(); } // reset input when focus is lost
|
|
294
|
-
}
|
|
295
407
|
|
|
296
|
-
//
|
|
297
|
-
function
|
|
298
|
-
{
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
408
|
+
// enable touch input mouse passthrough
|
|
409
|
+
function touchInputInit()
|
|
410
|
+
{
|
|
411
|
+
// add non passive touch event listeners
|
|
412
|
+
document.addEventListener('touchstart', (e) => handleTouch(e), { passive: false });
|
|
413
|
+
document.addEventListener('touchmove', (e) => handleTouch(e), { passive: false });
|
|
414
|
+
document.addEventListener('touchend', (e) => handleTouch(e), { passive: false });
|
|
415
|
+
|
|
416
|
+
// handle all touch events the same way
|
|
417
|
+
let wasTouching;
|
|
418
|
+
function handleTouch(e)
|
|
419
|
+
{
|
|
420
|
+
if (!touchInputEnable)
|
|
421
|
+
return;
|
|
304
422
|
|
|
305
|
-
|
|
306
|
-
|
|
423
|
+
// route touch to gamepad
|
|
424
|
+
if (touchGamepadEnable)
|
|
425
|
+
handleTouchGamepad(e);
|
|
307
426
|
|
|
308
|
-
//
|
|
309
|
-
|
|
427
|
+
// fix stalled audio requiring user interaction
|
|
428
|
+
if (soundEnable && !headlessMode && audioContext && !audioIsRunning())
|
|
429
|
+
audioContext.resume();
|
|
310
430
|
|
|
311
|
-
//
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
431
|
+
// check if touching and pass to mouse events
|
|
432
|
+
const touching = e.touches.length;
|
|
433
|
+
const button = 0; // all touches are left mouse button
|
|
434
|
+
if (touching)
|
|
435
|
+
{
|
|
436
|
+
// set event pos and pass it along
|
|
437
|
+
const pos = vec2(e.touches[0].clientX, e.touches[0].clientY);
|
|
438
|
+
const mousePosScreenLast = mousePosScreen;
|
|
439
|
+
mousePosScreen = mouseEventToScreen(pos);
|
|
440
|
+
if (wasTouching)
|
|
441
|
+
{
|
|
442
|
+
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
|
|
443
|
+
isUsingGamepad = touchGamepadEnable;
|
|
444
|
+
}
|
|
445
|
+
else
|
|
446
|
+
inputData[0][button] = 3;
|
|
447
|
+
}
|
|
448
|
+
else if (wasTouching)
|
|
449
|
+
inputData[0][button] = inputData[0][button] & 2 | 4;
|
|
322
450
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
{
|
|
326
|
-
if (!touchGamepadTimer.isSet())
|
|
327
|
-
return;
|
|
451
|
+
// set was touching
|
|
452
|
+
wasTouching = touching;
|
|
328
453
|
|
|
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();
|
|
454
|
+
// prevent default handling like copy, magnifier lens, and scrolling
|
|
455
|
+
if (inputPreventDefault && document.hasFocus() && e.cancelable)
|
|
456
|
+
e.preventDefault();
|
|
457
|
+
|
|
458
|
+
// must return true so the document will get focus
|
|
459
|
+
return true;
|
|
340
460
|
}
|
|
341
461
|
|
|
342
|
-
//
|
|
343
|
-
|
|
344
|
-
for (let i=10; i--;)
|
|
462
|
+
// special handling for virtual gamepad mode
|
|
463
|
+
function handleTouchGamepad(e)
|
|
345
464
|
{
|
|
346
|
-
|
|
347
|
-
|
|
465
|
+
// clear touch gamepad input
|
|
466
|
+
touchGamepadSticks.length = 0;
|
|
467
|
+
touchGamepadSticks[0] = vec2();
|
|
468
|
+
touchGamepadSticks[1] = vec2();
|
|
469
|
+
touchGamepadButtons.length = 0;
|
|
470
|
+
isUsingGamepad = true;
|
|
471
|
+
|
|
472
|
+
const touching = e.touches.length;
|
|
473
|
+
if (touching)
|
|
474
|
+
{
|
|
475
|
+
touchGamepadTimer.set();
|
|
476
|
+
if (touchGamepadCenterButton && !wasTouching && paused)
|
|
477
|
+
{
|
|
478
|
+
// touch anywhere to press start when paused
|
|
479
|
+
touchGamepadButtons[9] = 1;
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// don't process touch gamepad if paused
|
|
485
|
+
if (paused)
|
|
486
|
+
return;
|
|
487
|
+
|
|
488
|
+
// get center of left and right sides
|
|
489
|
+
const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
490
|
+
const buttonCenter = touchGamepadButtonCenter();
|
|
491
|
+
const startCenter = mainCanvasSize.scale(.5);
|
|
492
|
+
|
|
493
|
+
// check each touch point
|
|
494
|
+
for (const touch of e.touches)
|
|
495
|
+
{
|
|
496
|
+
const touchPos = mouseEventToScreen(vec2(touch.clientX, touch.clientY));
|
|
497
|
+
if (stickCenter.distance(touchPos) < touchGamepadSize)
|
|
498
|
+
{
|
|
499
|
+
// virtual analog stick
|
|
500
|
+
const delta = touchPos.subtract(stickCenter);
|
|
501
|
+
touchGamepadSticks[0] = delta.scale(2/touchGamepadSize).clampLength();
|
|
502
|
+
}
|
|
503
|
+
else if (buttonCenter.distance(touchPos) < touchGamepadSize)
|
|
504
|
+
{
|
|
505
|
+
if (touchGamepadButtonCount === 1)
|
|
506
|
+
{
|
|
507
|
+
// virtual right analog stick
|
|
508
|
+
const delta = touchPos.subtract(buttonCenter);
|
|
509
|
+
touchGamepadSticks[1] = delta.scale(2/touchGamepadSize).clampLength();
|
|
510
|
+
}
|
|
511
|
+
// virtual face buttons
|
|
512
|
+
let button = buttonCenter.subtract(touchPos).direction();
|
|
513
|
+
button = mod(button+2, 4);
|
|
514
|
+
if (touchGamepadButtonCount === 1)
|
|
515
|
+
button = 0;
|
|
516
|
+
else if (touchGamepadButtonCount === 2)
|
|
517
|
+
{
|
|
518
|
+
const delta = buttonCenter.subtract(touchPos);
|
|
519
|
+
button = -delta.x < delta.y ? 1 : 0;
|
|
520
|
+
}
|
|
521
|
+
// fix button locations (swap 2 and 3 to match gamepad layout)
|
|
522
|
+
button = button === 3 ? 2 : button === 2 ? 3 : button;
|
|
523
|
+
if (button < touchGamepadButtonCount)
|
|
524
|
+
touchGamepadButtons[button] = 1;
|
|
525
|
+
}
|
|
526
|
+
else if (touchGamepadCenterButton &&
|
|
527
|
+
startCenter.distance(touchPos) < touchGamepadSize)
|
|
528
|
+
{
|
|
529
|
+
// virtual start button in center
|
|
530
|
+
touchGamepadButtons[9] = 1;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
348
533
|
}
|
|
534
|
+
}
|
|
349
535
|
|
|
350
|
-
|
|
351
|
-
|
|
536
|
+
// convert a mouse or touch event position to screen space
|
|
537
|
+
function mouseEventToScreen(mousePos)
|
|
538
|
+
{
|
|
539
|
+
const rect = mainCanvas.getBoundingClientRect();
|
|
540
|
+
const px = percent(mousePos.x, rect.left, rect.right);
|
|
541
|
+
const py = percent(mousePos.y, rect.top, rect.bottom);
|
|
542
|
+
return vec2(px*mainCanvas.width, py*mainCanvas.height);
|
|
352
543
|
}
|
|
544
|
+
}
|
|
353
545
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
546
|
+
function inputUpdate()
|
|
547
|
+
{
|
|
548
|
+
if (headlessMode) return;
|
|
357
549
|
|
|
358
|
-
//
|
|
359
|
-
if (!
|
|
360
|
-
|
|
550
|
+
// clear input when lost focus (prevent stuck keys)
|
|
551
|
+
if (!(touchInputEnable && isTouchDevice) && !document.hasFocus())
|
|
552
|
+
inputClear();
|
|
361
553
|
|
|
362
|
-
//
|
|
363
|
-
|
|
364
|
-
|
|
554
|
+
// update mouse world space position and delta
|
|
555
|
+
mousePos = screenToWorld(mousePosScreen);
|
|
556
|
+
mouseDelta = screenToWorldDelta(mouseDeltaScreen);
|
|
557
|
+
|
|
558
|
+
// update gamepads if enabled
|
|
559
|
+
gamepadsUpdate();
|
|
560
|
+
|
|
561
|
+
// gamepads are updated by engine every frame automatically
|
|
562
|
+
function gamepadsUpdate()
|
|
365
563
|
{
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
564
|
+
const applyDeadZones = (v)=>
|
|
565
|
+
{
|
|
566
|
+
const min=.3, max=.8;
|
|
567
|
+
const deadZone = (v)=>
|
|
568
|
+
v > min ? percent(v, min, max) :
|
|
569
|
+
v < -min ? -percent(-v, min, max) : 0;
|
|
570
|
+
return vec2(deadZone(v.x), deadZone(-v.y)).clampLength();
|
|
571
|
+
}
|
|
370
572
|
|
|
371
|
-
if
|
|
573
|
+
// update touch gamepad if enabled
|
|
574
|
+
if (touchGamepadEnable && isTouchDevice)
|
|
372
575
|
{
|
|
576
|
+
if (!touchGamepadTimer.isSet())
|
|
577
|
+
return;
|
|
578
|
+
|
|
579
|
+
// read virtual analog stick
|
|
580
|
+
gamepadPrimary = 0; // touch gamepad uses index 0
|
|
581
|
+
const sticks = gamepadStickData[0] ?? (gamepadStickData[0] = []);
|
|
582
|
+
const dpad = gamepadDpadData[0] ?? (gamepadDpadData[0] = vec2());
|
|
583
|
+
sticks[0] = vec2();
|
|
584
|
+
dpad.set();
|
|
585
|
+
const leftTouchStick = touchGamepadSticks[0] ?? vec2();
|
|
586
|
+
if (touchGamepadAnalog)
|
|
587
|
+
sticks[0] = applyDeadZones(leftTouchStick);
|
|
588
|
+
else if (leftTouchStick.lengthSquared() > .3)
|
|
589
|
+
{
|
|
590
|
+
// convert to 8 way dpad
|
|
591
|
+
const x = clamp(round(leftTouchStick.x), -1, 1);
|
|
592
|
+
const y = clamp(round(leftTouchStick.y), -1, 1);
|
|
593
|
+
dpad.set(x, -y);
|
|
594
|
+
sticks[0] = dpad.clampLength(); // clamp to circle
|
|
595
|
+
}
|
|
596
|
+
if (touchGamepadButtonCount === 1)
|
|
597
|
+
{
|
|
598
|
+
const rightTouchStick = touchGamepadSticks[1] ?? vec2();
|
|
599
|
+
sticks[1] = applyDeadZones(rightTouchStick);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// read virtual gamepad buttons
|
|
603
|
+
const data = inputData[1] ?? (inputData[1] = []);
|
|
604
|
+
for (let i=10; i--;)
|
|
605
|
+
{
|
|
606
|
+
const wasDown = gamepadIsDown(i,0);
|
|
607
|
+
data[i] = touchGamepadButtons[i] ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// disable normal gamepads when touch gamepad is active
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// return if gamepads are disabled or not supported
|
|
615
|
+
if (!gamepadsEnable || !navigator || !navigator.getGamepads)
|
|
616
|
+
return;
|
|
617
|
+
|
|
618
|
+
// only poll gamepads when focused or in debug mode
|
|
619
|
+
if (!debug && !document.hasFocus())
|
|
620
|
+
return;
|
|
621
|
+
|
|
622
|
+
// poll gamepads
|
|
623
|
+
const maxGamepads = 8;
|
|
624
|
+
const gamepads = navigator.getGamepads();
|
|
625
|
+
const gamepadCount = min(maxGamepads, gamepads.length)
|
|
626
|
+
for (let i=0; i<gamepadCount; ++i)
|
|
627
|
+
{
|
|
628
|
+
// get or create gamepad data
|
|
629
|
+
const gamepad = gamepads[i];
|
|
630
|
+
if (!gamepad)
|
|
631
|
+
{
|
|
632
|
+
// clear gamepad data if not connected
|
|
633
|
+
inputData[i+1] = undefined;
|
|
634
|
+
gamepadStickData[i] = undefined;
|
|
635
|
+
gamepadDpadData[i] = undefined;
|
|
636
|
+
gamepadHadInput[i] = undefined;
|
|
637
|
+
continue;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const data = inputData[i+1] ?? (inputData[i+1] = []);
|
|
641
|
+
const sticks = gamepadStickData[i] ?? (gamepadStickData[i] = []);
|
|
642
|
+
const dpad = gamepadDpadData[i] ?? (gamepadDpadData[i] = vec2());
|
|
643
|
+
|
|
373
644
|
// read analog sticks
|
|
374
645
|
for (let j = 0; j < gamepad.axes.length-1; j+=2)
|
|
375
646
|
sticks[j>>1] = applyDeadZones(vec2(gamepad.axes[j],gamepad.axes[j+1]));
|
|
376
647
|
|
|
377
648
|
// read buttons
|
|
649
|
+
let hadInput = false;
|
|
378
650
|
for (let j = gamepad.buttons.length; j--;)
|
|
379
651
|
{
|
|
380
652
|
const button = gamepad.buttons[j];
|
|
381
653
|
const wasDown = gamepadIsDown(j,i);
|
|
382
654
|
data[j] = button.pressed ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
655
|
+
|
|
656
|
+
// check for any input on this gamepad, analog must be full press
|
|
657
|
+
if (button.pressed)
|
|
658
|
+
if (!button.value || button.value > .9)
|
|
659
|
+
hadInput = true;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// set new primary gamepad if current is not connected
|
|
663
|
+
if (hadInput)
|
|
664
|
+
{
|
|
665
|
+
gamepadHadInput[i] = true;
|
|
666
|
+
if (!gamepadHadInput[gamepadPrimary])
|
|
667
|
+
gamepadPrimary = i;
|
|
668
|
+
isUsingGamepad ||= (gamepadPrimary === i);
|
|
386
669
|
}
|
|
387
670
|
|
|
388
|
-
if (
|
|
671
|
+
if (gamepad.mapping === 'standard')
|
|
389
672
|
{
|
|
390
|
-
//
|
|
391
|
-
|
|
673
|
+
// get dpad buttons (standard mapping)
|
|
674
|
+
dpad.set(
|
|
392
675
|
(gamepadIsDown(15,i)&&1) - (gamepadIsDown(14,i)&&1),
|
|
393
676
|
(gamepadIsDown(12,i)&&1) - (gamepadIsDown(13,i)&&1));
|
|
394
|
-
|
|
395
|
-
|
|
677
|
+
}
|
|
678
|
+
else if (gamepad.axes && gamepad.axes.length >= 2)
|
|
679
|
+
{
|
|
680
|
+
// digital style dpad from axes
|
|
681
|
+
const x = clamp(round(gamepad.axes[0]), -1, 1);
|
|
682
|
+
const y = clamp(round(gamepad.axes[1]), -1, 1);
|
|
683
|
+
dpad.set(x, -y);
|
|
396
684
|
}
|
|
397
685
|
|
|
398
|
-
//
|
|
399
|
-
|
|
686
|
+
// copy dpad to left analog stick when pressed
|
|
687
|
+
if (gamepadDirectionEmulateStick && !dpad.isZero())
|
|
688
|
+
sticks[0] = dpad.clampLength();
|
|
400
689
|
}
|
|
690
|
+
|
|
691
|
+
// disable touch gamepad if using real gamepad
|
|
692
|
+
touchGamepadEnable && isUsingGamepad && touchGamepadTimer.unset();
|
|
401
693
|
}
|
|
402
694
|
}
|
|
403
695
|
|
|
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()
|
|
696
|
+
function inputUpdatePost()
|
|
427
697
|
{
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
698
|
+
if (headlessMode) return;
|
|
699
|
+
|
|
700
|
+
// clear input to prepare for next frame
|
|
701
|
+
for (const deviceInputData of inputData)
|
|
702
|
+
for (const i in deviceInputData)
|
|
703
|
+
deviceInputData[i] &= 1;
|
|
704
|
+
mouseWheel = 0;
|
|
705
|
+
mouseDelta = vec2();
|
|
706
|
+
mouseDeltaScreen = vec2();
|
|
433
707
|
}
|
|
434
708
|
|
|
435
|
-
|
|
436
|
-
function touchInputInit()
|
|
709
|
+
function inputRender()
|
|
437
710
|
{
|
|
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)
|
|
711
|
+
touchGamepadRender();
|
|
712
|
+
|
|
713
|
+
function touchGamepadRender()
|
|
446
714
|
{
|
|
447
|
-
if (!touchInputEnable)
|
|
715
|
+
if (!touchInputEnable || !isTouchDevice || headlessMode) return;
|
|
716
|
+
if (!touchGamepadEnable || !touchGamepadTimer.isSet())
|
|
448
717
|
return;
|
|
449
718
|
|
|
450
|
-
//
|
|
451
|
-
|
|
452
|
-
|
|
719
|
+
// fade off when not touching or paused
|
|
720
|
+
const alpha = percent(touchGamepadTimer.get(), 4, 3);
|
|
721
|
+
if (!alpha || paused)
|
|
722
|
+
return;
|
|
453
723
|
|
|
454
|
-
//
|
|
455
|
-
|
|
456
|
-
|
|
724
|
+
// setup the canvas
|
|
725
|
+
const context = overlayContext;
|
|
726
|
+
context.save();
|
|
727
|
+
context.globalAlpha = alpha*touchGamepadAlpha;
|
|
728
|
+
context.strokeStyle = '#fff';
|
|
729
|
+
context.lineWidth = 3;
|
|
457
730
|
|
|
458
|
-
//
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
|
|
731
|
+
// draw left analog stick
|
|
732
|
+
const leftTouchStick = touchGamepadSticks[0] ?? vec2();
|
|
733
|
+
context.fillStyle = leftTouchStick.lengthSquared() > 0 ? '#fff' : '#000';
|
|
734
|
+
context.beginPath();
|
|
735
|
+
const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
736
|
+
if (touchGamepadAnalog)
|
|
462
737
|
{
|
|
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;
|
|
738
|
+
// draw circle shaped gamepad
|
|
739
|
+
context.arc(stickCenter.x, stickCenter.y, touchGamepadSize/2, 0, 9);
|
|
474
740
|
}
|
|
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)
|
|
741
|
+
else
|
|
499
742
|
{
|
|
500
|
-
|
|
501
|
-
|
|
743
|
+
// draw cross shaped gamepad
|
|
744
|
+
for (let i=10; --i;)
|
|
502
745
|
{
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
746
|
+
const angle = i*PI/4;
|
|
747
|
+
context.arc(stickCenter.x, stickCenter.y,touchGamepadSize*.6, angle + PI/8, angle + PI/8);
|
|
748
|
+
i%2 && context.arc(stickCenter.x, stickCenter.y, touchGamepadSize*.33, angle, angle);
|
|
506
749
|
}
|
|
507
750
|
}
|
|
751
|
+
context.fill();
|
|
752
|
+
context.stroke();
|
|
508
753
|
|
|
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)
|
|
754
|
+
// draw right face buttons
|
|
516
755
|
{
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
touchGamepadStick = touchPos.subtract(stickCenter).scale(2/touchGamepadSize).clampLength();
|
|
522
|
-
}
|
|
523
|
-
else if (buttonCenter.distance(touchPos) < touchGamepadSize)
|
|
756
|
+
const buttonCenter = touchGamepadButtonCenter();
|
|
757
|
+
const buttonSize = touchGamepadButtonCount > 1 ?
|
|
758
|
+
touchGamepadSize/4 : touchGamepadSize/2;
|
|
759
|
+
for (let i=0; i<touchGamepadButtonCount; i++)
|
|
524
760
|
{
|
|
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
|
-
}
|
|
761
|
+
const j = mod(i-1, 4);
|
|
762
|
+
let button = touchGamepadButtonCount > 2 ?
|
|
763
|
+
j : min(j, touchGamepadButtonCount-1);
|
|
535
764
|
// fix button locations (swap 2 and 3 to match gamepad layout)
|
|
536
765
|
button = button === 3 ? 2 : button === 2 ? 3 : button;
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
touchGamepadButtons[9] = 1;
|
|
766
|
+
const pos = touchGamepadButtonCount < 2 ? buttonCenter :
|
|
767
|
+
buttonCenter.add(vec2().setDirection(j, touchGamepadSize/2));
|
|
768
|
+
context.fillStyle = touchGamepadButtons[button] ? '#fff' : '#000';
|
|
769
|
+
context.beginPath();
|
|
770
|
+
context.arc(pos.x, pos.y, buttonSize, 0,9);
|
|
771
|
+
context.fill();
|
|
772
|
+
context.stroke();
|
|
545
773
|
}
|
|
546
774
|
}
|
|
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
775
|
|
|
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();
|
|
776
|
+
// set canvas back to normal
|
|
777
|
+
context.restore();
|
|
607
778
|
}
|
|
608
|
-
|
|
609
|
-
// set canvas back to normal
|
|
610
|
-
context.restore();
|
|
611
779
|
}
|
|
612
780
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
/** Request to lock the pointer, does not work on touch devices
|
|
617
|
-
* @memberof Input */
|
|
618
|
-
function pointerLockRequest()
|
|
781
|
+
// center position for right tocuh pad face buttons
|
|
782
|
+
function touchGamepadButtonCenter()
|
|
619
783
|
{
|
|
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; }
|
|
784
|
+
const center = mainCanvasSize.subtract(vec2(touchGamepadSize));
|
|
785
|
+
if (touchGamepadButtonCount === 2)
|
|
786
|
+
center.x += touchGamepadSize/2;
|
|
787
|
+
return center;
|
|
788
|
+
}
|