From 3c8cd20b709907c8f895998c5a822afea78fd2db Mon Sep 17 00:00:00 2001 From: Richard Davey Date: Thu, 29 Aug 2013 03:52:59 +0100 Subject: [PATCH] RequestAnimationFrame done and optimised massively. PluginManager added (but needs testing). Game now fleshed out with all the state changing and core loop, also optimised heavily. Also Pixi integration started and the basics are working well :) --- examples/math sincos.html | 14 +- examples/pixi 1.html | 139 ++++++++++ examples/raf.html | 45 ++++ examples/wip1.html | 107 +++++++- src/Game.js | 379 ++++++++++++++++++++++++++-- src/core/Plugin.js | 79 ++++++ src/core/PluginManager.js | 199 +++++++++++++++ src/system/RequestAnimationFrame.js | 111 ++++++++ src/time/Time.js | 47 +++- 9 files changed, 1076 insertions(+), 44 deletions(-) create mode 100644 examples/pixi 1.html create mode 100644 examples/raf.html create mode 100644 src/core/Plugin.js create mode 100644 src/core/PluginManager.js create mode 100644 src/system/RequestAnimationFrame.js diff --git a/examples/math sincos.html b/examples/math sincos.html index e7eb48a0..061282ff 100644 --- a/examples/math sincos.html +++ b/examples/math sincos.html @@ -3,13 +3,18 @@ phaser.js - a(nother) new beginning + + + + + @@ -25,14 +30,15 @@ var game = new Phaser.Game(this, '', 800, 600); -var data = Phaser.Math.sinCosGenerator(10); +var data = game.math.sinCosGenerator(10); console.log('Sin', data.sin); console.log('Cos', data.cos); -console.log('Shift value 1', Phaser.Math.shift(data.sin)); -console.log('Shift value 2', Phaser.Math.shift(data.sin)); -console.log('Shift value 3', Phaser.Math.shift(data.sin)); +console.log('Shift value 1', game.math.shift(data.sin)); +console.log('Shift value 2', game.math.shift(data.sin)); +console.log('Shift value 3', game.math.shift(data.sin)); +console.log('Shift value 4', game.math.shift(data.sin)); console.log('Sin', data.sin); diff --git a/examples/pixi 1.html b/examples/pixi 1.html new file mode 100644 index 00000000..4bd70188 --- /dev/null +++ b/examples/pixi 1.html @@ -0,0 +1,139 @@ + + + + phaser.js - a(nother) new beginning + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/raf.html b/examples/raf.html new file mode 100644 index 00000000..939844a7 --- /dev/null +++ b/examples/raf.html @@ -0,0 +1,45 @@ + + + + phaser.js - a(nother) new beginning + + + + + + + + + + + + + + + + + + + + + + + + +
?
+
Game Time: 
+ + + + + \ No newline at end of file diff --git a/examples/wip1.html b/examples/wip1.html index 225755e9..aa95919c 100644 --- a/examples/wip1.html +++ b/examples/wip1.html @@ -3,9 +3,51 @@ phaser.js - a(nother) new beginning + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -29,17 +71,70 @@ var game = new Phaser.Game(this, '', 800, 600); -var test = { x: 0 }; +// PIXI it +var stage = new PIXI.Stage(0x000044); +var renderer = PIXI.autoDetectRenderer(800, 600); +document.body.appendChild(renderer.view); -var tween = game.tweens.create(test); +game.load.image('cockpit', 'assets/pics/cockpit.png'); +game.load.image('rememberMe', 'assets/pics/remember-me.jpg'); +game.load.image('overdose', 'assets/pics/lance-overdose-loader_eye.png'); -tween.onComplete.add(onComplete, this); -tween.to({x: 100}, 1000, Phaser.Easing.Linear.None, true); +game.load.onLoadStart.add(loadStarted, this); +game.load.onFileComplete.add(fileLoaded, this); +game.load.onLoadComplete.add(loadCompleted, this); -function onComplete() { - console.log('tween finished, new data: ', test); +game.load.start(); + +function loadStarted(size) { + console.log('Loader started, queue size:', size); } +// this.progress, previousKey, success, this.queueSize - this._keys.length, this.queueSize +function fileLoaded(progress, key, success, remaining, total) { + console.log('File Loaded:', key); + console.log('Progress: ' + progress + '%'); + console.log('File: ' + remaining + ' out of ' + total); +} + +var bunny; + +function loadCompleted() { + + console.log('Loader finished, creating base textures'); + + // Create a basetexture + var base = new PIXI.BaseTexture(game.cache.getImage('overdose')); + var texture = new PIXI.Texture(base); + bunny = new PIXI.Sprite(texture); + + // center the sprites anchor point + bunny.anchor.x = 0.5; + bunny.anchor.y = 0.5; + + // move the sprite t the center of the screen + bunny.position.x = 200; + bunny.position.y = 150; + + stage.addChild(bunny); + + requestAnimFrame(animate); +} + +function animate() { + + requestAnimFrame( animate ); + + // just for fun, lets rotate mr rabbit a little + bunny.rotation += 0.1; + bunny.scale.x += 0.01; + bunny.scale.y += 0.01; + + // render the stage + renderer.render(stage); +} + + diff --git a/src/Game.js b/src/Game.js index 8f2e6fab..15bdf7d7 100644 --- a/src/Game.js +++ b/src/Game.js @@ -33,18 +33,14 @@ Phaser.Game = function (callbackContext, parent, width, height, preloadCallback, this.onRenderCallback = renderCallback; this.onDestroyCallback = destroyCallback; - var _this = this; - if (document.readyState === 'complete' || document.readyState === 'interactive') { - setTimeout(function () { - return Phaser.GAMES[_this.id].boot(parent, width, height); - }); + setTimeout(Phaser.GAMES[this.id].boot(parent, width, height), 0); } else { - document.addEventListener('DOMContentLoaded', Phaser.GAMES[_this.id].boot(parent, width, height), false); - window.addEventListener('load', Phaser.GAMES[_this.id].boot(parent, width, height), false); + document.addEventListener('DOMContentLoaded', Phaser.GAMES[this.id].boot(parent, width, height), false); + window.addEventListener('load', Phaser.GAMES[this.id].boot(parent, width, height), false); } }; @@ -147,6 +143,95 @@ Phaser.Game.prototype = { */ isRunning: false, + /** + * Automatically handles the core game loop via requestAnimationFrame or setTimeout + */ + raf: null, + + /** + * Reference to the GameObject Factory. + * @type {Phaser.GameObjectFactory} + */ + add: null, + + /** + * Reference to the assets cache. + * @type {Phaser.Cache} + */ + cache: null, + + /** + * Reference to the input manager + * @type {Phaser.InputManager} + */ + input: null, + + /** + * Reference to the assets loader. + * @type {Phaser.Loader} + */ + load: null, + + /** + * Reference to the math helper. + * @type {Phaser.GameMath} + */ + math: null, + + /** + * Reference to the network class. + * @type {Phaser.Net} + */ + net: null, + + /** + * Reference to the sound manager. + * @type {Phaser.SoundManager} + */ + sound: null, + + /** + * Reference to the stage. + * @type {Phaser.Stage} + */ + stage: null, + + /** + * Reference to game clock. + * @type {Phaser.TimeManager} + */ + time: null, + + /** + * Reference to the tween manager. + * @type {Phaser.TweenManager} + */ + tweens: null, + + /** + * Reference to the world. + * @type {Phaser.World} + */ + world: null, + + /** + * Reference to the physics manager. + * @type {Phaser.Physics.PhysicsManager} + */ + physics: null, + + /** + * Instance of repeatable random data generator helper. + * @type {Phaser.RandomDataGenerator} + */ + rnd: null, + + /** + * Contains device information and capabilities. + * @type {Phaser.Device} + */ + device: null, + /** * Initialize engine sub modules and start the game. * @param parent {string} ID of parent Dom element. @@ -155,29 +240,24 @@ Phaser.Game.prototype = { */ boot: function (parent, width, height) { - var _this = this; - if (this.isBooted) { return; } if (!document.body) { - setTimeout(function () { - return Phaser.GAMES[_this.id].boot(parent, width, height); - }, 13); + setTimeout(Phaser.GAMES[this.id].boot(parent, width, height), 13); } else { - document.removeEventListener('DOMContentLoaded', Phaser.GAMES[_this.id].boot); - window.removeEventListener('load', Phaser.GAMES[_this.id].boot); + document.removeEventListener('DOMContentLoaded', Phaser.GAMES[this.id].boot); + window.removeEventListener('load', Phaser.GAMES[this.id].boot); - console.log('Phaser', Phaser.VERSION, 'alive'); + this.onPause = new Phaser.Signal(); + this.onResume = new Phaser.Signal(); - // this.onPause = new Phaser.Signal(); - // this.onResume = new Phaser.Signal(); this.device = new Phaser.Device(); this.net = new Phaser.Net(this); - // this.math = new Phaser.GameMath(this); + this.math = Phaser.Math; // this.stage = new Phaser.Stage(this, parent, width, height); // this.world = new Phaser.World(this, width, height); // this.add = new Phaser.GameObjectFactory(this); @@ -189,16 +269,275 @@ Phaser.Game.prototype = { // this.sound = new Phaser.SoundManager(this); this.rnd = new Phaser.RandomDataGenerator([(Date.now() * Math.random()).toString()]); // this.physics = new Phaser.Physics.PhysicsManager(this); - // this.plugins = new Phaser.PluginManager(this, this); - // this.load.onLoadComplete.add(this.loadComplete, this); + this.plugins = new Phaser.PluginManager(this, this); + + this.load.onLoadComplete.add(this.loadComplete, this); + // this.setRenderer(Phaser.Types.RENDERER_CANVAS); // this.world.boot(); // this.stage.boot(); // this.input.boot(); this.isBooted = true; + + if (this.onPreloadCallback == null && this.onCreateCallback == null && this.onUpdateCallback == null && this.onRenderCallback == null && this._pendingState == null) { + console.warn("Phaser update loop cannot start: No preload, create, update or render functions given and no pending State found"); + } + else + { + console.log('Phaser', Phaser.VERSION, 'alive'); + this.isRunning = true; + this._loadComplete = false; + + this.raf = new Phaser.RequestAnimationFrame(this); + this.raf.start(); + + if (this._pendingState) + { + this.switchState(this._pendingState, false, false); + } + else + { + this.startState(); + } + } + } }, + /** + * Called when the load has finished after preload was run. + */ + loadComplete: function () { + + this._loadComplete = true; + this.onCreateCallback.call(this.callbackContext); + + }, + + /** + * Start the current state + */ + startState: function () { + + if (this.onPreloadCallback !== null) + { + this.load.reset(); + this.onPreloadCallback.call(this.callbackContext); + + // Is the loader empty? + if (this.load.queueSize == 0) + { + if (this.onCreateCallback !== null) + { + this.onCreateCallback.call(this.callbackContext); + } + + this._loadComplete = true; + + } + else + { + // Start the loader going as we have something in the queue + this.load.onLoadComplete.add(this.loadComplete, this); + this.load.start(); + } + } + else + { + // No init? Then there was nothing to load either + if (this.onCreateCallback !== null) { + this.onCreateCallback.call(this.callbackContext); + } + + this._loadComplete = true; + + } + + }, + + /** + * Set the most common state callbacks (init, create, update, render). + * @param preloadCallback {function} Init callback invoked when init state. + * @param createCallback {function} Create callback invoked when create state. + * @param updateCallback {function} Update callback invoked when update state. + * @param renderCallback {function} Render callback invoked when render state. + * @param destroyCallback {function} Destroy callback invoked when state is destroyed. + */ + setCallbacks: function (preloadCallback, createCallback, updateCallback, renderCallback, destroyCallback) { + + if (typeof preloadCallback === "undefined") { preloadCallback = null; } + if (typeof createCallback === "undefined") { createCallback = null; } + if (typeof updateCallback === "undefined") { updateCallback = null; } + if (typeof renderCallback === "undefined") { renderCallback = null; } + if (typeof destroyCallback === "undefined") { destroyCallback = null; } + + this.onPreloadCallback = preloadCallback; + this.onCreateCallback = createCallback; + this.onUpdateCallback = updateCallback; + this.onRenderCallback = renderCallback; + this.onDestroyCallback = destroyCallback; + + }, + + update: function (time) { + + this.time.update(time); + + this.plugins.preUpdate(); + + this.tweens.update(); + this.input.update(); + this.stage.update(); + this.sound.update(); + this.physics.update(); + this.world.update(); + + this.plugins.update(); + + if (this._loadComplete) + { + if (this.onUpdateCallback) + { + this.onUpdateCallback.call(this.callbackContext); + } + + this.world.postUpdate(); + this.plugins.postUpdate(); + this.plugins.preRender(); + + if (this.onPreRenderCallback) + { + this.onPreRenderCallback.call(this.callbackContext); + } + + this.renderer.render(); + this.plugins.render(); + + if (this.onRenderCallback) + { + this.onRenderCallback.call(this.callbackContext); + } + + this.plugins.postRender(); + } + else + { + // Still loading assets + if (this.onLoadUpdateCallback) + { + this.onLoadUpdateCallback.call(this.callbackContext); + } + + this.world.postUpdate(); + this.plugins.postUpdate(); + this.plugins.preRender(); + this.renderer.render(); + this.plugins.render(); + + if (this.onLoadRenderCallback) + { + this.onLoadRenderCallback.call(this.callbackContext); + } + + this.plugins.postRender(); + } + + }, + + /** + * Switch to a new State. + * @param state {State} The state you want to switch to. + * @param [clearWorld] {bool} clear everything in the world? (Default to true) + * @param [clearCache] {bool} clear asset cache? (Default to false and ONLY available when clearWorld=true) + */ + switchState: function (state, clearWorld, clearCache) { + + if (typeof clearWorld === "undefined") { clearWorld = true; } + if (typeof clearCache === "undefined") { clearCache = false; } + + if (this.isBooted == false) { + this._pendingState = state; + return; + } + + // Destroy current state? + if (this.onDestroyCallback !== null) { + this.onDestroyCallback.call(this.callbackContext); + } + + this.input.reset(true); + + // Prototype? + if (typeof state === 'function') + { + this.state = new state(this); + } + else + { + this.state = state; + } + + // Ok, have we got at least a create or update function? + if (this.state['create'] || this.state['update']) { + + this.callbackContext = this.state; + + // Bingo, let's set them up + this.onPreloadCallback = this.state['preload'] || null; + this.onLoadRenderCallback = this.state['loadRender'] || null; + this.onLoadUpdateCallback = this.state['loadUpdate'] || null; + this.onCreateCallback = this.state['create'] || null; + this.onUpdateCallback = this.state['update'] || null; + this.onPreRenderCallback = this.state['preRender'] || null; + this.onRenderCallback = this.state['render'] || null; + this.onPausedCallback = this.state['paused'] || null; + this.onDestroyCallback = this.state['destroy'] || null; + + if (clearWorld) { + + //this.world.destroy(); + + if (clearCache == true) { + this.cache.destroy(); + } + } + + this._loadComplete = false; + + this.startState(); + + } + else + { + console.warn("Invalid Phaser State object given. Must contain at least a create or update function."); + } + }, + + /** + * Nuke the entire game from orbit + */ + destroy: function () { + + this.callbackContext = null; + this.onPreloadCallback = null; + this.onLoadRenderCallback = null; + this.onLoadUpdateCallback = null; + this.onCreateCallback = null; + this.onUpdateCallback = null; + this.onRenderCallback = null; + this.onPausedCallback = null; + this.onDestroyCallback = null; + this.cache = null; + this.input = null; + this.load = null; + this.sound = null; + this.stage = null; + this.time = null; + this.world = null; + this.isBooted = false; + + } + }; diff --git a/src/core/Plugin.js b/src/core/Plugin.js new file mode 100644 index 00000000..568a8375 --- /dev/null +++ b/src/core/Plugin.js @@ -0,0 +1,79 @@ +/** +* Phaser - Plugin +* +* This is a base Plugin template to use for any Phaser plugin development +*/ +Phaser.Plugin = function (game, parent) { + + this.game = game; + this.parent = parent; + + this.active = false; + this.visible = false; + + this.hasPreUpdate = false; + this.hasUpdate = false; + this.hasPostUpdate = false; + this.hasPreRender = false; + this.hasRender = false; + this.hasPostRender = false; + +}; + +Phaser.Plugin.prototype = {, + + /** + * Pre-update is called at the start of the update cycle, before any other updates have taken place. + * It is only called if active is set to true. + */ + preUpdate: function () { + }, + + /** + * Pre-update is called at the start of the update cycle, before any other updates have taken place. + * It is only called if active is set to true. + */ + update: function () { + }, + + /** + * Post-update is called at the end of the objects update cycle, after other update logic has taken place. + * It is only called if active is set to true. + */ + postUpdate: function () { + }, + + /** + * Pre-render is called right before the Game Renderer starts and before any custom preRender callbacks have been run. + * It is only called if visible is set to true. + */ + preRender: function () { + }, + + /** + * Pre-render is called right before the Game Renderer starts and before any custom preRender callbacks have been run. + * It is only called if visible is set to true. + */ + render: function () { + }, + + /** + * Post-render is called after every camera and game object has been rendered, also after any custom postRender callbacks have been run. + * It is only called if visible is set to true. + */ + postRender: function () { + }, + + /** + * Clear down this Plugin and null out references + */ + destroy: function () { + + this.game = null; + this.parent = null; + this.active = false; + this.visible = false; + + } + +}; diff --git a/src/core/PluginManager.js b/src/core/PluginManager.js new file mode 100644 index 00000000..4509c289 --- /dev/null +++ b/src/core/PluginManager.js @@ -0,0 +1,199 @@ +/** +* Phaser - PluginManager +* +* TODO: We can optimise this a lot by using separate hashes per function (update, render, etc) +*/ + +Phaser.PluginManager = function(game, parent) { + + this.game = game; + this._parent = parent; + this.plugins = []; + this._pluginsLength = 0; + +}; + +Phaser.PluginManager.prototype = {, + + /** + * Add a new Plugin to the PluginManager. + * The plugins game and parent reference are set to this game and pluginmanager parent. + * @type {Phaser.Plugin} + */ + add: function (plugin) { + + var result = false; + + // Prototype? + if (typeof plugin === 'function') + { + plugin = new plugin(this.game, this._parent); + } + else + { + plugin.game = this.game; + plugin.parent = this._parent; + } + + // Check for methods now to avoid having to do this every loop + if (typeof plugin['preUpdate'] === 'function') { + plugin.hasPreUpdate = true; + result = true; + } + + if (typeof plugin['update'] === 'function') { + plugin.hasUpdate = true; + result = true; + } + + if (typeof plugin['postUpdate'] === 'function') { + plugin.hasPostUpdate = true; + result = true; + } + + if (typeof plugin['preRender'] === 'function') { + plugin.hasPreRender = true; + result = true; + } + + if (typeof plugin['render'] === 'function') { + plugin.hasRender = true; + result = true; + } + + if (typeof plugin['postRender'] === 'function') { + plugin.hasPostRender = true; + result = true; + } + + // The plugin must have at least one of the above functions to be added to the PluginManager. + if (result) { + + if (plugin.hasPreUpdate || plugin.hasUpdate || plugin.hasPostUpdate) + { + plugin.active = true; + } + + if (plugin.hasPreRender || plugin.hasRender || plugin.hasPostRender) + { + plugin.visible = true; + } + + this._pluginsLength = this.plugins.push(plugin); + return plugin; + + } + else + { + return null; + } + }, + + remove: function (plugin) { + + // TODO + this._pluginsLength--; + + }, + + preUpdate: function () { + + if (this._pluginsLength == 0) + { + return; + } + + for (this._p = 0; this._p < this._pluginsLength; this._p++) { + if (this.plugins[this._p].active && this.plugins[this._p].hasPreUpdate) { + this.plugins[this._p].preUpdate(); + } + } + + }, + + update: function () { + + if (this._pluginsLength == 0) + { + return; + } + + for (this._p = 0; this._p < this._pluginsLength; this._p++) { + if (this.plugins[this._p].active && this.plugins[this._p].hasUpdate) { + this.plugins[this._p].update(); + } + } + + }, + + postUpdate: function () { + + if (this._pluginsLength == 0) + { + return; + } + + for (this._p = 0; this._p < this._pluginsLength; this._p++) { + if (this.plugins[this._p].active && this.plugins[this._p].hasPostUpdate) { + this.plugins[this._p].postUpdate(); + } + } + + }, + + preRender: function () { + + if (this._pluginsLength == 0) + { + return; + } + + for (this._p = 0; this._p < this._pluginsLength; this._p++) { + if (this.plugins[this._p].visible && this.plugins[this._p].hasPreRender) { + this.plugins[this._p].preRender(); + } + } + + }, + + render: function () { + + if (this._pluginsLength == 0) + { + return; + } + + for (this._p = 0; this._p < this._pluginsLength; this._p++) { + if (this.plugins[this._p].visible && this.plugins[this._p].hasRender) { + this.plugins[this._p].render(); + } + } + + }, + + postRender: function () { + + if (this._pluginsLength == 0) + { + return; + } + + for (this._p = 0; this._p < this._pluginsLength; this._p++) { + + if (this.plugins[this._p].visible && this.plugins[this._p].hasPostRender) { + this.plugins[this._p].postRender(); + } + } + + }, + + destroy: function () { + + this.plugins.length = 0; + this._pluginsLength = 0; + this.game = null; + this._parent = null; + + } + +}; diff --git a/src/system/RequestAnimationFrame.js b/src/system/RequestAnimationFrame.js new file mode 100644 index 00000000..de24c67e --- /dev/null +++ b/src/system/RequestAnimationFrame.js @@ -0,0 +1,111 @@ +/** +* Phaser - RequestAnimationFrame +* +* Abstracts away the use of RAF or setTimeOut for the core game update loop. +*/ +Phaser.RequestAnimationFrame = function(game) { + + this.game = game; + + this._isSetTimeOut = false; + this.isRunning = false; + + var vendors = [ + 'ms', + 'moz', + 'webkit', + 'o' + ]; + + for (var x = 0; x < vendors.length && !window.requestAnimationFrame; x++) { + window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame']; + window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame']; + } + +}; + +Phaser.RequestAnimationFrame.prototype = { + + /** + * Starts the requestAnimatioFrame running or setTimeout if unavailable in browser + * @method start + **/ + start: function () { + + this.isRunning = true; + + if (!window.requestAnimationFrame) + { + this._isSetTimeOut = true; + this._timeOutID = window.setTimeout(Phaser.GAMES[this.game.id].raf.updateSetTimeout, 0); + } + else + { + this._isSetTimeOut = false; + window.requestAnimationFrame(Phaser.GAMES[this.game.id].raf.updateRAF); + } + + }, + + /** + * The update method for the requestAnimationFrame + * @method RAFUpdate + **/ + updateRAF: function (time) { + + this.game.update(time); + + window.requestAnimationFrame(Phaser.GAMES[this.game.id].raf.updateRAF); + + }, + + /** + * The update method for the setTimeout + * @method SetTimeoutUpdate + **/ + updateSetTimeout: function () { + + this.game.update(Date.now()); + + this._timeOutID = window.setTimeout(Phaser.GAMES[this.game.id].raf.updateSetTimeout, this.game.time.timeToCall); + + }, + + /** + * Stops the requestAnimationFrame from running + * @method stop + **/ + stop: function () { + + if (this._isSetTimeOut) + { + clearTimeout(this._timeOutID); + } + else + { + window.cancelAnimationFrame; + } + + this.isRunning = false; + + }, + + /** + * Is the browser using setTimeout? + * @method isSetTimeOut + * @return bool + **/ + isSetTimeOut: function () { + return this._isSetTimeOut; + }, + + /** + * Is the browser using requestAnimationFrame? + * @method isRAF + * @return bool + **/ + isRAF: function () { + return (this._isSetTimeOut === false); + } + +}; \ No newline at end of file diff --git a/src/time/Time.js b/src/time/Time.js index d46600fc..23d4df1e 100644 --- a/src/time/Time.js +++ b/src/time/Time.js @@ -6,7 +6,7 @@ */ /** -* This is the core internal game clock. It manages the elapsed time and calculation of delta values, +* This is the core internal game clock. It manages the elapsed time and calculation of elapsed values, * used for game object motion and tweens. * * @class Time @@ -89,11 +89,11 @@ Phaser.Time.prototype = { /** * Elapsed time since the last frame. - * @property delta + * @property elapsed * @public * @type {Number} */ - delta: 0, + elapsed: 0, /** * Frames per second. @@ -151,6 +151,22 @@ Phaser.Time.prototype = { */ pauseDuration: 0, + /** + * The value that setTimeout needs to work out when to next update + * @property timeToCall + * @public + * @type {Number} + */ + timeToCall: 0, + + /** + * Internal value used by timeToCall as part of the setTimeout loop + * @property lastTime + * @public + * @type {Number} + */ + lastTime: 0, + /** * The number of seconds that have elapsed since the game was started. * @method totalElapsedSeconds @@ -161,18 +177,20 @@ Phaser.Time.prototype = { }, /** - * Update clock and calculate the fps. - * This is called automatically by Game._raf + * Updates the game clock and calculate the fps. + * This is called automatically by Phaser.Game * @method update - * @param {Number} raf The current timestamp, either performance.now or Date.now + * @param {Number} time The current timestamp, either performance.now or Date.now depending on the browser */ - update: function (raf) { + update: function (time) { - this.now = raf; - this.delta = this.now - this.time; + this.now = time; + this.timeToCall = Math.max(0, 16 - (time - this.lastTime)); - this.msMin = Math.min(this.msMin, this.delta); - this.msMax = Math.max(this.msMax, this.delta); + this.elapsed = this.now - this.time; + + this.msMin = Math.min(this.msMin, this.elapsed); + this.msMax = Math.max(this.msMax, this.elapsed); this.frames++; @@ -186,7 +204,8 @@ Phaser.Time.prototype = { } this.time = this.now; - this.physicsElapsed = 1.0 * (this.delta / 1000); + this.lastTime = time + this.timeToCall; + this.physicsElapsed = 1.0 * (this.elapsed / 1000); // Paused? if (this.game.paused) { @@ -211,8 +230,8 @@ Phaser.Time.prototype = { */ gameResumed: function () { - // Level out the delta timer to avoid spikes - this.delta = 0; + // Level out the elapsed timer to avoid spikes + this.elapsed = 0; this.physicsElapsed = 0; this.time = Date.now(); this.pauseDuration = this.pausedTime;