- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
- 307
- 308
- 309
- 310
- 311
- 312
- 313
- 314
- 315
- 316
- 317
- 318
- 319
- 320
- 321
- 322
- 323
- 324
- 325
- 326
- 327
- 328
- 329
- 330
- 331
- 332
- 333
- 334
- 335
- 336
- 337
- 338
- 339
- 340
- 341
- 342
- 343
- 344
- 345
- 346
- 347
- 348
- 349
- 350
- 351
- 352
- 353
- 354
- 355
- 356
- 357
- 358
- 359
- 360
- 361
- 362
- 363
- 364
- 365
- 366
- 367
- 368
- 369
- 370
- 371
- 372
- 373
- 374
- 375
- 376
- 377
- 378
- 379
- 380
- 381
- 382
- 383
- 384
- 385
- 386
- 387
- 388
- 389
- 390
- 391
- 392
- 393
- 394
- 395
- 396
- 397
- 398
- 399
- 400
- 401
- 402
- 403
- 404
- 405
- 406
- 407
- 408
- 409
- 410
- 411
- 412
- 413
- 414
- 415
- 416
- 417
- 418
- 419
- 420
- 421
- 422
- 423
- 424
- 425
- 426
- 427
- 428
- 429
- 430
- 431
- 432
- 433
- 434
- 435
- 436
- 437
- 438
- 439
- 440
- 441
- 442
- 443
- 444
- 445
- 446
- 447
- 448
- 449
- 450
- 451
- 452
- 453
- 454
- 455
- 456
- 457
- 458
- 459
- 460
- 461
- 462
- 463
- 464
- 465
- 466
- 467
- 468
- 469
- 470
- 471
- 472
- 473
- 474
- 475
- 476
- 477
- 478
- 479
- 480
- 481
- 482
- 483
- 484
- 485
- 486
- 487
- 488
import {getBindingKey, triggerKeyEvent} from "./keyboard.js";
import * as event from "./../system/event.js";
// Analog deadzone
let deadzone = 0.1;
/**
* Normalize axis values for wired Xbox 360
* @ignore
*/
function wiredXbox360NormalizeFn(value, axis, button) {
if (button === this.GAMEPAD.BUTTONS.L2 || button === this.GAMEPAD.BUTTONS.R2) {
return (value + 1) / 2;
}
return value;
}
/**
* Normalize axis values for OUYA
* @ignore
*/
function ouyaNormalizeFn(value, axis, button) {
if (value > 0) {
if (button === this.GAMEPAD.BUTTONS.L2) {
// L2 is wonky; seems like the deadzone is around 20000
// (That's over 15% of the total range!)
value = Math.max(0, value - 20000) / 111070;
}
else {
// Normalize [1..65536] => [0.0..0.5]
value = (value - 1) / 131070;
}
}
else {
// Normalize [-65536..-1] => [0.5..1.0]
value = (65536 + value) / 131070 + 0.5;
}
return value;
}
// Match vendor and product codes for Firefox
const vendorProductRE = /^([0-9a-f]{1,4})-([0-9a-f]{1,4})-/i;
// Match leading zeros
const leadingZeroRE = /^0+/;
/**
* Firefox reports different ids for gamepads depending on the platform:
* - Windows: vendor and product codes contain leading zeroes
* - Mac: vendor and product codes are sparse (no leading zeroes)
*
* This function normalizes the id to support both formats
* @ignore
*/
function addMapping(id, mapping) {
const expanded_id = id.replace(vendorProductRE, (_, a, b) =>
"000".slice(a.length - 1) + a + "-" +
"000".slice(b.length - 1) + b + "-"
);
const sparse_id = id.replace(vendorProductRE, (_, a, b) =>
a.replace(leadingZeroRE, "") + "-" +
b.replace(leadingZeroRE, "") + "-"
);
// Normalize optional parameters
mapping.analog = mapping.analog || mapping.buttons.map(() => -1);
mapping.normalize_fn = mapping.normalize_fn || function (value) { return value; };
remap.set(expanded_id, mapping);
remap.set(sparse_id, mapping);
}
// binding list
let bindings = {};
// mapping list
let remap = new Map();
let updateEventHandler;
// Default gamepad mappings
[
// Firefox mappings
[
"45e-28e-Xbox 360 Wired Controller",
{
"axes" : [ 0, 1, 3, 4 ],
"buttons" : [ 11, 12, 13, 14, 8, 9, -1, -1, 5, 4, 6, 7, 0, 1, 2, 3, 10 ],
"analog" : [ -1, -1, -1, -1, -1, -1, 2, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1 ],
"normalize_fn" : wiredXbox360NormalizeFn
}
],
[
"54c-268-PLAYSTATION(R)3 Controller",
{
"axes" : [ 0, 1, 2, 3 ],
"buttons" : [ 14, 13, 15, 12, 10, 11, 8, 9, 0, 3, 1, 2, 4, 6, 7, 5, 16 ]
}
],
[
"54c-5c4-Wireless Controller", // PS4 Controller
{
"axes" : [ 0, 1, 2, 3 ],
"buttons" : [ 1, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15, 16, 17, 12, 13 ]
}
],
[
"2836-1-OUYA Game Controller",
{
"axes" : [ 0, 3, 7, 9 ],
"buttons" : [ 3, 6, 4, 5, 7, 8, 15, 16, -1, -1, 9, 10, 11, 12, 13, 14, -1 ],
"analog" : [ -1, -1, -1, -1, -1, -1, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1 ],
"normalize_fn" : ouyaNormalizeFn
}
],
// Chrome mappings
[
"OUYA Game Controller (Vendor: 2836 Product: 0001)",
{
"axes" : [ 0, 1, 3, 4 ],
"buttons" : [ 0, 3, 1, 2, 4, 5, 12, 13, -1, -1, 6, 7, 8, 9, 10, 11, -1 ],
"analog" : [ -1, -1, -1, -1, -1, -1, 2, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1 ],
"normalize_fn" : ouyaNormalizeFn
}
]
].forEach((value) => {
addMapping(value[0], value[1]);
});
/**
* Update gamepad status
* @ignore
*/
let updateGamepads = function () {
let gamepads = navigator.getGamepads();
// Trigger button bindings
Object.keys(bindings).forEach((index) => {
let gamepad = gamepads[index];
if (!gamepad) {
return;
}
let mapping = null;
if (gamepad.mapping !== "standard") {
mapping = remap.get(gamepad.id);
}
let binding = bindings[index];
// Iterate all buttons that have active bindings
Object.keys(binding.buttons).forEach((button) => {
let last = binding.buttons[button];
let mapped_button = button;
let mapped_axis = -1;
// Remap buttons if necessary
if (mapping) {
mapped_button = mapping.buttons[button];
mapped_axis = mapping.analog[button];
if (mapped_button < 0 && mapped_axis < 0) {
// Button is not mapped
return;
}
}
// Get mapped button
let current = gamepad.buttons[mapped_button] || {};
// Remap an axis to an analog button
if (mapping) {
if (mapped_axis >= 0) {
let value = mapping.normalize_fn(gamepad.axes[mapped_axis], -1, +button);
// Create a new object, because GamepadButton is read-only
current = {
"value" : value,
"pressed" : current.pressed || (Math.abs(value) >= deadzone)
};
}
}
event.emit(event.GAMEPAD_UPDATE, index, "buttons", +button, current);
// Edge detection
if (!last.pressed && current.pressed) {
triggerKeyEvent(last.keyCode, true, mapped_button + 256);
}
else if (last.pressed && !current.pressed) {
triggerKeyEvent(last.keyCode, false, mapped_button + 256);
}
// Update last button state
last.value = current.value;
last.pressed = current.pressed;
});
// Iterate all axes that have active bindings
Object.keys(binding.axes).forEach((axis) => {
let last = binding.axes[axis];
let mapped_axis = axis;
// Remap buttons if necessary
if (mapping) {
mapped_axis = mapping.axes[axis];
if (mapped_axis < 0) {
// axe is not mapped
return;
}
}
// retrieve the current value and normalize if necessary
let value = gamepad.axes[mapped_axis];
if (typeof(value) === "undefined") {
return;
}
if (mapping) {
value = mapping.normalize_fn(value, +axis, -1);
}
// normalize value into a [-1, 1] range value (treat 0 as positive)
let range = Math.sign(value) || 1;
if (last[range].keyCode === 0) {
return;
}
let pressed = (Math.abs(value) >= (deadzone + Math.abs(last[range].threshold)));
event.emit(event.GAMEPAD_UPDATE, index, "axes", +axis, value);
// Edge detection
if (!last[range].pressed && pressed) {
// Release the opposite direction, if necessary
if (last[-range].pressed) {
triggerKeyEvent(last[-range].keyCode, false, mapped_axis + 256);
last[-range].value = 0;
last[-range].pressed = false;
}
triggerKeyEvent(last[range].keyCode, true, mapped_axis + 256);
}
else if ((last[range].pressed || last[-range].pressed) && !pressed) {
range = last[range].pressed ? range : -range;
triggerKeyEvent(last[range].keyCode, false, mapped_axis + 256);
}
// Update last axis state
last[range].value = value;
last[range].pressed = pressed;
});
});
};
// gamepad connected callback
if (globalThis.navigator && typeof globalThis.navigator.getGamepads === "function") {
globalThis.addEventListener("gamepadconnected", (e) => {
event.emit(event.GAMEPAD_CONNECTED, e.gamepad);
}, false);
/*
* gamepad disconnected callback
*/
globalThis.addEventListener("gamepaddisconnected", (e) => {
event.emit(event.GAMEPAD_DISCONNECTED, e.gamepad);
}, false);
}
/*
* PUBLIC STUFF
*/
/**
* Namespace for standard gamepad mapping constants
* @public
* @namespace GAMEPAD
* @memberof input
*/
export let GAMEPAD = {
/**
* Standard gamepad mapping information for axes<br>
* <ul>
* <li>Left control stick: <code>LX</code> (horizontal), <code>LY</code> (vertical)</li>
* <li>Right control stick: <code>RX</code> (horizontal), <code>RY</code> (vertical)</li>
* <li>Extras: <code>EXTRA_1</code>, <code>EXTRA_2</code>, <code>EXTRA_3</code>, <code>EXTRA_4</code></li>
* </ul>
* @public
* @name AXES
* @enum {number}
* @memberof input.GAMEPAD
* @see https://w3c.github.io/gamepad/#remapping
*/
"AXES" : {
"LX" : 0,
"LY" : 1,
"RX" : 2,
"RY" : 3,
"EXTRA_1" : 4,
"EXTRA_2" : 5,
"EXTRA_3" : 6,
"EXTRA_4" : 7
},
/**
* Standard gamepad mapping information for buttons<br>
* <ul>
* <li>Face buttons: <code>FACE_1</code>, <code>FACE_2</code>, <code>FACE_3</code>, <code>FACE_4</code></li>
* <li>D-Pad: <code>UP</code>, <code>DOWN</code>, <code>LEFT</code>, <code>RIGHT</code></li>
* <li>Shoulder buttons: <code>L1</code>, <code>L2</code>, <code>R1</code>, <code>R2</code></li>
* <li>Analog stick (clicks): <code>L3</code>, <code>R3</code></li>
* <li>Navigation: <code>SELECT</code> (<code>BACK</code>), <code>START</code> (<code>FORWARD</code>), <code>HOME</code></li>
* <li>Extras: <code>EXTRA_1</code>, <code>EXTRA_2</code>, <code>EXTRA_3</code>, <code>EXTRA_4</code></li>
* </ul>
* @public
* @name BUTTONS
* @enum {number}
* @memberof input.GAMEPAD
* @see https://w3c.github.io/gamepad/#remapping
*/
"BUTTONS" : {
"FACE_1" : 0,
"FACE_2" : 1,
"FACE_3" : 2,
"FACE_4" : 3,
"L1" : 4,
"R1" : 5,
"L2" : 6,
"R2" : 7,
"SELECT" : 8,
"BACK" : 8,
"START" : 9,
"FORWARD" : 9,
"L3" : 10,
"R3" : 11,
"UP" : 12,
"DOWN" : 13,
"LEFT" : 14,
"RIGHT" : 15,
"HOME" : 16,
"EXTRA_1" : 17,
"EXTRA_2" : 18,
"EXTRA_3" : 19,
"EXTRA_4" : 20
}
};
/**
* Associate a gamepad event to a keycode
* @name bindGamepad
* @memberof input
* @public
* @param {number} index - Gamepad index
* @param {object} button - Button/Axis definition
* @param {string} button.type - "buttons" or "axes"
* @param {number} button.code - button or axis code id (See {@link input.GAMEPAD.BUTTONS}, {@link input.GAMEPAD.AXES})
* @param {number} [button.threshold=1] - value indicating when the axis should trigger the keycode (e.g. -0.5 or 0.5)
* @param {number} keyCode - (See {@link input.KEY})
* @example
* // enable the keyboard
* me.input.bindKey(me.input.KEY.X, "shoot");
* ...
* // map the lower face button on the first gamepad to the X key
* me.input.bindGamepad(0, {type:"buttons", code: me.input.GAMEPAD.BUTTONS.FACE_1}, me.input.KEY.X);
* // map the left axis value on the first gamepad to the LEFT key
* me.input.bindGamepad(0, {type:"axes", code: me.input.GAMEPAD.AXES.LX, threshold: -0.5}, me.input.KEY.LEFT);
*/
export function bindGamepad(index, button, keyCode) {
// Throw an exception if no action is defined for the specified keycode
if (!getBindingKey(keyCode)) {
throw new Error("no action defined for keycode " + keyCode);
}
// register to the the update event if not yet done and supported by the browser
// if not supported, the function will fail silently (-> update loop won't be called)
if (typeof updateEventHandler === "undefined" && typeof navigator.getGamepads === "function") {
updateEventHandler = event.on(event.GAME_BEFORE_UPDATE, updateGamepads);
}
// Allocate bindings if not defined
if (!bindings[index]) {
bindings[index] = {
"axes" : {},
"buttons" : {}
};
}
let mapping = {
"keyCode" : keyCode,
"value" : 0,
"pressed" : false,
"threshold" : button.threshold // can be undefined
};
let binding = bindings[index][button.type];
// Map the gamepad button or axis to the keycode
if (button.type === "buttons") {
// buttons are defined by a `gamePadButton` object
binding[button.code] = mapping;
} else if (button.type === "axes") {
// normalize threshold into a value that can represent both side of the axis
let range = (Math.sign(button.threshold) || 1);
// axes are defined using two objects; one for negative and one for positive
if (!binding[button.code]) {
binding[button.code] = {};
}
let axes = binding[button.code];
axes[range] = mapping;
// Ensure the opposite axis exists
if (!axes[-range]) {
axes[-range] = {
"keyCode" : 0,
"value" : 0,
"pressed" : false,
"threshold" : -range
};
}
}
}
/**
* unbind the defined keycode
* @name unbindGamepad
* @memberof input
* @public
* @param {number} index - Gamepad index
* @param {number} button - (See {@link input.GAMEPAD.BUTTONS})
* @example
* me.input.unbindGamepad(0, me.input.GAMEPAD.BUTTONS.FACE_1);
*/
export function unbindGamepad(index, button) {
if (!bindings[index]) {
throw new Error("no bindings for gamepad " + index);
}
bindings[index].buttons[button] = {};
}
/**
* Set deadzone for analog gamepad inputs<br>
* The default deadzone is 0.1 (10%) Analog values less than this will be ignored
* @name setGamepadDeadzone
* @memberof input
* @public
* @param {number} value - Deadzone value
*/
export function setGamepadDeadzone(value) {
deadzone = value;
}
/**
* specify a custom mapping for a specific gamepad id<br>
* see below for the default mapping : <br>
* <center><img src="images/gamepad_diagram.png"/></center><br>
* @name setGamepadMapping
* @memberof input
* @public
* @param {string} id - Gamepad id string
* @param {object} mapping - A hash table
* @param {number[]} mapping.axes - Standard analog control stick axis locations
* @param {number[]} mapping.buttons - Standard digital button locations
* @param {number[]} [mapping.analog] - Analog axis locations for buttons
* @param {Function} [mapping.normalize_fn] - a function that returns a normalized value in range [-1.0..1.0] for the given value, axis and button
* @example
* // A weird controller that has its axis mappings reversed
* me.input.setGamepadMapping("Generic USB Controller", {
* "axes" : [ 3, 2, 1, 0 ],
* "buttons" : [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ]
* });
*
* // Mapping extra axes to analog buttons
* me.input.setGamepadMapping("Generic Analog Controller", {
* "axes" : [ 0, 1, 2, 3 ],
* "buttons" : [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ],
*
* // Raw axis 4 is mapped to GAMEPAD.BUTTONS.FACE_1
* // Raw axis 5 is mapped to GAMEPAD.BUTTONS.FACE_2
* // etc...
* // Also maps left and right triggers
* "analog" : [ 4, 5, 6, 7, -1, -1, 8, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1 ],
*
* // Normalize the value of button L2: [-1.0..1.0] => [0.0..1.0]
* "normalize_fn" : function (value, axis, button) {
* return ((button === me.input.GAMEPAD.BUTTONS.L2) ? ((value + 1) / 2) : value) || 0;
* }
* });
*/
export let setGamepadMapping = addMapping;