/** * @author Richard Davey * @copyright 2020 Photon Storm Ltd. * @license {@link https://opensource.org/licenses/MIT|MIT License} */ var ArrayRemove = require('../utils/array/Remove'); var Class = require('../utils/Class'); var NumberTweenBuilder = require('./builders/NumberTweenBuilder'); var PluginCache = require('../plugins/PluginCache'); var SceneEvents = require('../scene/events'); var StaggerBuilder = require('./builders/StaggerBuilder'); var TimelineBuilder = require('./builders/TimelineBuilder'); var TWEEN_CONST = require('./tween/const'); var TweenBuilder = require('./builders/TweenBuilder'); /** * @classdesc * The Tween Manager is a default Scene Plugin which controls and updates Tweens and Timelines. * * @class TweenManager * @memberof Phaser.Tweens * @constructor * @since 3.0.0 * * @param {Phaser.Scene} scene - The Scene which owns this Tween Manager. */ var TweenManager = new Class({ initialize: function TweenManager (scene) { /** * The Scene which owns this Tween Manager. * * @name Phaser.Tweens.TweenManager#scene * @type {Phaser.Scene} * @since 3.0.0 */ this.scene = scene; /** * The Systems object of the Scene which owns this Tween Manager. * * @name Phaser.Tweens.TweenManager#systems * @type {Phaser.Scenes.Systems} * @since 3.0.0 */ this.systems = scene.sys; /** * The time scale of the Tween Manager. * * This value scales the time delta between two frames, thus influencing the speed of time for all Tweens owned by this Tween Manager. * * @name Phaser.Tweens.TweenManager#timeScale * @type {number} * @default 1 * @since 3.0.0 */ this.timeScale = 1; /** * An array of Tweens and Timelines which will be added to the Tween Manager at the start of the frame. * * @name Phaser.Tweens.TweenManager#_add * @type {array} * @private * @since 3.0.0 */ this._add = []; /** * An array of Tweens and Timelines pending to be later added to the Tween Manager. * * @name Phaser.Tweens.TweenManager#_pending * @type {array} * @private * @since 3.0.0 */ this._pending = []; /** * An array of Tweens and Timelines which are still incomplete and are actively processed by the Tween Manager. * * @name Phaser.Tweens.TweenManager#_active * @type {array} * @private * @since 3.0.0 */ this._active = []; /** * An array of Tweens and Timelines which will be removed from the Tween Manager at the start of the frame. * * @name Phaser.Tweens.TweenManager#_destroy * @type {array} * @private * @since 3.0.0 */ this._destroy = []; /** * The number of Tweens and Timelines which need to be processed by the Tween Manager at the start of the frame. * * @name Phaser.Tweens.TweenManager#_toProcess * @type {integer} * @private * @default 0 * @since 3.0.0 */ this._toProcess = 0; 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.Tweens.TweenManager#boot * @private * @since 3.5.1 */ boot: function () { this.systems.events.once(SceneEvents.DESTROY, this.destroy, this); }, /** * 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.Tweens.TweenManager#start * @private * @since 3.5.0 */ start: function () { var eventEmitter = this.systems.events; eventEmitter.on(SceneEvents.PRE_UPDATE, this.preUpdate, this); eventEmitter.on(SceneEvents.UPDATE, this.update, this); eventEmitter.once(SceneEvents.SHUTDOWN, this.shutdown, this); this.timeScale = 1; }, /** * Create a Tween Timeline and return it, but do NOT add it to the active or pending Tween lists. * * @method Phaser.Tweens.TweenManager#createTimeline * @since 3.0.0 * * @param {Phaser.Types.Tweens.TimelineBuilderConfig} [config] - The configuration object for the Timeline and its Tweens. * * @return {Phaser.Tweens.Timeline} The created Timeline object. */ createTimeline: function (config) { return TimelineBuilder(this, config); }, /** * Create a Tween Timeline and add it to the active Tween list/ * * @method Phaser.Tweens.TweenManager#timeline * @since 3.0.0 * * @param {Phaser.Types.Tweens.TimelineBuilderConfig} [config] - The configuration object for the Timeline and its Tweens. * * @return {Phaser.Tweens.Timeline} The created Timeline object. */ timeline: function (config) { var timeline = TimelineBuilder(this, config); if (!timeline.paused) { this._add.push(timeline); this._toProcess++; } return timeline; }, /** * Create a Tween and return it, but do NOT add it to the active or pending Tween lists. * * @method Phaser.Tweens.TweenManager#create * @since 3.0.0 * * @param {Phaser.Types.Tweens.TweenBuilderConfig|object} config - The configuration object for the Tween. * * @return {Phaser.Tweens.Tween} The created Tween object. */ create: function (config) { return TweenBuilder(this, config); }, /** * Create a Tween and add it to the active Tween list. * * @method Phaser.Tweens.TweenManager#add * @since 3.0.0 * * @param {Phaser.Types.Tweens.TweenBuilderConfig|object} config - The configuration object for the Tween. * * @return {Phaser.Tweens.Tween} The created Tween. */ add: function (config) { var tween = TweenBuilder(this, config); this._add.push(tween); this._toProcess++; return tween; }, /** * Add an existing tween into the active Tween list. * * @method Phaser.Tweens.TweenManager#existing * @since 3.0.0 * * @param {Phaser.Tweens.Tween} tween - The Tween to add. * * @return {Phaser.Tweens.TweenManager} This Tween Manager object. */ existing: function (tween) { this._add.push(tween); this._toProcess++; return this; }, /** * Create a Number Tween and add it to the active Tween list. * * @method Phaser.Tweens.TweenManager#addCounter * @since 3.0.0 * * @param {Phaser.Types.Tweens.NumberTweenBuilderConfig} config - The configuration object for the Number Tween. * * @return {Phaser.Tweens.Tween} The created Number Tween. */ addCounter: function (config) { var tween = NumberTweenBuilder(this, config); this._add.push(tween); this._toProcess++; return tween; }, /** * Creates a Stagger function to be used by a Tween property. * * The stagger function will allow you to stagger changes to the value of the property across all targets of the tween. * * This is only worth using if the tween has multiple targets. * * The following will stagger the delay by 100ms across all targets of the tween, causing them to scale down to 0.2 * over the duration specified: * * ```javascript * this.tweens.add({ * targets: [ ... ], * scale: 0.2, * ease: 'linear', * duration: 1000, * delay: this.tweens.stagger(100) * }); * ``` * * The following will stagger the delay by 500ms across all targets of the tween using a 10 x 6 grid, staggering * from the center out, using a cubic ease. * * ```javascript * this.tweens.add({ * targets: [ ... ], * scale: 0.2, * ease: 'linear', * duration: 1000, * delay: this.tweens.stagger(500, { grid: [ 10, 6 ], from: 'center', ease: 'cubic.out' }) * }); * ``` * * @method Phaser.Tweens.TweenManager#stagger * @since 3.19.0 * * @param {(number|number[])} value - The amount to stagger by, or an array containing two elements representing the min and max values to stagger between. * @param {Phaser.Types.Tweens.StaggerConfig} config - The configuration object for the Stagger function. * * @return {function} The stagger function. */ stagger: function (value, options) { return StaggerBuilder(value, options); }, /** * Updates the Tween Manager's internal lists at the start of the frame. * * This method will return immediately if no changes have been indicated. * * @method Phaser.Tweens.TweenManager#preUpdate * @since 3.0.0 */ preUpdate: function () { if (this._toProcess === 0) { // Quick bail return; } var list = this._destroy; var active = this._active; var pending = this._pending; var i; var tween; // Clear the 'destroy' list for (i = 0; i < list.length; i++) { tween = list[i]; // Remove from the 'active' array var idx = active.indexOf(tween); if (idx === -1) { // Not in the active array, is it in pending instead? idx = pending.indexOf(tween); if (idx > -1) { tween.state = TWEEN_CONST.REMOVED; pending.splice(idx, 1); } } else { tween.state = TWEEN_CONST.REMOVED; active.splice(idx, 1); } } list.length = 0; // Process the addition list // This stops callbacks and out of sync events from populating the active array mid-way during the update list = this._add; for (i = 0; i < list.length; i++) { tween = list[i]; if (tween.state === TWEEN_CONST.PENDING_ADD) { // Return true if the Tween should be started right away, otherwise false if (tween.init()) { tween.play(); this._active.push(tween); } else { this._pending.push(tween); } } } list.length = 0; this._toProcess = 0; }, /** * Updates all Tweens and Timelines of the Tween Manager. * * @method Phaser.Tweens.TweenManager#update * @since 3.0.0 * * @param {number} timestamp - The current time in milliseconds. * @param {number} delta - The delta time in ms since the last frame. This is a smoothed and capped value based on the FPS rate. */ update: function (timestamp, delta) { // Process active tweens var list = this._active; var tween; // Scale the delta delta *= this.timeScale; for (var i = 0; i < list.length; i++) { tween = list[i]; // If Tween.update returns 'true' then it means it has completed, // so move it to the destroy list if (tween.update(timestamp, delta)) { this._destroy.push(tween); this._toProcess++; } } }, /** * Removes the given tween from the Tween Manager, regardless of its state (pending or active). * * @method Phaser.Tweens.TweenManager#remove * @since 3.17.0 * * @param {Phaser.Tweens.Tween} tween - The Tween to be removed. * * @return {Phaser.Tweens.TweenManager} This Tween Manager object. */ remove: function (tween) { ArrayRemove(this._add, tween); ArrayRemove(this._pending, tween); ArrayRemove(this._active, tween); ArrayRemove(this._destroy, tween); tween.state = TWEEN_CONST.REMOVED; return this; }, /** * Checks if a Tween or Timeline is active and adds it to the Tween Manager at the start of the frame if it isn't. * * @method Phaser.Tweens.TweenManager#makeActive * @since 3.0.0 * * @param {Phaser.Tweens.Tween} tween - The Tween to check. * * @return {Phaser.Tweens.TweenManager} This Tween Manager object. */ makeActive: function (tween) { if (this._add.indexOf(tween) !== -1 || this._active.indexOf(tween) !== -1) { return this; } var idx = this._pending.indexOf(tween); if (idx !== -1) { this._pending.splice(idx, 1); } this._add.push(tween); tween.state = TWEEN_CONST.PENDING_ADD; this._toProcess++; return this; }, /** * Passes all Tweens to the given callback. * * @method Phaser.Tweens.TweenManager#each * @since 3.0.0 * * @param {function} callback - The function to call. * @param {object} [scope] - The scope (`this` object) to call the function with. * @param {...*} [args] - The arguments to pass into the function. Its first argument will always be the Tween currently being iterated. */ each: function (callback, scope) { var args = [ null ]; for (var i = 1; i < arguments.length; i++) { args.push(arguments[i]); } for (var texture in this.list) { args[0] = this.list[texture]; callback.apply(scope, args); } }, /** * Returns an array of all active Tweens and Timelines in the Tween Manager. * * @method Phaser.Tweens.TweenManager#getAllTweens * @since 3.0.0 * * @return {Phaser.Tweens.Tween[]} A new array containing references to all active Tweens and Timelines. */ getAllTweens: function () { var list = this._active; var output = []; for (var i = 0; i < list.length; i++) { output.push(list[i]); } return output; }, /** * Returns the scale of the time delta for all Tweens and Timelines owned by this Tween Manager. * * @method Phaser.Tweens.TweenManager#getGlobalTimeScale * @since 3.0.0 * * @return {number} The scale of the time delta, usually 1. */ getGlobalTimeScale: function () { return this.timeScale; }, /** * Returns an array of all Tweens or Timelines in the Tween Manager which affect the given target or array of targets. * * @method Phaser.Tweens.TweenManager#getTweensOf * @since 3.0.0 * * @param {(object|array)} target - The target to look for. Provide an array to look for multiple targets. * * @return {Phaser.Tweens.Tween[]} A new array containing all Tweens and Timelines which affect the given target(s). */ getTweensOf: function (target) { var list = this._active; var tween; var output = []; var i; if (Array.isArray(target)) { for (i = 0; i < list.length; i++) { tween = list[i]; for (var t = 0; t < target.length; t++) { if (tween.hasTarget(target[t])) { output.push(tween); } } } } else { for (i = 0; i < list.length; i++) { tween = list[i]; if (tween.hasTarget(target)) { output.push(tween); } } } return output; }, /** * Checks if the given object is being affected by a playing Tween. * * @method Phaser.Tweens.TweenManager#isTweening * @since 3.0.0 * * @param {object} target - target Phaser.Tweens.Tween object * * @return {boolean} returns if target tween object is active or not */ isTweening: function (target) { var list = this._active; var tween; for (var i = 0; i < list.length; i++) { tween = list[i]; if (tween.hasTarget(target) && tween.isPlaying()) { return true; } } return false; }, /** * Stops all Tweens in this Tween Manager. They will be removed at the start of the frame. * * @method Phaser.Tweens.TweenManager#killAll * @since 3.0.0 * * @return {Phaser.Tweens.TweenManager} This Tween Manager. */ killAll: function () { var tweens = this.getAllTweens(); for (var i = 0; i < tweens.length; i++) { tweens[i].stop(); } return this; }, /** * Stops all Tweens which affect the given target or array of targets. The Tweens will be removed from the Tween Manager at the start of the frame. * * @see {@link #getTweensOf} * * @method Phaser.Tweens.TweenManager#killTweensOf * @since 3.0.0 * * @param {(object|array)} target - The target to look for. Provide an array to look for multiple targets. * * @return {Phaser.Tweens.TweenManager} This Tween Manager. */ killTweensOf: function (target) { var tweens = this.getTweensOf(target); for (var i = 0; i < tweens.length; i++) { tweens[i].stop(); } return this; }, /** * Pauses all Tweens in this Tween Manager. * * @method Phaser.Tweens.TweenManager#pauseAll * @since 3.0.0 * * @return {Phaser.Tweens.TweenManager} This Tween Manager. */ pauseAll: function () { var list = this._active; for (var i = 0; i < list.length; i++) { list[i].pause(); } return this; }, /** * Resumes all Tweens in this Tween Manager. * * @method Phaser.Tweens.TweenManager#resumeAll * @since 3.0.0 * * @return {Phaser.Tweens.TweenManager} This Tween Manager. */ resumeAll: function () { var list = this._active; for (var i = 0; i < list.length; i++) { list[i].resume(); } return this; }, /** * Sets a new scale of the time delta for this Tween Manager. * * The time delta is the time elapsed between two consecutive frames and influences the speed of time for this Tween Manager and all Tweens it owns. Values higher than 1 increase the speed of time, while values smaller than 1 decrease it. A value of 0 freezes time and is effectively equivalent to pausing all Tweens. * * @method Phaser.Tweens.TweenManager#setGlobalTimeScale * @since 3.0.0 * * @param {number} value - The new scale of the time delta, where 1 is the normal speed. * * @return {Phaser.Tweens.TweenManager} This Tween Manager. */ setGlobalTimeScale: function (value) { this.timeScale = value; return this; }, /** * The Scene that owns this plugin is shutting down. * We need to kill and reset all internal properties as well as stop listening to Scene events. * * @method Phaser.Tweens.TweenManager#shutdown * @since 3.0.0 */ shutdown: function () { this.killAll(); this._add = []; this._pending = []; this._active = []; this._destroy = []; this._toProcess = 0; var eventEmitter = this.systems.events; eventEmitter.off(SceneEvents.PRE_UPDATE, this.preUpdate, this); eventEmitter.off(SceneEvents.UPDATE, this.update, this); eventEmitter.off(SceneEvents.SHUTDOWN, this.shutdown, this); }, /** * The Scene that owns this plugin is being destroyed. * We need to shutdown and then kill off all external references. * * @method Phaser.Tweens.TweenManager#destroy * @since 3.0.0 */ destroy: function () { this.shutdown(); this.scene.sys.events.off(SceneEvents.START, this.start, this); this.scene = null; this.systems = null; } }); PluginCache.register('TweenManager', TweenManager, 'tweens'); module.exports = TweenManager;