Recoded gamepad event flow, moved to direct handlers, exposed first 4 pads, added full docs

This commit is contained in:
Richard Davey 2018-06-06 23:05:10 +01:00
parent 4c4a415dc3
commit aa8d207485

View file

@ -16,19 +16,22 @@ var Gamepad = require('./Gamepad');
/** /**
* @typedef {object} Pad * @typedef {object} Pad
* *
* @property {string} id - [description] * @property {string} id - The ID of the Gamepad.
* @property {integer} index - [description] * @property {integer} index - The index of the Gamepad.
*/
/**
* @callback GamepadHandler
*
* @property {GamepadEvent} event - [description]
*/ */
/** /**
* @classdesc * @classdesc
* [description] * The Gamepad Manager is a helper class that belongs to the Input Manager.
*
* Its role is to listen for native DOM Gamepad Events and then process them.
*
* You do not need to create this class directly, the Input Manager will create an instance of it automatically.
*
* You can access it from within a Scene using `this.input.gamepad`. For example, you can do:
*
* ```javascript
* ```
* *
* @class GamepadManager * @class GamepadManager
* @extends Phaser.Events.EventEmitter * @extends Phaser.Events.EventEmitter
@ -36,7 +39,7 @@ var Gamepad = require('./Gamepad');
* @constructor * @constructor
* @since 3.0.0 * @since 3.0.0
* *
* @param {Phaser.Input.InputManager} inputManager - [description] * @param {Phaser.Input.InputManager} inputManager - A reference to the Input Manager.
*/ */
var GamepadManager = new Class({ var GamepadManager = new Class({
@ -49,7 +52,7 @@ var GamepadManager = new Class({
EventEmitter.call(this); EventEmitter.call(this);
/** /**
* [description] * A reference to the Input Manager.
* *
* @name Phaser.Input.Gamepad.GamepadManager#manager * @name Phaser.Input.Gamepad.GamepadManager#manager
* @type {Phaser.Input.InputManager} * @type {Phaser.Input.InputManager}
@ -58,35 +61,28 @@ var GamepadManager = new Class({
this.manager = inputManager; this.manager = inputManager;
/** /**
* [description] * A boolean that controls if the Gamepad Manager is enabled or not.
* Can be toggled on the fly.
* *
* @name Phaser.Input.Gamepad.GamepadManager#enabled * @name Phaser.Input.Gamepad.GamepadManager#enabled
* @type {boolean} * @type {boolean}
* @default false * @default true
* @since 3.0.0 * @since 3.0.0
*/ */
this.enabled = false; this.enabled = true;
/** /**
* [description] * The Gamepad Event target, as defined in the Game Config.
* Typically the browser window, but can be any interactive DOM element.
* *
* @name Phaser.Input.Gamepad.GamepadManager#target * @name Phaser.Input.Gamepad.GamepadManager#target
* @type {?object} * @type {any}
* @since 3.0.0 * @since 3.0.0
*/ */
this.target; this.target;
/** /**
* [description] * An array of the connected Gamepads.
*
* @name Phaser.Input.Gamepad.GamepadManager#handler
* @type {?GamepadHandler}
* @since 3.0.0
*/
this.handler;
/**
* [description]
* *
* @name Phaser.Input.Gamepad.GamepadManager#gamepads * @name Phaser.Input.Gamepad.GamepadManager#gamepads
* @type {Phaser.Input.Gamepad.Gamepad[]} * @type {Phaser.Input.Gamepad.Gamepad[]}
@ -96,20 +92,25 @@ var GamepadManager = new Class({
this.gamepads = []; this.gamepads = [];
/** /**
* Standard FIFO queue. * An internal event queue.
* *
* @name Phaser.Input.Gamepad.GamepadManager#queue * @name Phaser.Input.Gamepad.GamepadManager#queue
* @type {GamepadEvent[]} * @type {GamepadEvent[]}
* @default [] * @private
* @since 3.0.0 * @since 3.0.0
*/ */
this.queue = []; this.queue = [];
this._pad1;
this._pad2;
this._pad3;
this._pad4;
inputManager.events.once('boot', this.boot, this); inputManager.events.once('boot', this.boot, this);
}, },
/** /**
* [description] * The Boot handler is called by Phaser.Game when it first starts up.
* *
* @method Phaser.Input.Gamepad.GamepadManager#boot * @method Phaser.Input.Gamepad.GamepadManager#boot
* @since 3.0.0 * @since 3.0.0
@ -118,9 +119,8 @@ var GamepadManager = new Class({
{ {
var config = this.manager.config; var config = this.manager.config;
this.enabled = config.inputGamepad && this.manager.game.device.input.gamepads; this.enabled = (config.inputGamepad && this.manager.game.device.input.gamepads);
this.target = config.inputGamepadEventTarget;
this.target = window;
if (this.enabled) if (this.enabled)
{ {
@ -129,44 +129,74 @@ var GamepadManager = new Class({
}, },
/** /**
* [description] * The Gamepad Connected Event Handler.
*
* @method Phaser.Input.Gamepad.GamepadManager#onGamepadConnected
* @since 3.10.0
*
* @param {GamepadEvent} event - The native DOM Gamepad Event.
*/
onGamepadConnected: function (event)
{
// console.log(event);
if (event.defaultPrevented || !this.enabled)
{
// Do nothing if event already handled
return;
}
this.refreshPads();
this.queue.push(event);
},
/**
* The Gamepad Disconnected Event Handler.
*
* @method Phaser.Input.Gamepad.GamepadManager#onGamepadDisconnected
* @since 3.10.0
*
* @param {GamepadEvent} event - The native DOM Gamepad Event.
*/
onGamepadDisconnected: function (event)
{
if (event.defaultPrevented || !this.enabled)
{
// Do nothing if event already handled
return;
}
this.refreshPads();
this.queue.push(event);
},
/**
* Starts the Gamepad Event listeners running.
* This is called automatically and does not need to be manually invoked.
* *
* @method Phaser.Input.Gamepad.GamepadManager#startListeners * @method Phaser.Input.Gamepad.GamepadManager#startListeners
* @since 3.0.0 * @since 3.0.0
*/ */
startListeners: function () startListeners: function ()
{ {
var queue = this.queue;
var handler = function handler (event)
{
if (event.defaultPrevented)
{
// Do nothing if event already handled
return;
}
queue.push(event);
};
this.handler = handler;
var target = this.target; var target = this.target;
target.addEventListener('gamepadconnected', handler, false); target.addEventListener('gamepadconnected', this.onGamepadConnected.bind(this), false);
target.addEventListener('gamepaddisconnected', handler, false); target.addEventListener('gamepaddisconnected', this.onGamepadDisconnected.bind(this), false);
// FF only for now: // FF also supports gamepadbuttondown, gamepadbuttonup and gamepadaxismove but
target.addEventListener('gamepadbuttondown', handler, false); // nothing else does, and we can get those values via the gamepads anyway, so we will
target.addEventListener('gamepadbuttonup', handler, false); // until more browsers support this
target.addEventListener('gamepadaxismove', handler, false);
// Finally, listen for an update event from the Input Manager // Finally, listen for an update event from the Input Manager
this.manager.events.on('update', this.update, this); this.manager.events.on('update', this.update, this);
}, },
/** /**
* [description] * Stops the Gamepad Event listeners.
* This is called automatically and does not need to be manually invoked.
* *
* @method Phaser.Input.Gamepad.GamepadManager#stopListeners * @method Phaser.Input.Gamepad.GamepadManager#stopListeners
* @since 3.0.0 * @since 3.0.0
@ -174,20 +204,15 @@ var GamepadManager = new Class({
stopListeners: function () stopListeners: function ()
{ {
var target = this.target; var target = this.target;
var handler = this.handler;
target.removeEventListener('gamepadconnected', handler); target.removeEventListener('gamepadconnected', this.onGamepadConnected);
target.removeEventListener('gamepaddisconnected', handler); target.removeEventListener('gamepaddisconnected', this.onGamepadDisconnected);
target.removeEventListener('gamepadbuttondown', handler);
target.removeEventListener('gamepadbuttonup', handler);
target.removeEventListener('gamepadaxismove', handler);
this.manager.events.off('update', this.update); this.manager.events.off('update', this.update);
}, },
/** /**
* [description] * Disconnects all current Gamepads.
* *
* @method Phaser.Input.Gamepad.GamepadManager#disconnectAll * @method Phaser.Input.Gamepad.GamepadManager#disconnectAll
* @since 3.0.0 * @since 3.0.0
@ -201,92 +226,99 @@ var GamepadManager = new Class({
}, },
/** /**
* [description] * Refreshes the list of connected Gamepads.
* *
* @method Phaser.Input.Gamepad.GamepadManager#addPad * This is called automatically when a gamepad is connected or disconnected,
* @since 3.0.0 * and during the update loop.
*
* @param {Pad} pad - [description]
*
* @return {Phaser.Input.Gamepad.Gamepad} [description]
*/
addPad: function (pad)
{
var gamepad = new Gamepad(this, pad.id, pad.index);
this.gamepads[pad.index] = gamepad;
return gamepad;
},
/**
* [description]
*
* @method Phaser.Input.Gamepad.GamepadManager#removePad
* @since 3.0.0
* @todo Code this feature
*
* @param {number} index - [description]
* @param {Pad} pad - [description]
*/
removePad: function ()
{
// TODO
},
/**
* [description]
* *
* @method Phaser.Input.Gamepad.GamepadManager#refreshPads * @method Phaser.Input.Gamepad.GamepadManager#refreshPads
* @private
* @since 3.0.0 * @since 3.0.0
*
* @param {Pad[]} pads - [description]
*/ */
refreshPads: function (pads) refreshPads: function ()
{ {
if (!pads) var connectedPads = navigator.getGamepads();
if (!connectedPads)
{ {
this.disconnectAll(); this.disconnectAll();
} }
else else
{ {
for (var i = 0; i < pads.length; i++) var currentPads = this.gamepads;
{
var pad = pads[i];
if (!pad) for (var i = 0; i < connectedPads.length; i++)
{
var livePad = connectedPads[i];
// Because sometimes they're null (yes, really)
if (!livePad)
{ {
// removePad?
continue; continue;
} }
if (this.gamepads[pad.index] === undefined) var id = livePad.id;
{ var index = livePad.index;
this.addPad(pad); var currentPad = currentPads[index];
}
this.gamepads[pad.index].update(pad); if (!currentPad)
{
// A new Gamepad, not currently stored locally
var newPad = new Gamepad(this, livePad);
currentPads[index] = newPad;
if (!this._pad1)
{
this._pad1 = newPad;
}
else if (!this._pad2)
{
this._pad2 = newPad;
}
else if (!this._pad3)
{
this._pad3 = newPad;
}
else if (!this._pad4)
{
this._pad4 = newPad;
}
}
else if (currentPad.id !== id)
{
// A new Gamepad with a different vendor string, but it has got the same index as an old one
currentPad.destroy();
currentPads[index] = new Gamepad(this, livePad);
}
else
{
// If neither of these, it's a pad we've already got, so update it
currentPad.update(livePad);
}
} }
} }
}, },
/** /**
* [description] * Returns an array of all currently connected Gamepads.
* *
* @method Phaser.Input.Gamepad.GamepadManager#getAll * @method Phaser.Input.Gamepad.GamepadManager#getAll
* @since 3.0.0 * @since 3.0.0
* *
* @return {Phaser.Input.Gamepad.Gamepad[]} [description] * @return {Phaser.Input.Gamepad.Gamepad[]} An array of all currently connected Gamepads.
*/ */
getAll: function () getAll: function ()
{ {
var out = []; var out = [];
var pads = this.gamepads;
for (var i = 0; i < this.gamepads.length; i++) for (var i = 0; i < pads.length; i++)
{ {
if (this.gamepads[i]) if (pads[i])
{ {
out.push(this.gamepads[i]); out.push(pads[i]);
} }
} }
@ -294,30 +326,35 @@ var GamepadManager = new Class({
}, },
/** /**
* [description] * Looks-up a single Gamepad based on the given index value.
* *
* @method Phaser.Input.Gamepad.GamepadManager#getPad * @method Phaser.Input.Gamepad.GamepadManager#getPad
* @since 3.0.0 * @since 3.0.0
* *
* @param {number} index - [description] * @param {number} index - The index of the Gamepad to get.
* *
* @return {Phaser.Input.Gamepad.Gamepad} [description] * @return {Phaser.Input.Gamepad.Gamepad} The Gamepad matching the given index, or undefined if none were found.
*/ */
getPad: function (index) getPad: function (index)
{ {
for (var i = 0; i < this.gamepads.length; i++) var pads = this.gamepads;
for (var i = 0; i < pads.length; i++)
{ {
if (this.gamepads[i].index === index) if (pads[i] && pads[i].index === index)
{ {
return this.gamepads[i]; return pads[i];
} }
} }
}, },
/** /**
* [description] * The internal update loop. Refreshes all connected gamepads and processes their events.
*
* Called automatically by the Input Manager, invoked from the Game step.
* *
* @method Phaser.Input.Gamepad.GamepadManager#update * @method Phaser.Input.Gamepad.GamepadManager#update
* @private
* @since 3.0.0 * @since 3.0.0
*/ */
update: function () update: function ()
@ -327,7 +364,7 @@ var GamepadManager = new Class({
return; return;
} }
this.refreshPads(navigator.getGamepads()); this.refreshPads();
var len = this.queue.length; var len = this.queue.length;
@ -366,7 +403,7 @@ var GamepadManager = new Class({
}, },
/** /**
* [description] * Destroys this Gamepad Manager, disconnecting all Gamepads and releasing internal references.
* *
* @method Phaser.Input.Gamepad.GamepadManager#destroy * @method Phaser.Input.Gamepad.GamepadManager#destroy
* @since 3.0.0 * @since 3.0.0
@ -376,14 +413,27 @@ var GamepadManager = new Class({
this.stopListeners(); this.stopListeners();
this.disconnectAll(); this.disconnectAll();
this.removeAllListeners();
for (var i = 0; i < this.gamepads.length; i++)
{
if (this.gamepads[i])
{
this.gamepads[i].destroy();
}
}
this.gamepads = []; this.gamepads = [];
this.target = null;
this.manager = null;
}, },
/** /**
* The total number of connected game pads. * The total number of connected game pads.
* *
* @name Phaser.Input.Gamepad.GamepadManager#total * @name Phaser.Input.Gamepad.GamepadManager#total
* @type {number} * @type {integer}
* @since 3.0.0 * @since 3.0.0
*/ */
total: { total: {
@ -393,6 +443,86 @@ var GamepadManager = new Class({
return this.gamepads.length; return this.gamepads.length;
} }
},
/**
* A reference to the first connected Gamepad.
*
* This will be undefined if either no pads are connected, or the browser
* has not yet issued a gamepadconnect, which can happen even if a Gamepad
* is plugged in, but hasn't yet had any buttons pressed on it.
*
* @name Phaser.Input.Gamepad.GamepadManager#pad1
* @type {Phaser.Input.Gamepad.Gamepad}
* @since 3.10.0
*/
pad1: {
get: function ()
{
return this._pad1;
}
},
/**
* A reference to the second connected Gamepad.
*
* This will be undefined if either no pads are connected, or the browser
* has not yet issued a gamepadconnect, which can happen even if a Gamepad
* is plugged in, but hasn't yet had any buttons pressed on it.
*
* @name Phaser.Input.Gamepad.GamepadManager#pad2
* @type {Phaser.Input.Gamepad.Gamepad}
* @since 3.10.0
*/
pad2: {
get: function ()
{
return this._pad2;
}
},
/**
* A reference to the third connected Gamepad.
*
* This will be undefined if either no pads are connected, or the browser
* has not yet issued a gamepadconnect, which can happen even if a Gamepad
* is plugged in, but hasn't yet had any buttons pressed on it.
*
* @name Phaser.Input.Gamepad.GamepadManager#pad3
* @type {Phaser.Input.Gamepad.Gamepad}
* @since 3.10.0
*/
pad3: {
get: function ()
{
return this._pad3;
}
},
/**
* A reference to the fourth connected Gamepad.
*
* This will be undefined if either no pads are connected, or the browser
* has not yet issued a gamepadconnect, which can happen even if a Gamepad
* is plugged in, but hasn't yet had any buttons pressed on it.
*
* @name Phaser.Input.Gamepad.GamepadManager#pad4
* @type {Phaser.Input.Gamepad.Gamepad}
* @since 3.10.0
*/
pad4: {
get: function ()
{
return this._pad4;
}
} }
}); });