phaser/src/input/InputManager.js
Richard Davey a2c20a9aa7 lint fixes
2018-06-05 01:08:32 +01:00

1261 lines
40 KiB
JavaScript

/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2018 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
var Class = require('../utils/Class');
var CONST = require('./const');
var EventEmitter = require('eventemitter3');
var Gamepad = require('./gamepad/GamepadManager');
var Keyboard = require('./keyboard/KeyboardManager');
var Mouse = require('./mouse/MouseManager');
var Pointer = require('./Pointer');
var Rectangle = require('../geom/rectangle/Rectangle');
var Touch = require('./touch/TouchManager');
var TransformMatrix = require('../gameobjects/components/TransformMatrix');
var TransformXY = require('../math/TransformXY');
/**
* @classdesc
* The Input Manager is responsible for handling all of the input related systems in a single Phaser Game instance.
*
* Based on the Game Config it will create handlers for mouse, touch, keyboard and gamepad support.
*
* It then manages the event queue, pointer creation and general hit test related operations.
*
* You rarely need to interact with the Input Manager directly, and as such, all of its properties and methods
* should be considered private. Instead, you should use the Input Plugin, which is a Scene level system, responsible
* for dealing with all input events for a Scene.
*
* @class InputManager
* @memberOf Phaser.Input
* @constructor
* @since 3.0.0
*
* @param {Phaser.Game} game - The Game instance that owns the Input Manager.
* @param {object} config - The Input Configuration object, as set in the Game Config.
*/
var InputManager = new Class({
initialize:
function InputManager (game, config)
{
/**
* The Game instance that owns the Input Manager.
* A Game only maintains on instance of the Input Manager at any time.
*
* @name Phaser.Input.InputManager#game
* @type {Phaser.Game}
* @readOnly
* @since 3.0.0
*/
this.game = game;
/**
* The Canvas that is used for all DOM event input listeners.
*
* @name Phaser.Input.InputManager#canvas
* @type {HTMLCanvasElement}
* @since 3.0.0
*/
this.canvas;
/**
* The Input Configuration object, as set in the Game Config.
*
* @name Phaser.Input.InputManager#config
* @type {object}
* @since 3.0.0
*/
this.config = config;
/**
* If set, the Input Manager will run its update loop every frame.
*
* @name Phaser.Input.InputManager#enabled
* @type {boolean}
* @default true
* @since 3.0.0
*/
this.enabled = true;
/**
* The Event Emitter instance that the Input Manager uses to emit events from.
*
* @name Phaser.Input.InputManager#events
* @type {Phaser.Events.EventEmitter}
* @since 3.0.0
*/
this.events = new EventEmitter();
/**
* A standard FIFO queue for the native DOM events waiting to be handled by the Input Manager.
*
* @name Phaser.Input.InputManager#queue
* @type {array}
* @default []
* @since 3.0.0
*/
this.queue = [];
/**
* DOM Callbacks container.
*
* @name Phaser.Input.InputManager#domCallbacks
* @private
* @type {object}
* @since 3.10.0
*/
this.domCallbacks = { up: [], down: [], move: [], upOnce: [], downOnce: [], moveOnce: [] };
/**
* Are there any up callbacks defined?
*
* @name Phaser.Input.InputManager#_hasUpCallback
* @private
* @type {boolean}
* @since 3.10.0
*/
this._hasUpCallback = false;
/**
* Are there any down callbacks defined?
*
* @name Phaser.Input.InputManager#_hasDownCallback
* @private
* @type {boolean}
* @since 3.10.0
*/
this._hasDownCallback = false;
/**
* Are there any move callbacks defined?
*
* @name Phaser.Input.InputManager#_hasMoveCallback
* @private
* @type {boolean}
* @since 3.10.0
*/
this._hasMoveCallback = false;
/**
* A reference to the Keyboard Manager class, if enabled via the `input.keyboard` Game Config property.
*
* @name Phaser.Input.InputManager#keyboard
* @type {?Phaser.Input.Keyboard.KeyboardManager}
* @since 3.0.0
*/
this.keyboard = (config.inputKeyboard) ? new Keyboard(this) : null;
/**
* A reference to the Mouse Manager class, if enabled via the `input.mouse` Game Config property.
*
* @name Phaser.Input.InputManager#mouse
* @type {?Phaser.Input.Mouse.MouseManager}
* @since 3.0.0
*/
this.mouse = (config.inputMouse) ? new Mouse(this) : null;
/**
* A reference to the Touch Manager class, if enabled via the `input.touch` Game Config property.
*
* @name Phaser.Input.InputManager#touch
* @type {Phaser.Input.Touch.TouchManager}
* @since 3.0.0
*/
this.touch = (config.inputTouch) ? new Touch(this) : null;
/**
* A reference to the Gamepad Manager class, if enabled via the `input.gamepad` Game Config property.
*
* @name Phaser.Input.InputManager#gamepad
* @type {Phaser.Input.Gamepad.GamepadManager}
* @since 3.0.0
*/
this.gamepad = (config.inputGamepad) ? new Gamepad(this) : null;
/**
* An array of Pointers that have been added to the game.
* The first entry is reserved for the Mouse Pointer, the rest are Touch Pointers.
*
* By default there is 1 touch pointer enabled. If you need more use the `addPointer` method to start them,
* or set the `input.activePointers` property in the Game Config.
*
* @name Phaser.Input.InputManager#pointers
* @type {Phaser.Input.Pointer[]}
* @since 3.10.0
*/
this.pointers = [];
/**
* The number of touch objects activated and being processed each update.
*
* You can change this by either calling `addPointer` at run-time, or by
* setting the `input.activePointers` property in the Game Config.
*
* @name Phaser.Input.InputManager#pointersTotal
* @type {integer}
* @readOnly
* @since 3.10.0
*/
this.pointersTotal = config.inputActivePointers;
for (var i = 0; i <= this.pointersTotal; i++)
{
this.pointers.push(new Pointer(this, i));
}
/**
* The mouse has its own unique Pointer object, which you can reference directly if making a _desktop specific game_.
* If you are supporting both desktop and touch devices then do not use this property, instead use `activePointer`
* which will always map to the most recently interacted pointer.
*
* @name Phaser.Input.InputManager#mousePointer
* @type {?Phaser.Input.Pointer}
* @since 3.10.0
*/
this.mousePointer = (config.inputMouse) ? this.pointers[0] : null;
/**
* The most recently active Pointer object.
*
* If you've only 1 Pointer in your game then this will accurately be either the first finger touched, or the mouse.
*
* If your game doesn't need to support multi-touch then you can safely use this property in all of your game
* code and it will adapt to be either the mouse or the touch, based on device.
*
* @name Phaser.Input.InputManager#activePointer
* @type {Phaser.Input.Pointer}
* @since 3.0.0
*/
this.activePointer = this.pointers[0];
/**
* Reset every frame. Set to `true` if any of the Pointers are dirty this frame.
*
* @name Phaser.Input.InputManager#dirty
* @type {boolean}
* @since 3.10.0
*/
this.dirty = false;
/**
* The Scale factor being applied to input coordinates.
*
* @name Phaser.Input.InputManager#scale
* @type { { x:number, y:number } }
* @since 3.0.0
*/
this.scale = { x: 1, y: 1 };
/**
* If the top-most Scene in the Scene List receives an input it will stop input from
* propagating any lower down the scene list, i.e. if you have a UI Scene at the top
* and click something on it, that click will not then be passed down to any other
* Scene below. Disable this to have input events passed through all Scenes, all the time.
*
* @name Phaser.Input.InputManager#globalTopOnly
* @type {boolean}
* @default true
* @since 3.0.0
*/
this.globalTopOnly = true;
/**
* An internal flag that controls if the Input Manager will ignore or process native DOM events this frame.
* Set via the InputPlugin.stopPropagation method.
*
* @name Phaser.Input.InputManager#ignoreEvents
* @type {boolean}
* @default false
* @since 3.0.0
*/
this.ignoreEvents = false;
/**
* The bounds of the Input Manager, used for pointer hit test calculations.
*
* @name Phaser.Input.InputManager#bounds
* @type {Phaser.Geom.Rectangle}
* @since 3.0.0
*/
this.bounds = new Rectangle();
/**
* A re-cycled point-like object to store hit test values in.
*
* @name Phaser.Input.InputManager#_tempPoint
* @type {{x:number,y:number}}
* @private
* @since 3.0.0
*/
this._tempPoint = { x: 0, y: 0 };
/**
* A re-cycled array to store hit results in.
*
* @name Phaser.Input.InputManager#_tempHitTest
* @type {array}
* @private
* @default []
* @since 3.0.0
*/
this._tempHitTest = [];
/**
* A re-cycled matrix used in hit test calculations.
*
* @name Phaser.Input.InputManager#_tempMatrix
* @type {Phaser.GameObjects.Components.TransformMatrix}
* @private
* @since 3.4.0
*/
this._tempMatrix = new TransformMatrix();
game.events.once('boot', this.boot, this);
},
/**
* The Boot handler is called by Phaser.Game when it first starts up.
* The renderer is available by now.
*
* @method Phaser.Input.InputManager#boot
* @protected
* @since 3.0.0
*/
boot: function ()
{
this.canvas = this.game.canvas;
this.updateBounds();
this.events.emit('boot');
this.game.events.on('prestep', this.update, this);
this.game.events.once('destroy', this.destroy, this);
},
/**
* Updates the Input Manager bounds rectangle to match the bounding client rectangle of the
* canvas element being used to track input events.
*
* @method Phaser.Input.InputManager#updateBounds
* @since 3.0.0
*/
updateBounds: function ()
{
var bounds = this.bounds;
var clientRect = this.canvas.getBoundingClientRect();
bounds.x = clientRect.left + window.pageXOffset - document.documentElement.clientLeft;
bounds.y = clientRect.top + window.pageYOffset - document.documentElement.clientTop;
bounds.width = clientRect.width;
bounds.height = clientRect.height;
},
/**
* Resizes the Input Manager internal values, including the bounds and scale factor.
*
* @method Phaser.Input.InputManager#resize
* @since 3.2.0
*/
resize: function ()
{
this.updateBounds();
// Game config size
var gw = this.game.config.width;
var gh = this.game.config.height;
// Actual canvas size
var bw = this.bounds.width;
var bh = this.bounds.height;
// Scale factor
this.scale.x = gw / bw;
this.scale.y = gh / bh;
},
/**
* Internal update loop, called automatically by the Game Step.
*
* @method Phaser.Input.InputManager#update
* @private
* @since 3.0.0
*
* @param {number} time - The time stamp value of this game step.
*/
update: function (time)
{
var i;
this.events.emit('update');
this.ignoreEvents = false;
this.dirty = false;
var len = this.queue.length;
var pointers = this.pointers;
for (i = 0; i < this.pointersTotal; i++)
{
pointers[i].reset();
}
if (!this.enabled || len === 0)
{
return;
}
this.dirty = true;
this.updateBounds();
this.scale.x = this.game.config.width / this.bounds.width;
this.scale.y = this.game.config.height / this.bounds.height;
// Clears the queue array, and also means we don't work on array data that could potentially
// be modified during the processing phase
var queue = this.queue.splice(0, len);
var mouse = this.mousePointer;
// Process the event queue, dispatching all of the events that have stored up
for (i = 0; i < len; i += 2)
{
var type = queue[i];
var event = queue[i + 1];
switch (type)
{
case CONST.MOUSE_DOWN:
mouse.down(event, time);
break;
case CONST.MOUSE_MOVE:
mouse.move(event, time);
break;
case CONST.MOUSE_UP:
mouse.up(event, time);
break;
case CONST.TOUCH_START:
this.startPointer(event, time);
break;
case CONST.TOUCH_MOVE:
this.updatePointer(event, time);
break;
case CONST.TOUCH_END:
this.stopPointer(event, time);
break;
case CONST.POINTER_LOCK_CHANGE:
this.events.emit('pointerlockchange', event, this.mouse.locked);
break;
}
}
},
// event.targetTouches = list of all touches on the TARGET ELEMENT (i.e. game dom element)
// event.touches = list of all touches on the ENTIRE DOCUMENT, not just the target element
// event.changedTouches = the touches that CHANGED in this event, not the total number of them
/**
* Called by the main update loop when a Touch Start Event is received.
*
* @method Phaser.Input.InputManager#startPointer
* @private
* @since 3.10.0
*
* @param {TouchEvent} event - The native DOM event to be processed.
* @param {number} time - The time stamp value of this game step.
*/
startPointer: function (event, time)
{
var pointers = this.pointers;
for (var c = 0; c < event.changedTouches.length; c++)
{
var changedTouch = event.changedTouches[c];
for (var i = 1; i < this.pointersTotal; i++)
{
var pointer = pointers[i];
if (!pointer.active)
{
pointer.touchstart(changedTouch, time);
this.activePointer = pointer;
break;
}
}
}
},
/**
* Called by the main update loop when a Touch Move Event is received.
*
* @method Phaser.Input.InputManager#updatePointer
* @private
* @since 3.10.0
*
* @param {TouchEvent} event - The native DOM event to be processed.
* @param {number} time - The time stamp value of this game step.
*/
updatePointer: function (event, time)
{
var pointers = this.pointers;
for (var c = 0; c < event.changedTouches.length; c++)
{
var changedTouch = event.changedTouches[c];
for (var i = 1; i < this.pointersTotal; i++)
{
var pointer = pointers[i];
if (pointer.active && pointer.identifier === changedTouch.identifier)
{
pointer.touchmove(changedTouch, time);
this.activePointer = pointer;
break;
}
}
}
},
// For touch end its a list of the touch points that have been removed from the surface
// https://developer.mozilla.org/en-US/docs/DOM/TouchList
// event.changedTouches = the touches that CHANGED in this event, not the total number of them
/**
* Called by the main update loop when a Touch End Event is received.
*
* @method Phaser.Input.InputManager#stopPointer
* @private
* @since 3.10.0
*
* @param {TouchEvent} event - The native DOM event to be processed.
* @param {number} time - The time stamp value of this game step.
*/
stopPointer: function (event, time)
{
var pointers = this.pointers;
for (var c = 0; c < event.changedTouches.length; c++)
{
var changedTouch = event.changedTouches[c];
for (var i = 1; i < this.pointersTotal; i++)
{
var pointer = pointers[i];
if (pointer.active && pointer.identifier === changedTouch.identifier)
{
pointer.touchend(changedTouch, time);
break;
}
}
}
},
/**
* Adds new Pointer objects to the Input Manager.
*
* By default Phaser creates 2 pointer objects: `mousePointer` and `pointer1`.
*
* You can create more either by calling this method, or by setting the `input.activePointers` property
* in the Game Config.
*
* The first 10 pointers are available via the `InputPlugin.pointerX` properties, once they have been added
* via this method.
*
* @method Phaser.Input.InputManager#addPointer
* @since 3.10.0
*
* @param {integer} [quantity=1] The number of new Pointers to create.
*
* @return {Phaser.Input.Pointer[]} An array containing all of the new Pointer objects that were created.
*/
addPointer: function (quantity)
{
if (quantity === undefined) { quantity = 1; }
var output = [];
for (var i = 0; i < quantity; i++)
{
var id = this.pointers.length;
var pointer = new Pointer(this, id);
this.pointers.push(pointer);
this.pointersTotal++;
output.push(pointer);
}
return output;
},
/**
* Process any pending DOM callbacks.
*
* @method Phaser.Input.InputManager#processDomCallbacks
* @private
* @since 3.10.0
*
* @param {array} once - The isOnce callbacks to invoke.
* @param {array} every - The every frame callbacks to invoke.
* @param {any} event - The native DOM event that is passed to the callbacks.
*
* @return {boolean} `true` if there are callbacks still in the list, otherwise `false`.
*/
processDomCallbacks: function (once, every, event)
{
var i = 0;
for (i = 0; i < once.length; i++)
{
once[i](event);
}
for (i = 0; i < every.length; i++)
{
every[i](event);
}
once = [];
return (every.length > 0);
},
/**
* Queues a touch start event, as passed in by the TouchManager.
* Also dispatches any DOM callbacks for this event.
*
* @method Phaser.Input.InputManager#queueTouchStart
* @private
* @since 3.10.0
*
* @param {TouchEvent} event - The native DOM Touch event.
*/
queueTouchStart: function (event)
{
this.queue.push(CONST.TOUCH_START, event);
if (this._hasDownCallback)
{
var callbacks = this.domCallbacks;
this._hasDownCallback = this.processDomCallbacks(callbacks.downOnce, callbacks.down, event);
}
},
/**
* Queues a touch move event, as passed in by the TouchManager.
* Also dispatches any DOM callbacks for this event.
*
* @method Phaser.Input.InputManager#queueTouchMove
* @private
* @since 3.10.0
*
* @param {TouchEvent} event - The native DOM Touch event.
*/
queueTouchMove: function (event)
{
this.queue.push(CONST.TOUCH_MOVE, event);
if (this._hasMoveCallback)
{
var callbacks = this.domCallbacks;
this._hasMoveCallback = this.processDomCallbacks(callbacks.moveOnce, callbacks.move, event);
}
},
/**
* Queues a touch end event, as passed in by the TouchManager.
* Also dispatches any DOM callbacks for this event.
*
* @method Phaser.Input.InputManager#queueTouchEnd
* @private
* @since 3.10.0
*
* @param {TouchEvent} event - The native DOM Touch event.
*/
queueTouchEnd: function (event)
{
this.queue.push(CONST.TOUCH_END, event);
if (this._hasUpCallback)
{
var callbacks = this.domCallbacks;
this._hasUpCallback = this.processDomCallbacks(callbacks.upOnce, callbacks.up, event);
}
},
/**
* Queues a mouse down event, as passed in by the MouseManager.
* Also dispatches any DOM callbacks for this event.
*
* @method Phaser.Input.InputManager#queueMouseDown
* @private
* @since 3.10.0
*
* @param {MouseEvent} event - The native DOM Mouse event.
*/
queueMouseDown: function (event)
{
this.queue.push(CONST.MOUSE_DOWN, event);
if (this._hasDownCallback)
{
var callbacks = this.domCallbacks;
this._hasDownCallback = this.processDomCallbacks(callbacks.downOnce, callbacks.down, event);
}
},
/**
* Queues a mouse move event, as passed in by the MouseManager.
* Also dispatches any DOM callbacks for this event.
*
* @method Phaser.Input.InputManager#queueMouseMove
* @private
* @since 3.10.0
*
* @param {MouseEvent} event - The native DOM Mouse event.
*/
queueMouseMove: function (event)
{
this.queue.push(CONST.MOUSE_MOVE, event);
if (this._hasMoveCallback)
{
var callbacks = this.domCallbacks;
this._hasMoveCallback = this.processDomCallbacks(callbacks.moveOnce, callbacks.move, event);
}
},
/**
* Queues a mouse up event, as passed in by the MouseManager.
* Also dispatches any DOM callbacks for this event.
*
* @method Phaser.Input.InputManager#queueMouseUp
* @private
* @since 3.10.0
*
* @param {MouseEvent} event - The native DOM Mouse event.
*/
queueMouseUp: function (event)
{
this.queue.push(CONST.MOUSE_UP, event);
if (this._hasUpCallback)
{
var callbacks = this.domCallbacks;
this._hasUpCallback = this.processDomCallbacks(callbacks.upOnce, callbacks.up, event);
}
},
/**
* Adds a callback to be invoked whenever the native DOM `mouseup` or `touchend` events are received.
* By setting the `isOnce` argument you can control if the callback is called once,
* or every time the DOM event occurs.
*
* Callbacks passed to this method are invoked _immediately_ when the DOM event happens,
* within the scope of the DOM event handler. Therefore, they are considered as 'native'
* from the perspective of the browser. This means they can be used for tasks such as
* opening new browser windows, or anything which explicitly requires user input to activate.
* However, as a result of this, they come with their own risks, and as such should not be used
* for general game input, but instead be reserved for special circumstances.
*
* If all you're trying to do is execute a callback when a pointer is released, then
* please use the internal Input event system instead.
*
* Please understand that these callbacks are invoked when the browser feels like doing so,
* which may be entirely out of the normal flow of the Phaser Game Loop. Therefore, you should absolutely keep
* Phaser related operations to a minimum in these callbacks. For example, don't destroy Game Objects,
* change Scenes or manipulate internal systems, otherwise you run a very real risk of creating
* heisenbugs (https://en.wikipedia.org/wiki/Heisenbug) that prove a challenge to reproduce, never mind
* solve.
*
* @method Phaser.Input.InputManager#addUpCallback
* @since 3.10.0
*
* @param {function} callback - The callback to be invoked on this dom event.
* @param {boolean} [isOnce=true] - `true` if the callback will only be invoked once, `false` to call every time this event happens.
*
* @return {this} The Input Manager.
*/
addUpCallback: function (callback, isOnce)
{
if (isOnce === undefined) { isOnce = true; }
if (isOnce)
{
this.domCallbacks.upOnce.push(callback);
}
else
{
this.domCallbacks.up.push(callback);
}
this._hasUpCallback = true;
return this;
},
/**
* Adds a callback to be invoked whenever the native DOM `mousedown` or `touchstart` events are received.
* By setting the `isOnce` argument you can control if the callback is called once,
* or every time the DOM event occurs.
*
* Callbacks passed to this method are invoked _immediately_ when the DOM event happens,
* within the scope of the DOM event handler. Therefore, they are considered as 'native'
* from the perspective of the browser. This means they can be used for tasks such as
* opening new browser windows, or anything which explicitly requires user input to activate.
* However, as a result of this, they come with their own risks, and as such should not be used
* for general game input, but instead be reserved for special circumstances.
*
* If all you're trying to do is execute a callback when a pointer is down, then
* please use the internal Input event system instead.
*
* Please understand that these callbacks are invoked when the browser feels like doing so,
* which may be entirely out of the normal flow of the Phaser Game Loop. Therefore, you should absolutely keep
* Phaser related operations to a minimum in these callbacks. For example, don't destroy Game Objects,
* change Scenes or manipulate internal systems, otherwise you run a very real risk of creating
* heisenbugs (https://en.wikipedia.org/wiki/Heisenbug) that prove a challenge to reproduce, never mind
* solve.
*
* @method Phaser.Input.InputManager#addDownCallback
* @since 3.10.0
*
* @param {function} callback - The callback to be invoked on this dom event.
* @param {boolean} [isOnce=true] - `true` if the callback will only be invoked once, `false` to call every time this event happens.
*
* @return {this} The Input Manager.
*/
addDownCallback: function (callback, isOnce)
{
if (isOnce === undefined) { isOnce = true; }
if (isOnce)
{
this.domCallbacks.downOnce.push(callback);
}
else
{
this.domCallbacks.down.push(callback);
}
this._hasDownCallback = true;
return this;
},
/**
* Adds a callback to be invoked whenever the native DOM `mousemove` or `touchmove` events are received.
* By setting the `isOnce` argument you can control if the callback is called once,
* or every time the DOM event occurs.
*
* Callbacks passed to this method are invoked _immediately_ when the DOM event happens,
* within the scope of the DOM event handler. Therefore, they are considered as 'native'
* from the perspective of the browser. This means they can be used for tasks such as
* opening new browser windows, or anything which explicitly requires user input to activate.
* However, as a result of this, they come with their own risks, and as such should not be used
* for general game input, but instead be reserved for special circumstances.
*
* If all you're trying to do is execute a callback when a pointer is moved, then
* please use the internal Input event system instead.
*
* Please understand that these callbacks are invoked when the browser feels like doing so,
* which may be entirely out of the normal flow of the Phaser Game Loop. Therefore, you should absolutely keep
* Phaser related operations to a minimum in these callbacks. For example, don't destroy Game Objects,
* change Scenes or manipulate internal systems, otherwise you run a very real risk of creating
* heisenbugs (https://en.wikipedia.org/wiki/Heisenbug) that prove a challenge to reproduce, never mind
* solve.
*
* @method Phaser.Input.InputManager#addMoveCallback
* @since 3.10.0
*
* @param {function} callback - The callback to be invoked on this dom event.
* @param {boolean} [isOnce=false] - `true` if the callback will only be invoked once, `false` to call every time this event happens.
*
* @return {this} The Input Manager.
*/
addMoveCallback: function (callback, isOnce)
{
if (isOnce === undefined) { isOnce = false; }
if (isOnce)
{
this.domCallbacks.moveOnce.push(callback);
}
else
{
this.domCallbacks.move.push(callback);
}
this._hasMoveCallback = true;
return this;
},
/**
* Checks if the given Game Object should be considered as a candidate for input or not.
*
* Checks if the Game Object has an input component that is enabled, that it will render,
* and finally, if it has a parent, that the parent parent, or any ancestor, is visible or not.
*
* @method Phaser.Input.InputManager#inputCandidate
* @private
* @since 3.10.0
*
* @param {Phaser.GameObjects.GameObject} gameObject - The Game Object to test.
*
* @return {boolean} `true` if the Game Object should be considered for input, otherwise `false`.
*/
inputCandidate: function (gameObject)
{
var input = gameObject.input;
if (!input || !input.enabled || !gameObject.willRender())
{
return false;
}
var visible = true;
var parent = gameObject.parentContainer;
if (parent)
{
do
{
if (!parent.visible)
{
visible = false;
break;
}
parent = parent.parentContainer;
} while (parent);
}
return visible;
},
/**
* Performs a hit test using the given Pointer and camera, against an array of interactive Game Objects.
*
* The Game Objects are culled against the camera, and then the coordinates are translated into the local camera space
* and used to determine if they fall within the remaining Game Objects hit areas or not.
*
* If nothing is matched an empty array is returned.
*
* This method is called automatically by InputPlugin.hitTestPointer and doesn't usually need to be invoked directly.
*
* @method Phaser.Input.InputManager#hitTest
* @since 3.0.0
*
* @param {Phaser.Input.Pointer} pointer - The Pointer to test against.
* @param {array} gameObjects - An array of interactive Game Objects to check.
* @param {Phaser.Cameras.Scene2D.Camera} camera - The Camera which is being tested against.
* @param {array} [output] - An array to store the results in. If not given, a new empty array is created.
*
* @return {array} An array of the Game Objects that were hit during this hit test.
*/
hitTest: function (pointer, gameObjects, camera, output)
{
if (output === undefined) { output = this._tempHitTest; }
var tempPoint = this._tempPoint;
var cameraW = camera.width;
var cameraH = camera.height;
output.length = 0;
var x = pointer.x;
var y = pointer.y;
if (!(x >= camera.x && y >= camera.y && x <= camera.x + cameraW && y <= camera.y + cameraH))
{
return output;
}
// Stores the world point inside of tempPoint
camera.getWorldPoint(x, y, tempPoint);
pointer.worldX = tempPoint.x;
pointer.worldY = tempPoint.y;
var culledGameObjects = camera.cull(gameObjects);
var point = { x: 0, y: 0 };
var res = this.game.config.resolution;
var matrix = this._tempMatrix;
for (var i = 0; i < culledGameObjects.length; i++)
{
var gameObject = culledGameObjects[i];
if (!this.inputCandidate(gameObject))
{
continue;
}
var px = tempPoint.x * res + (camera.scrollX * gameObject.scrollFactorX) - camera.scrollX;
var py = tempPoint.y * res + (camera.scrollY * gameObject.scrollFactorY) - camera.scrollY;
if (gameObject.parentContainer)
{
gameObject.getWorldTransformMatrix(matrix);
TransformXY(px, py, matrix.tx, matrix.ty, matrix.rotation, matrix.scaleX, matrix.scaleY, point);
}
else
{
TransformXY(px, py, gameObject.x, gameObject.y, gameObject.rotation, gameObject.scaleX, gameObject.scaleY, point);
}
if (this.pointWithinHitArea(gameObject, point.x, point.y))
{
output.push(gameObject);
}
}
return output;
},
/**
* Checks if the given x and y coordinate are within the hit area of the Game Object.
*
* This method assumes that the coordinate values have already been translated into the space of the Game Object.
*
* If the coordinates are within the hit area they are set into the Game Objects Input `localX` and `localY` properties.
*
* @method Phaser.Input.InputManager#pointWithinHitArea
* @since 3.0.0
*
* @param {Phaser.GameObjects.GameObject} gameObject - The interactive Game Object to check against.
* @param {number} x - The translated x coordinate for the hit test.
* @param {number} y - The translated y coordinate for the hit test.
*
* @return {boolean} `true` if the coordinates were inside the Game Objects hit area, otherwise `false`.
*/
pointWithinHitArea: function (gameObject, x, y)
{
// Normalize the origin
x += gameObject.displayOriginX;
y += gameObject.displayOriginY;
var input = gameObject.input;
if (input && input.hitAreaCallback(input.hitArea, x, y, gameObject))
{
input.localX = x;
input.localY = y;
return true;
}
else
{
return false;
}
},
/**
* Checks if the given x and y coordinate are within the hit area of the Interactive Object.
*
* This method assumes that the coordinate values have already been translated into the space of the Interactive Object.
*
* If the coordinates are within the hit area they are set into the Interactive Objects Input `localX` and `localY` properties.
*
* @method Phaser.Input.InputManager#pointWithinInteractiveObject
* @since 3.0.0
*
* @param {Phaser.Input.InteractiveObject} object - The Interactive Object to check against.
* @param {number} x - The translated x coordinate for the hit test.
* @param {number} y - The translated y coordinate for the hit test.
*
* @return {boolean} `true` if the coordinates were inside the Game Objects hit area, otherwise `false`.
*/
pointWithinInteractiveObject: function (object, x, y)
{
if (!object.hitArea)
{
return false;
}
// Normalize the origin
x += object.gameObject.displayOriginX;
y += object.gameObject.displayOriginY;
object.localX = x;
object.localY = y;
return object.hitAreaCallback(object.hitArea, x, y, object);
},
/**
* Transforms the pageX and pageY values of a Pointer into the scaled coordinate space of the Input Manager.
*
* @method Phaser.Input.InputManager#transformPointer
* @since 3.10.0
*
* @param {Phaser.Input.Pointer} pointer - The Pointer to transform the values for.
*
* @return {number} The translated value.
*/
transformPointer: function (pointer, pageX, pageY)
{
pointer.x = (pageX - this.bounds.left) * this.scale.x;
pointer.y = (pageY - this.bounds.top) * this.scale.y;
},
/**
* Transforms the pageX value into the scaled coordinate space of the Input Manager.
*
* @method Phaser.Input.InputManager#transformX
* @since 3.0.0
*
* @param {number} pageX - The DOM pageX value.
*
* @return {number} The translated value.
*/
transformX: function (pageX)
{
return (pageX - this.bounds.left) * this.scale.x;
},
/**
* Transforms the pageY value into the scaled coordinate space of the Input Manager.
*
* @method Phaser.Input.InputManager#transformY
* @since 3.0.0
*
* @param {number} pageY - The DOM pageY value.
*
* @return {number} The translated value.
*/
transformY: function (pageY)
{
return (pageY - this.bounds.top) * this.scale.y;
},
/**
* Returns the left offset of the Input bounds.
*
* @method Phaser.Input.InputManager#getOffsetX
* @since 3.0.0
*
* @return {number} The left bounds value.
*/
getOffsetX: function ()
{
return this.bounds.left;
},
/**
* Returns the top offset of the Input bounds.
*
* @method Phaser.Input.InputManager#getOffsetY
* @since 3.0.0
*
* @return {number} The top bounds value.
*/
getOffsetY: function ()
{
return this.bounds.top;
},
/**
* Returns the horizontal Input Scale value.
*
* @method Phaser.Input.InputManager#getScaleX
* @since 3.0.0
*
* @return {number} The horizontal scale factor of the input.
*/
getScaleX: function ()
{
return this.game.config.width / this.bounds.width;
},
/**
* Returns the vertical Input Scale value.
*
* @method Phaser.Input.InputManager#getScaleY
* @since 3.0.0
*
* @return {number} The vertical scale factor of the input.
*/
getScaleY: function ()
{
return this.game.config.height / this.bounds.height;
},
/**
* Destroys the Input Manager and all of its systems.
*
* There is no way to recover from doing this.
*
* @method Phaser.Input.InputManager#destroy
* @since 3.0.0
*/
destroy: function ()
{
this.events.removeAllListeners();
if (this.keyboard)
{
this.keyboard.destroy();
}
if (this.mouse)
{
this.mouse.destroy();
}
if (this.touch)
{
this.touch.destroy();
}
if (this.gamepad)
{
this.gamepad.destroy();
}
for (var i = 0; i < this.pointers.length; i++)
{
this.pointers[i].destroy();
}
this.domCallbacks = {};
this.pointers = [];
this.queue = [];
this._tempHitTest = [];
this._tempMatrix.destroy();
this.canvas = null;
this.game = null;
}
});
module.exports = InputManager;