phaser/src/time/Timeline.js

574 lines
16 KiB
JavaScript
Raw Normal View History

2023-03-29 12:38:45 +00:00
/**
* @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.
2023-03-29 12:38:45 +00:00
*
* @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.
2023-03-29 12:38:45 +00:00
*/
var Timeline = new Class({
Extends: EventEmitter,
initialize:
function Timeline (scene, config)
{
EventEmitter.call(this);
/**
* The Scene to which this Timeline belongs.
2023-03-29 12:38:45 +00:00
*
* @name Phaser.Time.Timeline#scene
2023-03-29 12:38:45 +00:00
* @type {Phaser.Scene}
* @since 3.60.0
2023-03-29 12:38:45 +00:00
*/
this.scene = scene;
/**
* A reference to the Scene Systems.
2023-03-29 12:38:45 +00:00
*
* @name Phaser.Time.Timeline#systems
2023-03-29 12:38:45 +00:00
* @type {Phaser.Scenes.Systems}
* @since 3.60.0
2023-03-29 12:38:45 +00:00
*/
this.systems = scene.sys;
/**
* The elapsed time counter.
*
* Treat this as read-only.
*
* @name Phaser.Time.Timeline#elapsed
* @type {number}
* @since 3.60.0
*/
2023-03-29 12:38:45 +00:00
this.elapsed = 0;
/**
* Whether the Timeline is running (`true`) or active (`false`).
2023-03-29 12:38:45 +00:00
*
* When paused, the Timeline will not run any of its actions.
2023-03-29 12:38:45 +00:00
*
* 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
2023-03-29 12:38:45 +00:00
* @type {boolean}
* @default true
* @since 3.60.0
2023-03-29 12:38:45 +00:00
*/
this.paused = true;
/**
* An array of all the Timeline Events.
2023-03-29 12:38:45 +00:00
*
* @name Phaser.Time.Timeline#events
* @type {Phaser.Types.Time.TimelineEvent[]}
* @since 3.60.0
*/
2023-03-29 12:38:45 +00:00
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);
2023-03-29 12:38:45 +00:00
if (config)
{
this.add(config);
}
},
/**
* Updates the elapsed time counter, if this Timeline is not paused.
2023-03-29 12:38:45 +00:00
*
* @method Phaser.Time.Timeline#preUpdate
* @since 3.60.0
2023-03-29 12:38:45 +00:00
*
* @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.
2023-03-29 12:38:45 +00:00
*
* 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
2023-03-29 12:38:45 +00:00
*
* @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 ()
2023-03-29 12:38:45 +00:00
{
if (this.paused)
{
return;
}
var i;
2023-03-29 12:38:45 +00:00
var events = this.events;
var removeSweep = false;
var sys = this.systems;
var target;
2023-03-29 12:38:45 +00:00
for (i = 0; i < events.length; i++)
2023-03-29 12:38:45 +00:00
{
var event = events[i];
if (!event.complete && event.time <= this.elapsed)
{
event.complete = true;
if (event.once)
{
removeSweep = true;
}
if (event.set && event.target)
2023-03-29 12:38:45 +00:00
{
// 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.twen.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);
}
2023-03-29 12:38:45 +00:00
}
target = (event.target) ? event.target : this;
2023-03-29 12:38:45 +00:00
if (event.event)
{
this.emit(event.event, target);
2023-03-29 12:38:45 +00:00
}
if (event.run)
{
event.run.call(target);
2023-03-29 12:38:45 +00:00
}
}
}
if (removeSweep)
{
for (i = 0; i < events.length; i++)
{
if (events[i].complete && events[i].once)
{
events.splice(i, 1);
i--;
}
2023-03-29 12:38:45 +00:00
}
}
},
/**
* 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.
2023-03-29 12:38:45 +00:00
*
* If you wish to resume a paused Timeline, then use the `Timeline.resume` method instead.
2023-03-29 12:38:45 +00:00
*
* @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.
2023-03-29 12:38:45 +00:00
*
* To resume it again, call the `Timeline.resume` method or set the `Timeline.paused` property to `false`.
2023-03-29 12:38:45 +00:00
*
* 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.
2023-03-29 12:38:45 +00:00
*
* @method Phaser.Time.Timeline#pause
* @since 3.60.0
2023-03-29 12:38:45 +00:00
*
* @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.
2023-03-29 12:38:45 +00:00
*/
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;
}
2023-03-29 12:38:45 +00:00
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)
2023-03-29 12:38:45 +00:00
{
startTime = prevTime + fromTime;
2023-03-29 12:38:45 +00:00
}
events.push({
2023-03-29 12:38:45 +00:00
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, 'run', null),
once: GetFastValue(entry, 'once', false)
2023-03-29 12:38:45 +00:00
});
prevTime = startTime;
2023-03-29 12:38:45 +00:00
}
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;
},
2023-03-29 12:38:45 +00:00
/**
* Destroys this Timeline.
2023-03-29 12:38:45 +00:00
*
* 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
2023-03-29 12:38:45 +00:00
*/
destroy: function ()
2023-03-29 12:38:45 +00:00
{
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);
2023-03-29 12:38:45 +00:00
this.scene = null;
this.systems = null;
this.events = [];
2023-03-29 12:38:45 +00:00
}
});
/**
* 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.
2023-03-29 12:38:45 +00:00
*
* @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.
2023-03-29 12:38:45 +00:00
*
* @return {Phaser.Time.Timeline} The Timeline that was created.
*/
GameObjectFactory.register('timeline', function (config)
{
return new Timeline(this.scene, config);
});
module.exports = Timeline;