/** * @author Richard Davey * @copyright 2016 Photon Storm Ltd. * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} */ /** * Create a new `Button` object. A Button is a special type of Sprite that is set-up to handle Pointer events automatically. * * The four states a Button responds to are: * * * 'Over' - when the Pointer moves over the Button. This is also commonly known as 'hover'. * * 'Out' - when the Pointer that was previously over the Button moves out of it. * * 'Down' - when the Pointer is pressed down on the Button. I.e. touched on a touch enabled device or clicked with the mouse. * * 'Up' - when the Pointer that was pressed down on the Button is released again. * * A different texture/frame and activation sound can be specified for any of the states. * * Frames can be specified as either an integer (the frame ID) or a string (the frame name); the same values that can be used with a Sprite constructor. * * @class Phaser.GameObject.Button * @constructor * @extends Phaser.Image * @param {Phaser.Game} game Current game instance. * @param {number} [x=0] - X position of the Button. * @param {number} [y=0] - Y position of the Button. * @param {string} [key] - The image key (in the Game.Cache) to use as the texture for this Button. * @param {function} [callback] - The function to call when this Button is pressed. * @param {object} [callbackContext] - The context in which the callback will be called (usually 'this'). * @param {string|integer} [overFrame] - The frame / frameName when the button is in the Over state. * @param {string|integer} [outFrame] - The frame / frameName when the button is in the Out state. * @param {string|integer} [downFrame] - The frame / frameName when the button is in the Down state. * @param {string|integer} [upFrame] - The frame / frameName when the button is in the Up state. */ Phaser.GameObject.Button = function (game, x, y, key, callback, callbackContext, overFrame, outFrame, downFrame, upFrame) { x = x || 0; y = y || 0; key = key || null; callback = callback || null; callbackContext = callbackContext || this; Phaser.GameObject.Image.call(this, game, x, y, key, outFrame); /** * The Phaser Object Type. * @property {number} type * @readonly */ this.type = Phaser.BUTTON; /** * @property {number} physicsType - The const physics body type of this object. * @readonly */ this.physicsType = Phaser.SPRITE; /** * The name or ID of the Over state frame. * @property {string|integer} onOverFrame * @private */ this._onOverFrame = null; /** * The name or ID of the Out state frame. * @property {string|integer} onOutFrame * @private */ this._onOutFrame = null; /** * The name or ID of the Down state frame. * @property {string|integer} onDownFrame * @private */ this._onDownFrame = null; /** * The name or ID of the Up state frame. * @property {string|integer} onUpFrame * @private */ this._onUpFrame = null; /** * The Sound to be played when this Buttons Over state is activated. * @property {Phaser.Sound|Phaser.AudioSprite|null} onOverSound * @readonly */ this.onOverSound = null; /** * The Sound to be played when this Buttons Out state is activated. * @property {Phaser.Sound|Phaser.AudioSprite|null} onOutSound * @readonly */ this.onOutSound = null; /** * The Sound to be played when this Buttons Down state is activated. * @property {Phaser.Sound|Phaser.AudioSprite|null} onDownSound * @readonly */ this.onDownSound = null; /** * The Sound to be played when this Buttons Up state is activated. * @property {Phaser.Sound|Phaser.AudioSprite|null} onUpSound * @readonly */ this.onUpSound = null; /** * The Sound Marker used in conjunction with the onOverSound. * @property {string} onOverSoundMarker * @readonly */ this.onOverSoundMarker = ''; /** * The Sound Marker used in conjunction with the onOutSound. * @property {string} onOutSoundMarker * @readonly */ this.onOutSoundMarker = ''; /** * The Sound Marker used in conjunction with the onDownSound. * @property {string} onDownSoundMarker * @readonly */ this.onDownSoundMarker = ''; /** * The Sound Marker used in conjunction with the onUpSound. * @property {string} onUpSoundMarker * @readonly */ this.onUpSoundMarker = ''; /** * The Signal (or event) dispatched when this Button is in an Over state. * @property {Phaser.Signal} onInputOver */ this.onInputOver = new Phaser.Signal(); /** * The Signal (or event) dispatched when this Button is in an Out state. * @property {Phaser.Signal} onInputOut */ this.onInputOut = new Phaser.Signal(); /** * The Signal (or event) dispatched when this Button is in an Down state. * @property {Phaser.Signal} onInputDown */ this.onInputDown = new Phaser.Signal(); /** * The Signal (or event) dispatched when this Button is in an Up state. * @property {Phaser.Signal} onInputUp */ this.onInputUp = new Phaser.Signal(); /** * If true then onOver events (such as onOverSound) will only be triggered if the Pointer object causing them was the Mouse Pointer. * The frame will still be changed as applicable. * * @property {boolean} onOverMouseOnly * @default */ this.onOverMouseOnly = true; /** * Suppress the over event if a pointer was just released and it matches the given {@link Phaser.PointerModer pointer mode bitmask}. * * This behavior was introduced in Phaser 2.3.1; this property is a soft-revert of the change. * * @property {Phaser.PointerMode?} justReleasedPreventsOver=ACTIVE_CURSOR */ this.justReleasedPreventsOver = Phaser.PointerMode.TOUCH; /** * When true the the texture frame will not be automatically switched on up/down/over/out events. * @property {boolean} freezeFrames * @default */ this.freezeFrames = false; /** * When the Button is touched / clicked and then released you can force it to enter a state of "out" instead of "up". * * This can also accept a {@link Phaser.PointerModer pointer mode bitmask} for more refined control. * * @property {boolean|Phaser.PointerMode} forceOut=false * @default */ this.forceOut = false; this.inputEnabled = true; this.input.start(0, true); this.input.useHandCursor = true; this.setFrames(overFrame, outFrame, downFrame, upFrame); if (callback !== null) { this.onInputUp.add(callback, callbackContext); } // Redirect the input events to here so we can handle animation updates, etc this.events.onInputOver.add(this.onInputOverHandler, this); this.events.onInputOut.add(this.onInputOutHandler, this); this.events.onInputDown.add(this.onInputDownHandler, this); this.events.onInputUp.add(this.onInputUpHandler, this); this.events.onRemovedFromWorld.add(this.removedFromWorld, this); }; Phaser.GameObject.Button.prototype = Object.create(Phaser.GameObject.Image.prototype); Phaser.GameObject.Button.prototype.constructor = Phaser.GameObject.Button; // State constants; local only. These are tied to property names in Phaser.GameObject.Button. var STATE_OVER = 'Over'; var STATE_OUT = 'Out'; var STATE_DOWN = 'Down'; var STATE_UP = 'Up'; /** * Clears all of the frames set on this Button. * * @method Phaser.GameObject.Button#clearFrames */ Phaser.GameObject.Button.prototype.clearFrames = function () { this.setFrames(null, null, null, null); }; /** * Called when this Button is removed from the World. * * @method Phaser.GameObject.Button#removedFromWorld * @protected */ Phaser.GameObject.Button.prototype.removedFromWorld = function () { this.inputEnabled = false; }; /** * Set the frame name/ID for the given state. * * @method Phaser.GameObject.Button#setStateFrame * @private * @param {object} state - See `STATE_*` * @param {number|string} frame - The number or string representing the frame. * @param {boolean} switchImmediately - Immediately switch to the frame if it was set - and this is true. */ Phaser.GameObject.Button.prototype.setStateFrame = function (state, frame, switchImmediately) { var frameKey = '_on' + state + 'Frame'; if (frame !== null) // not null or undefined { this[frameKey] = frame; if (switchImmediately) { this.changeStateFrame(state); } } else { this[frameKey] = null; } }; /** * Change the frame to that of the given state, _if_ the state has a frame assigned _and_ if the frames are not currently "frozen". * * @method Phaser.GameObject.Button#changeStateFrame * @private * @param {object} state - See `STATE_*` * @return {boolean} True only if the frame was assigned a value, possibly the same one it already had. */ Phaser.GameObject.Button.prototype.changeStateFrame = function (state) { if (this.freezeFrames) { return false; } var frameKey = '_on' + state + 'Frame'; var frame = this[frameKey]; if (typeof frame === 'string') { this.frameName = frame; return true; } else if (typeof frame === 'number') { this.frame = frame; return true; } else { return false; } }; /** * Used to manually set the frames that will be used for the different states of the Button. * * Frames can be specified as either an integer (the frame ID) or a string (the frame name); these are the same values that can be used with a Sprite constructor. * * @method Phaser.GameObject.Button#setFrames * @public * @param {string|integer} [overFrame] - The frame / frameName when the button is in the Over state. * @param {string|integer} [outFrame] - The frame / frameName when the button is in the Out state. * @param {string|integer} [downFrame] - The frame / frameName when the button is in the Down state. * @param {string|integer} [upFrame] - The frame / frameName when the button is in the Up state. */ Phaser.GameObject.Button.prototype.setFrames = function (overFrame, outFrame, downFrame, upFrame) { this.setStateFrame(STATE_OVER, overFrame, this.input.pointerOver()); this.setStateFrame(STATE_OUT, outFrame, !this.input.pointerOver()); this.setStateFrame(STATE_DOWN, downFrame, this.input.pointerDown()); this.setStateFrame(STATE_UP, upFrame, this.input.pointerUp()); }; /** * Set the sound/marker for the given state. * * @method Phaser.GameObject.Button#setStateSound * @private * @param {object} state - See `STATE_*` * @param {Phaser.Sound|Phaser.AudioSprite} [sound] - Sound. * @param {string} [marker=''] - Sound marker. */ Phaser.GameObject.Button.prototype.setStateSound = function (state, sound, marker) { var soundKey = 'on' + state + 'Sound'; var markerKey = 'on' + state + 'SoundMarker'; if (sound instanceof Phaser.Sound || sound instanceof Phaser.AudioSprite) { this[soundKey] = sound; this[markerKey] = typeof marker === 'string' ? marker : ''; } else { this[soundKey] = null; this[markerKey] = ''; } }; /** * Play the sound for the given state, _if_ the state has a sound assigned. * * @method Phaser.GameObject.Button#playStateSound * @private * @param {object} state - See `STATE_*` * @return {boolean} True only if a sound was played. */ Phaser.GameObject.Button.prototype.playStateSound = function (state) { var soundKey = 'on' + state + 'Sound'; var sound = this[soundKey]; if (sound) { var markerKey = 'on' + state + 'SoundMarker'; var marker = this[markerKey]; sound.play(marker); return true; } else { return false; } }; /** * Sets the sounds to be played whenever this Button is interacted with. Sounds can be either full Sound objects, or markers pointing to a section of a Sound object. * The most common forms of sounds are 'hover' effects and 'click' effects, which is why the order of the parameters is overSound then downSound. * * Call this function with no parameters to reset all sounds on this Button. * * @method Phaser.GameObject.Button#setSounds * @public * @param {Phaser.Sound|Phaser.AudioSprite} [overSound] - Over Button Sound. * @param {string} [overMarker] - Over Button Sound Marker. * @param {Phaser.Sound|Phaser.AudioSprite} [downSound] - Down Button Sound. * @param {string} [downMarker] - Down Button Sound Marker. * @param {Phaser.Sound|Phaser.AudioSprite} [outSound] - Out Button Sound. * @param {string} [outMarker] - Out Button Sound Marker. * @param {Phaser.Sound|Phaser.AudioSprite} [upSound] - Up Button Sound. * @param {string} [upMarker] - Up Button Sound Marker. */ Phaser.GameObject.Button.prototype.setSounds = function (overSound, overMarker, downSound, downMarker, outSound, outMarker, upSound, upMarker) { this.setStateSound(STATE_OVER, overSound, overMarker); this.setStateSound(STATE_OUT, outSound, outMarker); this.setStateSound(STATE_DOWN, downSound, downMarker); this.setStateSound(STATE_UP, upSound, upMarker); }; /** * The Sound to be played when a Pointer moves over this Button. * * @method Phaser.GameObject.Button#setOverSound * @public * @param {Phaser.Sound|Phaser.AudioSprite} sound - The Sound that will be played. * @param {string} [marker] - A Sound Marker that will be used in the playback. */ Phaser.GameObject.Button.prototype.setOverSound = function (sound, marker) { this.setStateSound(STATE_OVER, sound, marker); }; /** * The Sound to be played when a Pointer moves out of this Button. * * @method Phaser.GameObject.Button#setOutSound * @public * @param {Phaser.Sound|Phaser.AudioSprite} sound - The Sound that will be played. * @param {string} [marker] - A Sound Marker that will be used in the playback. */ Phaser.GameObject.Button.prototype.setOutSound = function (sound, marker) { this.setStateSound(STATE_OUT, sound, marker); }; /** * The Sound to be played when a Pointer presses down on this Button. * * @method Phaser.GameObject.Button#setDownSound * @public * @param {Phaser.Sound|Phaser.AudioSprite} sound - The Sound that will be played. * @param {string} [marker] - A Sound Marker that will be used in the playback. */ Phaser.GameObject.Button.prototype.setDownSound = function (sound, marker) { this.setStateSound(STATE_DOWN, sound, marker); }; /** * The Sound to be played when a Pointer has pressed down and is released from this Button. * * @method Phaser.GameObject.Button#setUpSound * @public * @param {Phaser.Sound|Phaser.AudioSprite} sound - The Sound that will be played. * @param {string} [marker] - A Sound Marker that will be used in the playback. */ Phaser.GameObject.Button.prototype.setUpSound = function (sound, marker) { this.setStateSound(STATE_UP, sound, marker); }; /** * Internal function that handles input events. * * @method Phaser.GameObject.Button#onInputOverHandler * @protected * @param {Phaser.GameObject.Button} sprite - The Button that the event occurred on. * @param {Phaser.Pointer} pointer - The Pointer that activated the Button. */ Phaser.GameObject.Button.prototype.onInputOverHandler = function (sprite, pointer) { if (pointer.justReleased() && (this.justReleasedPreventsOver & pointer.pointerMode) === pointer.pointerMode) { // If the Pointer was only just released then we don't fire an over event return; } this.changeStateFrame(STATE_OVER); if (this.onOverMouseOnly && !pointer.isMouse) { return; } this.playStateSound(STATE_OVER); if (this.onInputOver) { this.onInputOver.dispatch(this, pointer); } }; /** * Internal function that handles input events. * * @method Phaser.GameObject.Button#onInputOutHandler * @protected * @param {Phaser.GameObject.Button} sprite - The Button that the event occurred on. * @param {Phaser.Pointer} pointer - The Pointer that activated the Button. */ Phaser.GameObject.Button.prototype.onInputOutHandler = function (sprite, pointer) { this.changeStateFrame(STATE_OUT); this.playStateSound(STATE_OUT); if (this.onInputOut) { this.onInputOut.dispatch(this, pointer); } }; /** * Internal function that handles input events. * * @method Phaser.GameObject.Button#onInputDownHandler * @protected * @param {Phaser.GameObject.Button} sprite - The Button that the event occurred on. * @param {Phaser.Pointer} pointer - The Pointer that activated the Button. */ Phaser.GameObject.Button.prototype.onInputDownHandler = function (sprite, pointer) { this.changeStateFrame(STATE_DOWN); this.playStateSound(STATE_DOWN); if (this.onInputDown) { this.onInputDown.dispatch(this, pointer); } }; /** * Internal function that handles input events. * * @method Phaser.GameObject.Button#onInputUpHandler * @protected * @param {Phaser.GameObject.Button} sprite - The Button that the event occurred on. * @param {Phaser.Pointer} pointer - The Pointer that activated the Button. */ Phaser.GameObject.Button.prototype.onInputUpHandler = function (sprite, pointer, isOver) { this.playStateSound(STATE_UP); // Input dispatched early, before state change (but after sound) if (this.onInputUp) { this.onInputUp.dispatch(this, pointer, isOver); } if (this.freezeFrames) { return; } if (this.forceOut === true || (this.forceOut & pointer.pointerMode) === pointer.pointerMode) { this.changeStateFrame(STATE_OUT); } else { var changedUp = this.changeStateFrame(STATE_UP); if (!changedUp) { // No Up frame to show.. if (isOver) { this.changeStateFrame(STATE_OVER); } else { this.changeStateFrame(STATE_OUT); } } } };