phaser-ce
Version:
Phaser CE (Community Edition) is a fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers.
658 lines (574 loc) • 18.6 kB
JavaScript
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* This is the core internal game clock.
*
* It manages the elapsed time and calculation of elapsed values, used for game object motion and tweens,
* and also handles the standard Timer pool.
*
* To create a general timed event, use the master {@link Phaser.Timer} accessible through {@link Phaser.Time#events events}.
*
* There are different types of time in Phaser.
*
* Animations, lifespan, particles, physics, timers, and tweens use game time, represented by {@link Phaser.Time#delta} and {@link Phaser.Time#deltaTotal}.
* Game time is scaled by {@link Phaser.Time#slowMotion} and does not advance when paused.
*
* Input, sounds, and the Scale Manager use clock time, represented by {@link Phaser.Time#time}.
*
* @class Phaser.Time
* @constructor
* @param {Phaser.Game} game A reference to the currently running game.
*/
Phaser.Time = function (game)
{
/**
* @property {Phaser.Game} game - Local reference to game.
* @protected
*/
this.game = game;
/**
* The `Date.now()` value when the time was last updated.
* @property {integer} time
* @protected
*/
this.time = 0;
/**
* An increasing value representing cumulative milliseconds since an undisclosed epoch.
*
* While this value is in milliseconds and can be used to compute time deltas,
* it must must _not_ be used with `Date.now()` as it may not use the same epoch / starting reference.
*
* The source may either be from a high-res source (eg. if RAF is available) or the standard Date.now;
* the value can only be relied upon within a particular game instance.
*
* This is updated only once per animation frame, even if multiple logic update steps are done.
*
* @property {number} now
* @protected
*/
this.now = 0;
/**
* Elapsed time since the last time update, in milliseconds, based on `now`.
*
* This value _may_ include time that the game is paused/inactive.
*
* While the game is active, this will be similar to (1000 / {@link #fps}).
*
* This is updated only once per animation frame, even if multiple logic update steps are done.
*
* Don't use this for game timing. Use {@link #delta} instead.
*
* @property {number} elapsed
* @see Phaser.Time.time
* @protected
*/
this.elapsed = 0;
/**
* The time in ms since the last time update, in milliseconds, based on `time`.
*
* This value is corrected for game pauses and will be "about zero" after a game is resumed.
*
* This is updated at each logic update, possibly more than once per game loop.
* If multiple consecutive logic update steps are done, `elapsedMS` will be close to zero after the first.
*
* Don't use this for game timing. Use {@link #deltaTime} instead.
*
* @property {integer} elapsedMS
* @protected
*/
this.elapsedMS = 0;
/**
* The current game step interval in milliseconds.
* @property {number} delta
*/
this.delta = 0;
/**
* The total of all step intervals in milliseconds.
* @property {number} deltaTotal
*/
this.deltaTotal = 0;
/**
* The maximum acceptable step interval in milliseconds, based on `desiredMinFps`. You can also set this directly.
* @property {number} deltaMax
*/
this.deltaMax = 200;
/**
* The desired step interval in seconds, based on `desiredFps`.
* @property {integer} desiredFpsMult
* @protected
*/
this.desiredFpsMult = 1.0 / 60;
/**
* The desired frame rate of the game.
* @property {number} _desiredFps
* @private
* @default
* @see Phaser.Time#desiredFps
*/
this._desiredFps = 60;
/**
* The suggested frame rate for your game, based on an averaged real frame rate.
* This value is only populated if `Time.advancedTiming` is enabled.
*
* _Note:_ This is not available until after a few frames have passed; until then
* it's set to the same value as desiredFps.
*
* @property {number} suggestedFps
* @default
*/
this.suggestedFps = this.desiredFps;
/**
* Scaling factor to make the game move smoothly in slow motion (or fast motion)
*
* - 1.0 = normal speed
* - 2.0 = half speed
* - 0.5 = double speed
*
* @property {number} slowMotion
* @default
*/
this.slowMotion = 1.0;
/**
* If true then advanced profiling, including the fps rate, fps min/max, suggestedFps and msMin/msMax are updated. This isn't expensive, but displaying it with {@link Phaser.Utils.Debug#text} can be, especially in WebGL mode.
* @property {boolean} advancedTiming
* @default
*/
this.advancedTiming = false;
/**
* Advanced timing result: The number of animation frames received from the browser in the last second.
*
* Only calculated if {@link Phaser.Time#advancedTiming advancedTiming} is enabled.
* @property {integer} frames
* @readonly
*/
this.frames = 0;
/**
* Advanced timing result: The number of {@link Phaser.Game#updateLogic logic updates} made in the last second.
*
* Only calculated if {@link Phaser.Time#advancedTiming advancedTiming} is enabled.
* @property {integer} updates
* @readonly
*/
this.updates = 0;
/**
* Advanced timing result: The number of {@link Phaser.Game#updateRender renders} made in the last second.
*
* Only calculated if {@link Phaser.Time#advancedTiming advancedTiming} is enabled.
* @property {integer} renders
* @readonly
*/
this.renders = 0;
/**
* Advanced timing result: Frames per second.
*
* Only calculated if {@link Phaser.Time#advancedTiming advancedTiming} is enabled.
* @property {number} fps
* @readonly
*/
this.fps = 0;
/**
* Advanced timing result: Logic updates per second.
*
* Only calculated if {@link Phaser.Time#advancedTiming advancedTiming} is enabled.
* @property {number} ups
* @readonly
*/
this.ups = 0;
/**
* Advanced timing result: Renders per second.
*
* Only calculated if {@link Phaser.Time#advancedTiming advancedTiming} is enabled.
* @property {number} rps
* @readonly
*/
this.rps = 0;
/**
* Advanced timing result: The lowest rate the fps has dropped to.
*
* Only calculated if {@link Phaser.Time#advancedTiming advancedTiming} is enabled.
* This value can be manually reset.
* @property {number} fpsMin
*/
this.fpsMin = 1000;
/**
* Advanced timing result: The highest rate the fps has reached (usually no higher than 60fps).
*
* Only calculated if {@link Phaser.Time#advancedTiming advancedTiming} is enabled.
* This value can be manually reset.
* @property {number} fpsMax
*/
this.fpsMax = 0;
/**
* Advanced timing result: The minimum amount of time the game has taken between consecutive frames.
*
* Only calculated if {@link Phaser.Time#advancedTiming advancedTiming} is enabled.
* This value can be manually reset.
* @property {number} msMin
* @default
*/
this.msMin = 1000;
/**
* Advanced timing result: The maximum amount of time the game has taken between consecutive frames.
*
* Only calculated if {@link Phaser.Time#advancedTiming advancedTiming} is enabled.
* This value can be manually reset.
* @property {number} msMax
*/
this.msMax = 0;
/**
* Records how long the game was last paused, in milliseconds.
* (This is not updated until the game is resumed.)
* @property {number} pauseDuration
*/
this.pauseDuration = 0;
/**
* @property {number} timeToCall - The value that setTimeout needs to work out when to next update
* @protected
*/
this.timeToCall = 0;
/**
* @property {number} timeExpected - The time when the next call is expected when using setTimer to control the update loop
* @protected
*/
this.timeExpected = 0;
/**
* A {@link Phaser.Timer} object bound to the master clock (this Time object) which events can be added to.
* @property {Phaser.Timer} events
*/
this.events = new Phaser.Timer(this.game, false);
/**
* @property {number} _frameCount - count the number of calls to time.update since the last suggestedFps was calculated
* @private
*/
this._frameCount = 0;
/**
* @property {number} _elapsedAcumulator - sum of the elapsed time since the last suggestedFps was calculated
* @private
*/
this._elapsedAccumulator = 0;
/**
* @property {number} _started - The time at which the Game instance started.
* @private
*/
this._started = 0;
/**
* @property {number} _timeLastSecond - The time (in ms) that the last second counter ticked over.
* @private
*/
this._timeLastSecond = 0;
/**
* @property {number} _pauseStarted - The time the game started being paused.
* @private
*/
this._pauseStarted = 0;
/**
* @property {Phaser.Timer[]} _timers - Internal store of Phaser.Timer objects.
* @private
*/
this._timers = [];
};
Phaser.Time.prototype = {
/**
* Called automatically by Phaser.Game after boot. Should not be called directly.
*
* @method Phaser.Time#boot
* @protected
*/
boot: function ()
{
this._started = Date.now();
this.time = Date.now();
this.events.start();
this.timeExpected = this.time;
},
/**
* Adds an existing Phaser.Timer object to the Timer pool.
*
* @method Phaser.Time#add
* @param {Phaser.Timer} timer - An existing Phaser.Timer object.
* @return {Phaser.Timer} The given Phaser.Timer object.
*/
add: function (timer)
{
this._timers.push(timer);
return timer;
},
/**
* Creates a new stand-alone Phaser.Timer object.
*
* @method Phaser.Time#create
* @param {boolean} [autoDestroy=true] - A Timer that is set to automatically destroy itself will do so after all of its events have been dispatched (assuming no looping events).
* @return {Phaser.Timer} The Timer object that was created.
*/
create: function (autoDestroy)
{
if (autoDestroy === undefined) { autoDestroy = true; }
var timer = new Phaser.Timer(this.game, autoDestroy);
this._timers.push(timer);
return timer;
},
/**
* Remove all Timer objects, regardless of their state and clears all Timers from the {@link Phaser.Time#events events} timer.
*
* @method Phaser.Time#removeAll
*/
removeAll: function ()
{
for (var i = 0; i < this._timers.length; i++)
{
this._timers[i].destroy();
}
this._timers = [];
this.events.removeAll();
},
/**
* Refreshes the Time.time and Time.elapsedMS properties from the system clock.
*
* @method Phaser.Time#refresh
*/
refresh: function ()
{
var previousDateNow = this.time;
this.time = Date.now();
this.elapsedMS = this.time - previousDateNow;
},
/**
* Updates the game clock and advanced timing data (if enabled) from the given timestamp.
*
* This is called automatically by Phaser.Game once per animation frame (RAF or setTimeout).
*
* @method Phaser.Time#update
* @protected
* @param {number} time - The current relative timestamp; see {@link Phaser.Time#now now}.
*/
update: function (time)
{
// Set to the old Date.now value
var previousDateNow = this.time;
var previousNow = this.now;
this.time = Date.now();
this.elapsedMS = this.time - previousDateNow;
this.now = time;
this.elapsed = this.now - previousNow;
if (this.game.raf._isSetTimeOut)
{
this.timeToCall = Math.floor(Math.max(0, (1000.0 / this._desiredFps) + this.timeExpected - time));
this.timeExpected = time + this.timeToCall;
}
if (this.advancedTiming)
{
this.updateAdvancedTiming();
}
},
/**
* Handles the updating of the Phaser.Timers (if any)
* Called automatically by Time.update.
*
* @method Phaser.Time#updateTimers
* @private
*/
updateTimers: function ()
{
var i = 0;
var len = this._timers.length;
if (!len) { return; }
while (i < len)
{
if (this._timers[i].update(this.deltaTotal))
{
i++;
}
else
{
// Timer requests to be removed
this._timers.splice(i, 1);
len--;
}
}
},
/**
* Handles the updating of the advanced timing values (if enabled)
* Called automatically by Time.update.
*
* @method Phaser.Time#updateAdvancedTiming
* @private
*/
updateAdvancedTiming: function ()
{
// count the number of time.update calls
this._frameCount++;
this._elapsedAccumulator += this.elapsed;
// occasionally recalculate the suggestedFps based on the accumulated elapsed time
if (this._frameCount >= this._desiredFps * 2)
{
// this formula calculates suggestedFps in multiples of 5 fps
this.suggestedFps = Math.floor(200 / (this._elapsedAccumulator / this._frameCount)) * 5;
// the precise amount is (1000 * this._frameCount / this._elapsedAccumulator)
this._frameCount = 0;
this._elapsedAccumulator = 0;
}
this.msMin = Math.min(this.msMin, this.elapsed);
this.msMax = Math.max(this.msMax, this.elapsed);
this.frames++;
if (this.now > this._timeLastSecond + 1000)
{
var interval = this.now - this._timeLastSecond;
this.fps = Math.round((this.frames * 1000) / interval);
this.ups = Math.round((this.updates * 1000) / interval);
this.rps = Math.round((this.renders * 1000) / interval);
this.fpsMin = Math.min(this.fpsMin, this.fps);
this.fpsMax = Math.max(this.fpsMax, this.fps);
this._timeLastSecond = this.now;
this.frames = 0;
this.updates = 0;
this.renders = 0;
}
},
/**
* Updates the delta values.
*
* Counts one logic update if advanced timing is enabled.
*
* @method Phaser.Time#preUpdate
* @private
*/
preUpdate: function (delta)
{
delta *= 1000;
this.delta = delta;
this.deltaTotal += delta;
if (!this.game.paused)
{
this.events.update(this.deltaTotal);
this.updateTimers();
}
if (this.advancedTiming)
{
this.updates++;
}
},
/**
* Counts one render (if advanced timing is enabled).
*
* @method Phaser.Time#countRender
* @private
*/
preRender: function ()
{
if (this.advancedTiming)
{
this.renders++;
}
},
/**
* Called when the game enters a paused state.
*
* @method Phaser.Time#gamePaused
* @private
*/
gamePaused: function ()
{
this._pauseStarted = Date.now();
},
/**
* Called when the game resumes from a paused state.
*
* @method Phaser.Time#gameResumed
* @private
*/
gameResumed: function ()
{
// Set the parameter which stores Date.now() to make sure it's correct on resume
this.time = Date.now();
this.pauseDuration = this.time - this._pauseStarted;
},
/**
* The number of seconds that have elapsed since the game was started.
*
* @method Phaser.Time#totalElapsedSeconds
* @return {number} The number of seconds that have elapsed since the game was started.
*/
totalElapsedSeconds: function ()
{
return (this.time - this._started) * 0.001;
},
/**
* How long has passed since the given time.
*
* @method Phaser.Time#elapsedSince
* @param {number} since - The time you want to measure against.
* @return {number} The difference between the given time and now.
*/
elapsedSince: function (since)
{
return this.time - since;
},
/**
* How long has passed since the given time (in seconds).
*
* @method Phaser.Time#elapsedSecondsSince
* @param {number} since - The time you want to measure (in seconds).
* @return {number} Duration between given time and now (in seconds).
*/
elapsedSecondsSince: function (since)
{
return (this.time - since) * 0.001;
},
/**
* Resets the private _started value to now and removes all currently running Timers.
*
* @method Phaser.Time#reset
*/
reset: function ()
{
this._started = this.time;
this.removeAll();
}
};
/**
* The number of logic updates per second.
*
* This is used is used to calculate {@link Phaser.Time#delta} when {@link Phaser.Game#forceSingleUpdate} is off.
*
* The render rate is unaffected unless you also turn off {@link Phaser.Game#forceSingleRender}.
*
* @name Phaser.Time#desiredFps
* @type {integer}
* @default 60
*/
Object.defineProperty(Phaser.Time.prototype, 'desiredFps', {
get: function ()
{
return this._desiredFps;
},
set: function (value)
{
this._desiredFps = value;
this.desiredFpsMult = 1.0 / value;
}
});
/**
* The smallest acceptable logic update rate.
*
* This is used is used to calculate {@link Phaser.Time#deltaMax}.
*
* It should be substantially smaller than {@link Phaser.Time#desiredFps}.
*
* @name Phaser.Time#desiredMinFps
* @type {integer}
* @default 5
*/
Object.defineProperty(Phaser.Time.prototype, 'desiredMinFps', {
get: function ()
{
return 1000 / this.deltaMax;
},
set: function (value)
{
this.deltaMax = 1000 / value;
}
});
Phaser.Time.prototype.constructor = Phaser.Time;