mirror of
https://github.com/photonstorm/phaser
synced 2025-01-10 20:28:56 +00:00
573 lines
16 KiB
JavaScript
573 lines
16 KiB
JavaScript
/**
|
|
* @author Richard Davey <rich@photonstorm.com>
|
|
* @copyright 2013-2023 Photon Storm Ltd.
|
|
* @license {@link https://opensource.org/licenses/MIT|MIT License}
|
|
*/
|
|
|
|
var Class = require('../utils/Class');
|
|
var EventEmitter = require('eventemitter3');
|
|
var GameObjectFactory = require('../gameobjects/GameObjectFactory');
|
|
var GetFastValue = require('../utils/object/GetFastValue');
|
|
var SceneEvents = require('../scene/events');
|
|
|
|
/**
|
|
* @classdesc
|
|
* A Timeline is a way to schedule events to happen at specific times in the future.
|
|
*
|
|
* You can think of it as an event sequencer for your game, allowing you to schedule the
|
|
* running of callbacks, events and other actions at specific times in the future.
|
|
*
|
|
* A Timeline is a Scene level system, meaning you can have as many Timelines as you like, each
|
|
* belonging to a different Scene. You can also have multiple Timelines running at the same time.
|
|
*
|
|
* If the Scene is paused, the Timeline will also pause. If the Scene is destroyed, the Timeline
|
|
* will be automatically destroyed. However, you can control the Timeline directly, pausing,
|
|
* resuming and stopping it at any time.
|
|
*
|
|
* Create an instance of a Timeline via the Game Object Factory:
|
|
*
|
|
* ```js
|
|
* const timeline = this.add.timeline();
|
|
* ```
|
|
*
|
|
* The Timeline always starts paused. You must call `play` on it to start it running.
|
|
*
|
|
* You can also pass in a configuration object on creation, or an array of them:
|
|
*
|
|
* ```js
|
|
* const timeline = this.add.timeline({
|
|
* at: 1000,
|
|
* run: () => {
|
|
* this.add.sprite(400, 300, 'logo');
|
|
* }
|
|
* });
|
|
*
|
|
* timeline.play();
|
|
* ```
|
|
*
|
|
* In this example we sequence a few different events:
|
|
*
|
|
* ```js
|
|
* const timeline = this.add.timeline([
|
|
* {
|
|
* at: 1000,
|
|
* run: () => { this.logo = this.add.sprite(400, 300, 'logo'); },
|
|
* sound: 'TitleMusic'
|
|
* },
|
|
* {
|
|
* at: 2500,
|
|
* tween: {
|
|
* targets: this.logo,
|
|
* y: 600,
|
|
* yoyo: true
|
|
* },
|
|
* sound: 'Explode'
|
|
* },
|
|
* {
|
|
* at: 8000,
|
|
* event: 'HURRY_PLAYER',
|
|
* target: this.background,
|
|
* set: {
|
|
* tint: 0xff0000
|
|
* }
|
|
* }
|
|
* ]);
|
|
*
|
|
* timeline.play();
|
|
* ```
|
|
*
|
|
* There are lots of options available to you via the configuration object. See the
|
|
* {@link Phaser.Types.Time.TimelineEventConfig} typedef for more details.
|
|
*
|
|
* @class Timeline
|
|
* @memberof Phaser.Time
|
|
* @constructor
|
|
* @since 3.60.0
|
|
*
|
|
* @param {Phaser.Scene} scene - The Scene which owns this Timeline.
|
|
* @param {Phaser.Types.Time.TimelineEventConfig|Phaser.Types.Time.TimelineEventConfig[]} config - The configuration object for this Timeline Event, or an array of them.
|
|
*/
|
|
var Timeline = new Class({
|
|
|
|
Extends: EventEmitter,
|
|
|
|
initialize:
|
|
|
|
function Timeline (scene, config)
|
|
{
|
|
EventEmitter.call(this);
|
|
|
|
/**
|
|
* The Scene to which this Timeline belongs.
|
|
*
|
|
* @name Phaser.Time.Timeline#scene
|
|
* @type {Phaser.Scene}
|
|
* @since 3.60.0
|
|
*/
|
|
this.scene = scene;
|
|
|
|
/**
|
|
* A reference to the Scene Systems.
|
|
*
|
|
* @name Phaser.Time.Timeline#systems
|
|
* @type {Phaser.Scenes.Systems}
|
|
* @since 3.60.0
|
|
*/
|
|
this.systems = scene.sys;
|
|
|
|
/**
|
|
* The elapsed time counter.
|
|
*
|
|
* Treat this as read-only.
|
|
*
|
|
* @name Phaser.Time.Timeline#elapsed
|
|
* @type {number}
|
|
* @since 3.60.0
|
|
*/
|
|
this.elapsed = 0;
|
|
|
|
/**
|
|
* Whether the Timeline is running (`true`) or active (`false`).
|
|
*
|
|
* When paused, the Timeline will not run any of its actions.
|
|
*
|
|
* By default a Timeline is always paused and should be started by
|
|
* calling the `Timeline.play` method.
|
|
*
|
|
* You can use the `Timeline.pause` and `Timeline.resume` methods to control
|
|
* this value in a chainable way.
|
|
*
|
|
* @name Phaser.Time.Timeline#paused
|
|
* @type {boolean}
|
|
* @default true
|
|
* @since 3.60.0
|
|
*/
|
|
this.paused = true;
|
|
|
|
/**
|
|
* An array of all the Timeline Events.
|
|
*
|
|
* @name Phaser.Time.Timeline#events
|
|
* @type {Phaser.Types.Time.TimelineEvent[]}
|
|
* @since 3.60.0
|
|
*/
|
|
this.events = [];
|
|
|
|
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.destroy, this);
|
|
|
|
if (config)
|
|
{
|
|
this.add(config);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Updates the elapsed time counter, if this Timeline is not paused.
|
|
*
|
|
* @method Phaser.Time.Timeline#preUpdate
|
|
* @since 3.60.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.
|
|
*/
|
|
preUpdate: function (time, delta)
|
|
{
|
|
if (this.paused)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this.elapsed += delta;
|
|
},
|
|
|
|
/**
|
|
* Called automatically by the Scene update step.
|
|
*
|
|
* Iterates through all of the Timeline Events and checks to see if they should be run.
|
|
*
|
|
* If they should be run, then the `TimelineEvent.action` callback is invoked.
|
|
*
|
|
* If the `TimelineEvent.once` property is `true` then the event is removed from the Timeline.
|
|
*
|
|
* If the `TimelineEvent.event` property is set then the Timeline emits that event.
|
|
*
|
|
* If the `TimelineEvent.run` property is set then the Timeline invokes that method.
|
|
*
|
|
* If the `TimelineEvent.target` property is set then the Timeline invokes the `run` method on that target.
|
|
*
|
|
* @method Phaser.Time.Timeline#update
|
|
* @since 3.60.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.
|
|
*/
|
|
update: function ()
|
|
{
|
|
if (this.paused)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var i;
|
|
var events = this.events;
|
|
var removeSweep = false;
|
|
var sys = this.systems;
|
|
var target;
|
|
|
|
for (i = 0; i < events.length; i++)
|
|
{
|
|
var event = events[i];
|
|
|
|
if (!event.complete && event.time <= this.elapsed)
|
|
{
|
|
event.complete = true;
|
|
|
|
if (event.once)
|
|
{
|
|
removeSweep = true;
|
|
}
|
|
|
|
if (event.set && event.target)
|
|
{
|
|
// set is an object of key value pairs, apply them to target
|
|
for (var key in event.set)
|
|
{
|
|
event.target[key] = event.set[key];
|
|
}
|
|
}
|
|
|
|
if (event.tween)
|
|
{
|
|
sys.tweens.add(event.tween);
|
|
}
|
|
|
|
if (event.sound)
|
|
{
|
|
if (typeof event.sound === 'string')
|
|
{
|
|
sys.sound.play(event.sound);
|
|
}
|
|
else
|
|
{
|
|
sys.sound.play(event.sound.key, event.sound.config);
|
|
}
|
|
}
|
|
|
|
target = (event.target) ? event.target : this;
|
|
|
|
if (event.event)
|
|
{
|
|
this.emit(event.event, target);
|
|
}
|
|
|
|
if (event.run)
|
|
{
|
|
event.run.call(target);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (removeSweep)
|
|
{
|
|
for (i = 0; i < events.length; i++)
|
|
{
|
|
if (events[i].complete && events[i].once)
|
|
{
|
|
events.splice(i, 1);
|
|
|
|
i--;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Starts this Timeline running.
|
|
*
|
|
* If the Timeline is already running and the `fromStart` parameter is `true`,
|
|
* then calling this method will reset it the Timeline to the start.
|
|
*
|
|
* If you wish to resume a paused Timeline, then use the `Timeline.resume` method instead.
|
|
*
|
|
* @method Phaser.Time.Timeline#play
|
|
* @since 3.60.0
|
|
*
|
|
* @param {boolean} [fromStart=true] - Reset this Timeline back to the start before playing.
|
|
*
|
|
* @return {this} This Timeline instance.
|
|
*/
|
|
play: function (fromStart)
|
|
{
|
|
if (fromStart === undefined) { fromStart = true; }
|
|
|
|
this.paused = false;
|
|
|
|
if (fromStart)
|
|
{
|
|
this.reset();
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Pauses this Timeline.
|
|
*
|
|
* To resume it again, call the `Timeline.resume` method or set the `Timeline.paused` property to `false`.
|
|
*
|
|
* If the Timeline is paused while processing the current game step, then it
|
|
* will carry on with all events that are due to run during that step and pause
|
|
* from the next game step.
|
|
*
|
|
* @method Phaser.Time.Timeline#pause
|
|
* @since 3.60.0
|
|
*
|
|
* @return {this} This Timeline instance.
|
|
*/
|
|
pause: function ()
|
|
{
|
|
this.paused = true;
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Resumes this Timeline from a paused state.
|
|
*
|
|
* The Timeline will carry on from where it left off.
|
|
*
|
|
* If you need to reset the Timeline to the start, then call the `Timeline.reset` method.
|
|
*
|
|
* @method Phaser.Time.Timeline#resume
|
|
* @since 3.60.0
|
|
*
|
|
* @return {this} This Timeline instance.
|
|
*/
|
|
resume: function ()
|
|
{
|
|
this.paused = false;
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Resets this Timeline back to the start.
|
|
*
|
|
* This will set the elapsed time to zero and set all events to be incomplete.
|
|
*
|
|
* If the Timeline had any events that were set to `once` that have already
|
|
* been removed, they will **not** be present again after calling this method.
|
|
*
|
|
* @method Phaser.Time.Timeline#reset
|
|
* @since 3.60.0
|
|
*
|
|
* @return {this} This Timeline instance.
|
|
*/
|
|
reset: function ()
|
|
{
|
|
this.elapsed = 0;
|
|
|
|
for (var i = 0; i < this.events.length; i++)
|
|
{
|
|
this.events[i].complete = false;
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Adds one or more events to this Timeline.
|
|
*
|
|
*
|
|
* @method Phaser.Time.Timeline#add
|
|
* @since 3.60.0
|
|
*
|
|
* @param {Phaser.Types.Time.TimelineEventConfig|Phaser.Types.Time.TimelineEventConfig[]} config - The configuration object for this Timeline Event, or an array of them.
|
|
*
|
|
* @return {this} This Timeline instance.
|
|
*/
|
|
add: function (config)
|
|
{
|
|
if (!Array.isArray(config))
|
|
{
|
|
config = [ config ];
|
|
}
|
|
|
|
var events = this.events;
|
|
var prevTime = 0;
|
|
|
|
if (events.length > 0)
|
|
{
|
|
prevTime = events[events.length - 1].time;
|
|
}
|
|
|
|
for (var i = 0; i < config.length; i++)
|
|
{
|
|
var entry = config[i];
|
|
|
|
// Start at the exact time given, based on elapsed time (i.e. x ms from the start of the Timeline)
|
|
var startTime = GetFastValue(entry, 'at', 0);
|
|
|
|
// Start in x ms from whatever the current elapsed time is (i.e. x ms from now)
|
|
var offsetTime = GetFastValue(entry, 'in', null);
|
|
|
|
if (offsetTime !== null)
|
|
{
|
|
startTime = this.elapsed + offsetTime;
|
|
}
|
|
|
|
// Start in x ms from whatever the previous event's start time was (i.e. x ms after the previous event)
|
|
var fromTime = GetFastValue(entry, 'from', null);
|
|
|
|
if (fromTime !== null)
|
|
{
|
|
startTime = prevTime + fromTime;
|
|
}
|
|
|
|
events.push({
|
|
complete: false,
|
|
time: startTime,
|
|
run: GetFastValue(entry, 'run', null),
|
|
event: GetFastValue(entry, 'event', null),
|
|
target: GetFastValue(entry, 'target', null),
|
|
set: GetFastValue(entry, 'set', null),
|
|
tween: GetFastValue(entry, 'tween', null),
|
|
sound: GetFastValue(entry, 'sound', null),
|
|
once: GetFastValue(entry, 'once', false)
|
|
});
|
|
|
|
prevTime = startTime;
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Removes all events from this Timeline, resets the elapsed time to zero
|
|
* and pauses the Timeline.
|
|
*
|
|
* @method Phaser.Time.Timeline#clear
|
|
* @since 3.60.0
|
|
*
|
|
* @return {this} This Timeline instance.
|
|
*/
|
|
clear: function ()
|
|
{
|
|
this.events = [];
|
|
this.elapsed = 0;
|
|
this.paused = true;
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Destroys this Timeline.
|
|
*
|
|
* This will remove all events from the Timeline and stop it from processing.
|
|
*
|
|
* This method is called automatically when the Scene shuts down, but you may
|
|
* also call it directly should you need to destroy the Timeline earlier.
|
|
*
|
|
* @method Phaser.Time.Timeline#destroy
|
|
* @since 3.60.0
|
|
*/
|
|
destroy: function ()
|
|
{
|
|
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.destroy, this);
|
|
|
|
this.scene = null;
|
|
this.systems = null;
|
|
this.events = [];
|
|
}
|
|
|
|
});
|
|
|
|
/**
|
|
* A Timeline is a way to schedule events to happen at specific times in the future.
|
|
*
|
|
* You can think of it as an event sequencer for your game, allowing you to schedule the
|
|
* running of callbacks, events and other actions at specific times in the future.
|
|
*
|
|
* A Timeline is a Scene level system, meaning you can have as many Timelines as you like, each
|
|
* belonging to a different Scene. You can also have multiple Timelines running at the same time.
|
|
*
|
|
* If the Scene is paused, the Timeline will also pause. If the Scene is destroyed, the Timeline
|
|
* will be automatically destroyed. However, you can control the Timeline directly, pausing,
|
|
* resuming and stopping it at any time.
|
|
*
|
|
* Create an instance of a Timeline via the Game Object Factory:
|
|
*
|
|
* ```js
|
|
* const timeline = this.add.timeline();
|
|
* ```
|
|
*
|
|
* The Timeline always starts paused. You must call `play` on it to start it running.
|
|
*
|
|
* You can also pass in a configuration object on creation, or an array of them:
|
|
*
|
|
* ```js
|
|
* const timeline = this.add.timeline({
|
|
* at: 1000,
|
|
* run: () => {
|
|
* this.add.sprite(400, 300, 'logo');
|
|
* }
|
|
* });
|
|
*
|
|
* timeline.play();
|
|
* ```
|
|
*
|
|
* In this example we sequence a few different events:
|
|
*
|
|
* ```js
|
|
* const timeline = this.add.timeline([
|
|
* {
|
|
* at: 1000,
|
|
* run: () => { this.logo = this.add.sprite(400, 300, 'logo'); },
|
|
* sound: 'TitleMusic'
|
|
* },
|
|
* {
|
|
* at: 2500,
|
|
* tween: {
|
|
* targets: this.logo,
|
|
* y: 600,
|
|
* yoyo: true
|
|
* },
|
|
* sound: 'Explode'
|
|
* },
|
|
* {
|
|
* at: 8000,
|
|
* event: 'HURRY_PLAYER',
|
|
* target: this.background,
|
|
* set: {
|
|
* tint: 0xff0000
|
|
* }
|
|
* }
|
|
* ]);
|
|
*
|
|
* timeline.play();
|
|
* ```
|
|
*
|
|
* There are lots of options available to you via the configuration object. See the
|
|
* {@link Phaser.Types.Time.TimelineEventConfig} typedef for more details.
|
|
*
|
|
* @method Phaser.GameObjects.GameObjectFactory#timeline
|
|
* @since 3.60.0
|
|
*
|
|
* @param {Phaser.Types.Time.TimelineEventConfig|Phaser.Types.Time.TimelineEventConfig[]} config - The configuration object for this Timeline Event, or an array of them.
|
|
*
|
|
* @return {Phaser.Time.Timeline} The Timeline that was created.
|
|
*/
|
|
GameObjectFactory.register('timeline', function (config)
|
|
{
|
|
return new Timeline(this.scene, config);
|
|
});
|
|
|
|
module.exports = Timeline;
|