UNPKG

phaser

Version:

A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.

1,485 lines (1,263 loc) 118 kB
/** * @author Richard Davey <rich@phaser.io> * @copyright 2013-2025 Phaser Studio Inc. * @license {@link https://opensource.org/licenses/MIT|MIT License} */ var Circle = require('../geom/circle/Circle'); var CircleContains = require('../geom/circle/Contains'); var Class = require('../utils/Class'); var CONST = require('./const'); var CreateInteractiveObject = require('./CreateInteractiveObject'); var CreatePixelPerfectHandler = require('./CreatePixelPerfectHandler'); var DistanceBetween = require('../math/distance/DistanceBetween'); var Ellipse = require('../geom/ellipse/Ellipse'); var EllipseContains = require('../geom/ellipse/Contains'); var Events = require('./events'); var EventEmitter = require('eventemitter3'); var GetFastValue = require('../utils/object/GetFastValue'); var GEOM_CONST = require('../geom/const'); var InputPluginCache = require('./InputPluginCache'); var IsPlainObject = require('../utils/object/IsPlainObject'); var PluginCache = require('../plugins/PluginCache'); var Rectangle = require('../geom/rectangle/Rectangle'); var RectangleContains = require('../geom/rectangle/Contains'); var SceneEvents = require('../scene/events'); var Triangle = require('../geom/triangle/Triangle'); var TriangleContains = require('../geom/triangle/Contains'); /** * @classdesc * The Input Plugin belongs to a Scene and handles all input related events and operations for it. * * You can access it from within a Scene using `this.input`. * * It emits events directly. For example, you can do: * * ```javascript * this.input.on('pointerdown', callback, context); * ``` * * To listen for a pointer down event anywhere on the game canvas. * * Game Objects can be enabled for input by calling their `setInteractive` method. After which they * will directly emit input events: * * ```javascript * var sprite = this.add.sprite(x, y, texture); * sprite.setInteractive(); * sprite.on('pointerdown', callback, context); * ``` * * There are lots of game configuration options available relating to input. * See the [Input Config object]{@linkcode Phaser.Types.Core.InputConfig} for more details, including how to deal with Phaser * listening for input events outside of the canvas, how to set a default number of pointers, input * capture settings and more. * * Please also see the Input examples and tutorials for further information. * * **Incorrect input coordinates with Angular** * * If you are using Phaser within Angular, and use nglf or the router, to make the component in which the Phaser game resides * change state (i.e. appear or disappear) then you'll need to notify the Scale Manager about this, as Angular will mess with * the DOM in a way in which Phaser can't detect directly. Call `this.scale.updateBounds()` as part of your game init in order * to refresh the canvas DOM bounds values, which Phaser uses for input point position calculations. * * @class InputPlugin * @extends Phaser.Events.EventEmitter * @memberof Phaser.Input * @constructor * @since 3.0.0 * * @param {Phaser.Scene} scene - A reference to the Scene that this Input Plugin is responsible for. */ var InputPlugin = new Class({ Extends: EventEmitter, initialize: function InputPlugin (scene) { EventEmitter.call(this); /** * A reference to the Scene that this Input Plugin is responsible for. * * @name Phaser.Input.InputPlugin#scene * @type {Phaser.Scene} * @since 3.0.0 */ this.scene = scene; /** * A reference to the Scene Systems class. * * @name Phaser.Input.InputPlugin#systems * @type {Phaser.Scenes.Systems} * @since 3.0.0 */ this.systems = scene.sys; /** * A reference to the Scene Systems Settings. * * @name Phaser.Input.InputPlugin#settings * @type {Phaser.Types.Scenes.SettingsObject} * @since 3.5.0 */ this.settings = scene.sys.settings; /** * A reference to the Game Input Manager. * * @name Phaser.Input.InputPlugin#manager * @type {Phaser.Input.InputManager} * @since 3.0.0 */ this.manager = scene.sys.game.input; /** * Internal event queue used for plugins only. * * @name Phaser.Input.InputPlugin#pluginEvents * @type {Phaser.Events.EventEmitter} * @private * @since 3.10.0 */ this.pluginEvents = new EventEmitter(); /** * If `true` this Input Plugin will process DOM input events. * * @name Phaser.Input.InputPlugin#enabled * @type {boolean} * @default true * @since 3.5.0 */ this.enabled = true; /** * A reference to the Scene Display List. This property is set during the `boot` method. * * @name Phaser.Input.InputPlugin#displayList * @type {Phaser.GameObjects.DisplayList} * @since 3.0.0 */ this.displayList; /** * A reference to the Scene Cameras Manager. This property is set during the `boot` method. * * @name Phaser.Input.InputPlugin#cameras * @type {Phaser.Cameras.Scene2D.CameraManager} * @since 3.0.0 */ this.cameras; // Inject the available input plugins into this class InputPluginCache.install(this); /** * A reference to the Mouse Manager. * * This property is only set if Mouse support has been enabled in your Game Configuration file. * * If you just wish to get access to the mouse pointer, use the `mousePointer` property instead. * * @name Phaser.Input.InputPlugin#mouse * @type {?Phaser.Input.Mouse.MouseManager} * @since 3.0.0 */ this.mouse = this.manager.mouse; /** * When set to `true` (the default) the Input Plugin will emulate DOM behavior by only emitting events from * the top-most Game Objects in the Display List. * * If set to `false` it will emit events from all Game Objects below a Pointer, not just the top one. * * @name Phaser.Input.InputPlugin#topOnly * @type {boolean} * @default true * @since 3.0.0 */ this.topOnly = true; /** * How often should the Pointers be checked? * * The value is a time, given in ms, and is the time that must have elapsed between game steps before * the Pointers will be polled again. When a pointer is polled it runs a hit test to see which Game * Objects are currently below it, or being interacted with it. * * Pointers will *always* be checked if they have been moved by the user, or press or released. * * This property only controls how often they will be polled if they have not been updated. * You should set this if you want to have Game Objects constantly check against the pointers, even * if the pointer didn't itself move. * * Set to 0 to poll constantly. Set to -1 to only poll on user movement. * * @name Phaser.Input.InputPlugin#pollRate * @type {number} * @default -1 * @since 3.0.0 */ this.pollRate = -1; /** * Internal poll timer value. * * @name Phaser.Input.InputPlugin#_pollTimer * @type {number} * @private * @default 0 * @since 3.0.0 */ this._pollTimer = 0; var _eventData = { cancelled: false }; /** * Internal event propagation callback container. * * @name Phaser.Input.InputPlugin#_eventContainer * @type {Phaser.Types.Input.EventData} * @private * @since 3.13.0 */ this._eventContainer = { stopPropagation: function () { _eventData.cancelled = true; } }; /** * Internal event propagation data object. * * @name Phaser.Input.InputPlugin#_eventData * @type {object} * @private * @since 3.13.0 */ this._eventData = _eventData; /** * The distance, in pixels, a pointer has to move while being held down, before it thinks it is being dragged. * * @name Phaser.Input.InputPlugin#dragDistanceThreshold * @type {number} * @default 0 * @since 3.0.0 */ this.dragDistanceThreshold = 0; /** * The amount of time, in ms, a pointer has to be held down before it thinks it is dragging. * * The default polling rate is to poll only on move so once the time threshold is reached the * drag event will not start until you move the mouse. If you want it to start immediately * when the time threshold is reached, you must increase the polling rate by calling * [setPollAlways]{@linkcode Phaser.Input.InputPlugin#setPollAlways} or * [setPollRate]{@linkcode Phaser.Input.InputPlugin#setPollRate}. * * @name Phaser.Input.InputPlugin#dragTimeThreshold * @type {number} * @default 0 * @since 3.0.0 */ this.dragTimeThreshold = 0; /** * Used to temporarily store the results of the Hit Test * * @name Phaser.Input.InputPlugin#_temp * @type {array} * @private * @default [] * @since 3.0.0 */ this._temp = []; /** * Used to temporarily store the results of the Hit Test dropZones * * @name Phaser.Input.InputPlugin#_tempZones * @type {array} * @private * @default [] * @since 3.0.0 */ this._tempZones = []; /** * A list of all Game Objects that have been set to be interactive in the Scene this Input Plugin is managing. * * @name Phaser.Input.InputPlugin#_list * @type {Phaser.GameObjects.GameObject[]} * @private * @default [] * @since 3.0.0 */ this._list = []; /** * Objects waiting to be inserted to the list on the next call to 'begin'. * * @name Phaser.Input.InputPlugin#_pendingInsertion * @type {Phaser.GameObjects.GameObject[]} * @private * @default [] * @since 3.0.0 */ this._pendingInsertion = []; /** * Objects waiting to be removed from the list on the next call to 'begin'. * * @name Phaser.Input.InputPlugin#_pendingRemoval * @type {Phaser.GameObjects.GameObject[]} * @private * @default [] * @since 3.0.0 */ this._pendingRemoval = []; /** * A list of all Game Objects that have been enabled for dragging. * * @name Phaser.Input.InputPlugin#_draggable * @type {Phaser.GameObjects.GameObject[]} * @private * @default [] * @since 3.0.0 */ this._draggable = []; /** * A list of all Interactive Objects currently considered as being 'draggable' by any pointer, indexed by pointer ID. * * @name Phaser.Input.InputPlugin#_drag * @type {{0:Array,1:Array,2:Array,3:Array,4:Array,5:Array,6:Array,7:Array,8:Array,9:Array,10:Array}} * @private * @since 3.0.0 */ this._drag = { 0: [], 1: [], 2: [], 3: [], 4: [], 5: [], 6: [], 7: [], 8: [], 9: [], 10: [] }; /** * A array containing the dragStates, for this Scene, index by the Pointer ID. * * @name Phaser.Input.InputPlugin#_dragState * @type {number[]} * @private * @since 3.16.0 */ this._dragState = []; /** * A list of all Interactive Objects currently considered as being 'over' by any pointer, indexed by pointer ID. * * @name Phaser.Input.InputPlugin#_over * @type {{0:Array,1:Array,2:Array,3:Array,4:Array,5:Array,6:Array,7:Array,8:Array,9:Array,10:Array}} * @private * @since 3.0.0 */ this._over = { 0: [], 1: [], 2: [], 3: [], 4: [], 5: [], 6: [], 7: [], 8: [], 9: [], 10: [] }; /** * A list of valid DOM event types. * * @name Phaser.Input.InputPlugin#_validTypes * @type {string[]} * @private * @since 3.0.0 */ this._validTypes = [ 'onDown', 'onUp', 'onOver', 'onOut', 'onMove', 'onDragStart', 'onDrag', 'onDragEnd', 'onDragEnter', 'onDragLeave', 'onDragOver', 'onDrop' ]; /** * Internal property that tracks frame event state. * * @name Phaser.Input.InputPlugin#_updatedThisFrame * @type {boolean} * @private * @since 3.18.0 */ this._updatedThisFrame = false; scene.sys.events.once(SceneEvents.BOOT, this.boot, this); scene.sys.events.on(SceneEvents.START, this.start, this); }, /** * This method is called automatically, only once, when the Scene is first created. * Do not invoke it directly. * * @method Phaser.Input.InputPlugin#boot * @fires Phaser.Input.Events#BOOT * @private * @since 3.5.1 */ boot: function () { this.cameras = this.systems.cameras; this.displayList = this.systems.displayList; this.systems.events.once(SceneEvents.DESTROY, this.destroy, this); // Registered input plugins listen for this this.pluginEvents.emit(Events.BOOT); }, /** * This method is called automatically by the Scene when it is starting up. * It is responsible for creating local systems, properties and listening for Scene events. * Do not invoke it directly. * * @method Phaser.Input.InputPlugin#start * @fires Phaser.Input.Events#START * @private * @since 3.5.0 */ start: function () { var eventEmitter = this.systems.events; eventEmitter.on(SceneEvents.TRANSITION_START, this.transitionIn, this); eventEmitter.on(SceneEvents.TRANSITION_OUT, this.transitionOut, this); eventEmitter.on(SceneEvents.TRANSITION_COMPLETE, this.transitionComplete, this); eventEmitter.on(SceneEvents.PRE_UPDATE, this.preUpdate, this); eventEmitter.once(SceneEvents.SHUTDOWN, this.shutdown, this); this.manager.events.on(Events.GAME_OUT, this.onGameOut, this); this.manager.events.on(Events.GAME_OVER, this.onGameOver, this); this.enabled = true; // Populate the pointer drag states this._dragState = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]; // Registered input plugins listen for this this.pluginEvents.emit(Events.START); }, /** * Game Over handler. * * @method Phaser.Input.InputPlugin#onGameOver * @fires Phaser.Input.Events#GAME_OVER * @private * @since 3.16.2 */ onGameOver: function (event) { if (this.isActive()) { this.emit(Events.GAME_OVER, event.timeStamp, event); } }, /** * Game Out handler. * * @method Phaser.Input.InputPlugin#onGameOut * @fires Phaser.Input.Events#GAME_OUT * @private * @since 3.16.2 */ onGameOut: function (event) { if (this.isActive()) { this.emit(Events.GAME_OUT, event.timeStamp, event); } }, /** * The pre-update handler is responsible for checking the pending removal and insertion lists and * deleting old Game Objects. * * @method Phaser.Input.InputPlugin#preUpdate * @private * @fires Phaser.Input.Events#PRE_UPDATE * @since 3.0.0 */ preUpdate: function () { // Registered input plugins listen for this this.pluginEvents.emit(Events.PRE_UPDATE); var removeList = this._pendingRemoval; var insertList = this._pendingInsertion; var toRemove = removeList.length; var toInsert = insertList.length; if (toRemove === 0 && toInsert === 0) { // Quick bail return; } var current = this._list; // Delete old gameObjects for (var i = 0; i < toRemove; i++) { var gameObject = removeList[i]; var index = current.indexOf(gameObject); if (index > -1) { current.splice(index, 1); this.clear(gameObject, true); } } // Clear the removal list this._pendingRemoval.length = 0; // Move pendingInsertion to list (also clears pendingInsertion at the same time) this._list = current.concat(insertList.splice(0)); }, /** * Checks to see if the Input Manager, this plugin and the Scene to which it belongs are all active and input enabled. * * @method Phaser.Input.InputPlugin#isActive * @since 3.10.0 * * @return {boolean} `true` if the plugin and the Scene it belongs to is active. */ isActive: function () { return (this.manager && this.manager.enabled && this.enabled && this.scene.sys.canInput()); }, /** * Sets a custom cursor on the parent canvas element of the game, based on the `cursor` * setting of the given Interactive Object (i.e. a Sprite). * * See the CSS property `cursor` for more information on MDN: * * https://developer.mozilla.org/en-US/docs/Web/CSS/cursor * * @method Phaser.Input.InputPlugin#setCursor * @since 3.85.0 * * @param {Phaser.Types.Input.InteractiveObject} interactiveObject - The Interactive Object that will set the cursor on the canvas. */ setCursor: function (interactiveObject) { if (this.manager) { this.manager.setCursor(interactiveObject); } }, /** * Forces the Input Manager to clear the custom or hand cursor, regardless of the * interactive state of any Game Objects. * * @method Phaser.Input.InputPlugin#resetCursor * @since 3.85.0 */ resetCursor: function () { if (this.manager) { this.manager.resetCursor(null, true); } }, /** * This is called automatically by the Input Manager. * It emits events for plugins to listen to and also handles polling updates, if enabled. * * @method Phaser.Input.InputPlugin#updatePoll * @since 3.18.0 * * @param {number} time - The current time. Either a High Resolution Timer value if it comes from Request Animation Frame, or Date.now if using SetTimeout. * @param {number} delta - The delta time in ms since the last frame. This is a smoothed and capped value based on the FPS rate. * * @return {boolean} `true` if the plugin and the Scene it belongs to is active. */ updatePoll: function (time, delta) { if (!this.isActive()) { return false; } // The plugins should update every frame, regardless if there has been // any DOM input events or not (such as the Gamepad and Keyboard) this.pluginEvents.emit(Events.UPDATE, time, delta); // We can leave now if we've already updated once this frame via the immediate DOM event handlers if (this._updatedThisFrame) { this._updatedThisFrame = false; return false; } var i; var manager = this.manager; var pointers = manager.pointers; for (i = 0; i < pointers.length; i++) { pointers[i].updateMotion(); } // No point going any further if there aren't any interactive objects if (this._list.length === 0) { return false; } var rate = this.pollRate; if (rate === -1) { return false; } else if (rate > 0) { this._pollTimer -= delta; if (this._pollTimer < 0) { // Discard timer diff, we're ready to poll again this._pollTimer = this.pollRate; } else { // Not enough time has elapsed since the last poll, so abort now return false; } } // We got this far? Then we should poll for movement var captured = false; for (i = 0; i < pointers.length; i++) { var total = 0; var pointer = pointers[i]; // Always reset this array this._tempZones = []; // _temp contains a hit tested and camera culled list of IO objects this._temp = this.hitTestPointer(pointer); this.sortGameObjects(this._temp, pointer); this.sortDropZones(this._tempZones); if (this.topOnly) { // Only the top-most one counts now, so safely ignore the rest if (this._temp.length) { this._temp.splice(1); } if (this._tempZones.length) { this._tempZones.splice(1); } } total += this.processOverOutEvents(pointer); if (this.getDragState(pointer) === 2) { this.processDragThresholdEvent(pointer, time); } if (total > 0) { // We interacted with an event in this Scene, so block any Scenes below us from doing the same this frame captured = true; } } return captured; }, /** * This method is called when a DOM Event is received by the Input Manager. It handles dispatching the events * to relevant input enabled Game Objects in this scene. * * @method Phaser.Input.InputPlugin#update * @private * @fires Phaser.Input.Events#UPDATE * @since 3.0.0 * * @param {number} type - The type of event to process. * @param {Phaser.Input.Pointer[]} pointers - An array of Pointers on which the event occurred. * * @return {boolean} `true` if this Scene has captured the input events from all other Scenes, otherwise `false`. */ update: function (type, pointers) { if (!this.isActive()) { return false; } var captured = false; for (var i = 0; i < pointers.length; i++) { var total = 0; var pointer = pointers[i]; // Always reset this array this._tempZones = []; // _temp contains a hit tested and camera culled list of IO objects this._temp = this.hitTestPointer(pointer); this.sortGameObjects(this._temp, pointer); this.sortDropZones(this._tempZones); if (this.topOnly) { // Only the top-most one counts now, so safely ignore the rest if (this._temp.length) { this._temp.splice(1); } if (this._tempZones.length) { this._tempZones.splice(1); } } switch (type) { case CONST.MOUSE_DOWN: total += this.processDragDownEvent(pointer); total += this.processDownEvents(pointer); total += this.processOverOutEvents(pointer); break; case CONST.MOUSE_UP: total += this.processDragUpEvent(pointer); total += this.processUpEvents(pointer); total += this.processOverOutEvents(pointer); break; case CONST.TOUCH_START: total += this.processDragDownEvent(pointer); total += this.processDownEvents(pointer); total += this.processOverEvents(pointer); break; case CONST.TOUCH_END: case CONST.TOUCH_CANCEL: total += this.processDragUpEvent(pointer); total += this.processUpEvents(pointer); total += this.processOutEvents(pointer); break; case CONST.MOUSE_MOVE: case CONST.TOUCH_MOVE: total += this.processDragMoveEvent(pointer); total += this.processMoveEvents(pointer); total += this.processOverOutEvents(pointer); break; case CONST.MOUSE_WHEEL: total += this.processWheelEvent(pointer); break; } if (total > 0) { // We interacted with an event in this Scene, so block any Scenes below us from doing the same this frame captured = true; } } this._updatedThisFrame = true; return captured; }, /** * Clears a Game Object so it no longer has an Interactive Object associated with it. * The Game Object is then queued for removal from the Input Plugin on the next update. * * @method Phaser.Input.InputPlugin#clear * @since 3.0.0 * * @param {Phaser.GameObjects.GameObject} gameObject - The Game Object that will have its Interactive Object removed. * @param {boolean} [skipQueue=false] - Skip adding this Game Object into the removal queue? * * @return {Phaser.GameObjects.GameObject} The Game Object that had its Interactive Object removed. */ clear: function (gameObject, skipQueue) { if (skipQueue === undefined) { skipQueue = false; } this.disable(gameObject); var input = gameObject.input; // If GameObject.input already cleared from higher class if (input) { this.removeDebug(gameObject); this.manager.resetCursor(input); input.gameObject = undefined; input.target = undefined; input.hitArea = undefined; input.hitAreaCallback = undefined; input.callbackContext = undefined; gameObject.input = null; } if (!skipQueue) { this.queueForRemoval(gameObject); } var index = this._draggable.indexOf(gameObject); if (index > -1) { this._draggable.splice(index, 1); } return gameObject; }, /** * Disables Input on a single Game Object. * * An input disabled Game Object still retains its Interactive Object component and can be re-enabled * at any time, by passing it to `InputPlugin.enable`. * * @method Phaser.Input.InputPlugin#disable * @since 3.0.0 * * @param {Phaser.GameObjects.GameObject} gameObject - The Game Object to have its input system disabled. * @param {boolean} [resetCursor=false] - Reset the cursor to the default? * * @return {this} This Input Plugin. */ disable: function (gameObject, resetCursor) { if (resetCursor === undefined) { resetCursor = false; } var input = gameObject.input; if (input) { input.enabled = false; input.dragState = 0; } // Clear from _drag and _over var drag = this._drag; var over = this._over; var manager = this.manager; for (var i = 0, index; i < manager.pointers.length; i++) { index = drag[i].indexOf(gameObject); if (index > -1) { drag[i].splice(index, 1); } index = over[i].indexOf(gameObject); if (index > -1) { over[i].splice(index, 1); } } if (resetCursor) { this.resetCursor(); } return this; }, /** * Enable a Game Object for interaction. * * If the Game Object already has an Interactive Object component, it is enabled and returned. * * Otherwise, a new Interactive Object component is created and assigned to the Game Object's `input` property. * * Input works by using hit areas, these are nearly always geometric shapes, such as rectangles or circles, that act as the hit area * for the Game Object. However, you can provide your own hit area shape and callback, should you wish to handle some more advanced * input detection. * * If no arguments are provided it will try and create a rectangle hit area based on the texture frame the Game Object is using. If * this isn't a texture-bound object, such as a Graphics or BitmapText object, this will fail, and you'll need to provide a specific * shape for it to use. * * You can also provide an Input Configuration Object as the only argument to this method. * * @method Phaser.Input.InputPlugin#enable * @since 3.0.0 * * @param {Phaser.GameObjects.GameObject} gameObject - The Game Object to be enabled for input. * @param {(Phaser.Types.Input.InputConfiguration|any)} [hitArea] - Either an input configuration object, or a geometric shape that defines the hit area for the Game Object. If not specified a Rectangle will be used. * @param {Phaser.Types.Input.HitAreaCallback} [hitAreaCallback] - The 'contains' function to invoke to check if the pointer is within the hit area. * @param {boolean} [dropZone=false] - Is this Game Object a drop zone or not? * * @return {this} This Input Plugin. */ enable: function (gameObject, hitArea, hitAreaCallback, dropZone) { if (dropZone === undefined) { dropZone = false; } if (gameObject.input) { // If it already has an InteractiveObject then just enable it and return gameObject.input.enabled = true; } else { // Create an InteractiveObject and enable it this.setHitArea(gameObject, hitArea, hitAreaCallback); } if (gameObject.input && dropZone && !gameObject.input.dropZone) { gameObject.input.dropZone = dropZone; } return this; }, /** * Takes the given Pointer and performs a hit test against it, to see which interactive Game Objects * it is currently above. * * The hit test is performed against which-ever Camera the Pointer is over. If it is over multiple * cameras, it starts checking the camera at the top of the camera list, and if nothing is found, iterates down the list. * * @method Phaser.Input.InputPlugin#hitTestPointer * @since 3.0.0 * * @param {Phaser.Input.Pointer} pointer - The Pointer to check against the Game Objects. * * @return {Phaser.GameObjects.GameObject[]} An array of all the interactive Game Objects the Pointer was above. */ hitTestPointer: function (pointer) { var cameras = this.cameras.getCamerasBelowPointer(pointer); for (var c = 0; c < cameras.length; c++) { var camera = cameras[c]; // Get a list of all objects that can be seen by the camera below the pointer in the scene and store in 'over' array. // All objects in this array are input enabled, as checked by the hitTest method, so we don't need to check later on as well. var over = this.manager.hitTest(pointer, this._list, camera); // Filter out the drop zones for (var i = 0; i < over.length; i++) { var obj = over[i]; if (obj.input.dropZone) { this._tempZones.push(obj); } } if (over.length > 0) { pointer.camera = camera; return over; } } // If we got this far then there were no Game Objects below the pointer, but it was still over // a camera, so set that the top-most one into the pointer pointer.camera = cameras[0]; return []; }, /** * An internal method that handles the Pointer down event. * * @method Phaser.Input.InputPlugin#processDownEvents * @private * @fires Phaser.Input.Events#GAMEOBJECT_POINTER_DOWN * @fires Phaser.Input.Events#GAMEOBJECT_DOWN * @fires Phaser.Input.Events#POINTER_DOWN * @fires Phaser.Input.Events#POINTER_DOWN_OUTSIDE * @since 3.0.0 * * @param {Phaser.Input.Pointer} pointer - The Pointer being tested. * * @return {number} The total number of objects interacted with. */ processDownEvents: function (pointer) { var total = 0; var currentlyOver = this._temp; var _eventData = this._eventData; var _eventContainer = this._eventContainer; _eventData.cancelled = false; // Go through all objects the pointer was over and fire their events / callbacks for (var i = 0; i < currentlyOver.length; i++) { var gameObject = currentlyOver[i]; if (!gameObject.input || !gameObject.input.enabled) { continue; } total++; // 1) GAMEOBJECT_POINTER_DOWN gameObject.emit(Events.GAMEOBJECT_POINTER_DOWN, pointer, gameObject.input.localX, gameObject.input.localY, _eventContainer); if (_eventData.cancelled || !this.isActive()) { // They cancelled the whole event, it can't go any further break; } // Check that the game object wasn't input disabled or destroyed as a result of its input event if (gameObject.input && gameObject.input.enabled) { // 2) GAMEOBJECT_DOWN this.emit(Events.GAMEOBJECT_DOWN, pointer, gameObject, _eventContainer); if (_eventData.cancelled || !this.isActive()) { // They cancelled the whole event, it can't go any further break; } } } // If they pressed down outside the canvas, dispatch that event. if (!_eventData.cancelled && this.isActive()) { if (pointer.downElement === this.manager.game.canvas) { // 3) POINTER_DOWN this.emit(Events.POINTER_DOWN, pointer, currentlyOver); } else { // 4) POINTER_DOWN_OUTSIDE this.emit(Events.POINTER_DOWN_OUTSIDE, pointer); } } return total; }, /** * Returns the drag state of the given Pointer for this Input Plugin. * * The state will be one of the following: * * 0 = Not dragging anything * 1 = Primary button down and objects below, so collect a draglist * 2 = Pointer being checked if meets drag criteria * 3 = Pointer meets criteria, notify the draglist * 4 = Pointer actively dragging the draglist and has moved * 5 = Pointer actively dragging but has been released, notify draglist * * @method Phaser.Input.InputPlugin#getDragState * @since 3.16.0 * * @param {Phaser.Input.Pointer} pointer - The Pointer to get the drag state for. * * @return {number} The drag state of the given Pointer. */ getDragState: function (pointer) { return this._dragState[pointer.id]; }, /** * Sets the drag state of the given Pointer for this Input Plugin. * * The state must be one of the following values: * * 0 = Not dragging anything * 1 = Primary button down and objects below, so collect a draglist * 2 = Pointer being checked if meets drag criteria * 3 = Pointer meets criteria, notify the draglist * 4 = Pointer actively dragging the draglist and has moved * 5 = Pointer actively dragging but has been released, notify draglist * * @method Phaser.Input.InputPlugin#setDragState * @since 3.16.0 * * @param {Phaser.Input.Pointer} pointer - The Pointer to set the drag state for. * @param {number} state - The drag state value. An integer between 0 and 5. */ setDragState: function (pointer, state) { this._dragState[pointer.id] = state; }, /** * Checks to see if a Pointer is ready to drag the objects below it, based on either a distance * or time threshold. * * @method Phaser.Input.InputPlugin#processDragThresholdEvent * @private * @since 3.18.0 * * @param {Phaser.Input.Pointer} pointer - The Pointer to check the drag thresholds on. * @param {number} time - The current time. */ processDragThresholdEvent: function (pointer, time) { var passed = false; var timeThreshold = this.dragTimeThreshold; var distanceThreshold = this.dragDistanceThreshold; if (distanceThreshold > 0 && DistanceBetween(pointer.x, pointer.y, pointer.downX, pointer.downY) >= distanceThreshold) { // It has moved far enough to be considered a drag passed = true; } else if (timeThreshold > 0 && (time >= pointer.downTime + timeThreshold)) { // It has been held down long enough to be considered a drag passed = true; } if (passed) { this.setDragState(pointer, 3); return this.processDragStartList(pointer); } }, /** * Processes the drag list for the given pointer and dispatches the start events for each object on it. * * @method Phaser.Input.InputPlugin#processDragStartList * @private * @fires Phaser.Input.Events#DRAG_START * @fires Phaser.Input.Events#GAMEOBJECT_DRAG_START * @since 3.18.0 * * @param {Phaser.Input.Pointer} pointer - The Pointer to process the drag event on. * * @return {number} The number of items that DRAG_START was called on. */ processDragStartList: function (pointer) { // 3 = Pointer meets criteria and is freshly down, notify the draglist if (this.getDragState(pointer) !== 3) { return 0; } var list = this._drag[pointer.id]; if (list.length > 1) { list = list.slice(0); } for (var i = 0; i < list.length; i++) { var gameObject = list[i]; var input = gameObject.input; input.dragState = 2; input.dragStartX = gameObject.x; input.dragStartY = gameObject.y; input.dragStartXGlobal = pointer.worldX; input.dragStartYGlobal = pointer.worldY; input.dragStartCamera = pointer.camera; input.dragX = input.dragStartXGlobal - input.dragStartX; input.dragY = input.dragStartYGlobal - input.dragStartY; gameObject.emit(Events.GAMEOBJECT_DRAG_START, pointer, input.dragX, input.dragY); this.emit(Events.DRAG_START, pointer, gameObject); } this.setDragState(pointer, 4); return list.length; }, /** * Processes a 'drag down' event for the given pointer. Checks the pointer state, builds-up the drag list * and prepares them all for interaction. * * @method Phaser.Input.InputPlugin#processDragDownEvent * @private * @since 3.18.0 * * @param {Phaser.Input.Pointer} pointer - The Pointer to process the drag event on. * * @return {number} The number of items that were collected on the drag list. */ processDragDownEvent: function (pointer) { var currentlyOver = this._temp; if (this._draggable.length === 0 || currentlyOver.length === 0 || !pointer.primaryDown || this.getDragState(pointer) !== 0) { // There are no draggable items, no over items or the pointer isn't down, so let's not even bother going further return 0; } // 1 = Primary button down and objects below, so collect a draglist this.setDragState(pointer, 1); // Get draggable objects, sort them, pick the top (or all) and store them somewhere var draglist = []; for (var i = 0; i < currentlyOver.length; i++) { var gameObject = currentlyOver[i]; if (gameObject.input.draggable && (gameObject.input.dragState === 0)) { draglist.push(gameObject); } } if (draglist.length === 0) { this.setDragState(pointer, 0); return 0; } else if (draglist.length > 1) { this.sortGameObjects(draglist, pointer); if (this.topOnly) { draglist.splice(1); } } // draglist now contains all potential candidates for dragging this._drag[pointer.id] = draglist; if (this.dragDistanceThreshold === 0 && this.dragTimeThreshold === 0) { // No drag criteria, so snap immediately to mode 3 this.setDragState(pointer, 3); return this.processDragStartList(pointer); } else { // Check the distance / time on the next event this.setDragState(pointer, 2); return 0; } }, /** * Processes a 'drag move' event for the given pointer. * * @method Phaser.Input.InputPlugin#processDragMoveEvent * @private * @fires Phaser.Input.Events#DRAG_ENTER * @fires Phaser.Input.Events#DRAG * @fires Phaser.Input.Events#DRAG_LEAVE * @fires Phaser.Input.Events#DRAG_OVER * @fires Phaser.Input.Events#GAMEOBJECT_DRAG_ENTER * @fires Phaser.Input.Events#GAMEOBJECT_DRAG * @fires Phaser.Input.Events#GAMEOBJECT_DRAG_LEAVE * @fires Phaser.Input.Events#GAMEOBJECT_DRAG_OVER * @since 3.18.0 * * @param {Phaser.Input.Pointer} pointer - The Pointer to process the drag event on. * * @return {number} The number of items that were updated by this drag event. */ processDragMoveEvent: function (pointer) { // 2 = Pointer being checked if meets drag criteria if (this.getDragState(pointer) === 2) { this.processDragThresholdEvent(pointer, this.manager.game.loop.now); } if (this.getDragState(pointer) !== 4) { return 0; } // 4 = Pointer actively dragging the draglist and has moved var dropZones = this._tempZones; var list = this._drag[pointer.id]; if (list.length > 1) { list = list.slice(0); } for (var i = 0; i < list.length; i++) { var gameObject = list[i]; var input = gameObject.input; var target = input.target; // If this GO has a target then let's check it if (target) { var index = dropZones.indexOf(target); // Got a target, are we still over it? if (index === 0) { // We're still over it, and it's still the top of the display list, phew ... gameObject.emit(Events.GAMEOBJECT_DRAG_OVER, pointer, target); this.emit(Events.DRAG_OVER, pointer, gameObject, target); } else if (index > 0) { // Still over it but it's no longer top of the display list (targets must always be at the top) gameObject.emit(Events.GAMEOBJECT_DRAG_LEAVE, pointer, target); this.emit(Events.DRAG_LEAVE, pointer, gameObject, target); input.target = dropZones[0]; target = input.target; gameObject.emit(Events.GAMEOBJECT_DRAG_ENTER, pointer, target); this.emit(Events.DRAG_ENTER, pointer, gameObject, target); } else { // Nope, we've moved on (or the target has!), leave the old target gameObject.emit(Events.GAMEOBJECT_DRAG_LEAVE, pointer, target); this.emit(Events.DRAG_LEAVE, pointer, gameObject, target); // Anything new to replace it? // Yup! if (dropZones[0]) { input.target = dropZones[0]; target = input.target; gameObject.emit(Events.GAMEOBJECT_DRAG_ENTER, pointer, target); this.emit(Events.DRAG_ENTER, pointer, gameObject, target); } else { // Nope input.target = null; } } } else if (!target && dropZones[0]) { input.target = dropZones[0]; target = input.target; gameObject.emit(Events.GAMEOBJECT_DRAG_ENTER, pointer, target); this.emit(Events.DRAG_ENTER, pointer, gameObject, target); } var dragX; var dragY; var dragWorldXY = pointer.positionToCamera(input.dragStartCamera); if (!gameObject.parentContainer) { dragX = dragWorldXY.x - input.dragX; dragY = dragWorldXY.y - input.dragY; } else { var dx = dragWorldXY.x - input.dragStartXGlobal; var dy = dragWorldXY.y - input.dragStartYGlobal; var rotation = gameObject.getParentRotation(); var dxRotated = dx * Math.cos(rotation) + dy * Math.sin(rotation); var dyRotated = dy * Math.cos(rotation) - dx * Math.sin(rotation); dxRotated *= (1 / gameObject.parentContainer.scaleX); dyRotated *= (1 / gameObject.parentContainer.scaleY); dragX = dxRotated + input.dragStartX; dragY = dyRotated + input.dragStartY; } gameObject.emit(Events.GAMEOBJECT_DRAG, pointer, dragX, dragY); this.emit(Events.DRAG, pointer, gameObject, dragX, dragY); } return list.length; }, /** * Processes a 'drag down' event for the given pointer. Checks the pointer state, builds-up the drag list * and prepares them all for interaction. * * @method Phaser.Input.InputPlugin#processDragUpEvent * @fires Phaser.Input.Events#DRAG_END * @fires Phaser.Input.Events#DROP * @fires Phaser.Input.Events#GAMEOBJECT_DRAG_END * @fires Phaser.Input.Events#GAMEOBJECT_DROP * @private * @since 3.18.0 * * @param {Phaser.Input.Pointer} pointer - The Pointer to process the drag event on. * * @return {number} The number of items that were updated by this drag event. */ processDragUpEvent: function (pointer) { // 5 = Pointer was actively dragging but has been released, notify draglist var list = this._drag[pointer.id]; if (list.length > 1) { list = list.slice(0); } for (var i = 0; i < list.length; i++) { var gameObject = list[i]; var input = gameObject.input; if (input && input.dragState === 2) { input.dragState = 0; input.dragX = input.localX - gameObject.displayOriginX;