From 7bf529b8177b38fe53da5dd55794ec0f528cfec3 Mon Sep 17 00:00:00 2001 From: Pete Baron Date: Fri, 31 Oct 2014 16:59:55 +1300 Subject: [PATCH 01/10] RAF timer being used when RAF controlling loops. Time.time used for Date.now but Time.now may hold RAF hi-res value. Start of separation of game/render update. Minor adjustments to Time.update for clarity. --- src/core/Game.js | 13 +++++++++- src/system/RequestAnimationFrame.js | 5 ++-- src/time/Time.js | 39 ++++++++++++++++++----------- 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/src/core/Game.js b/src/core/Game.js index 6ba81a712..815a75dae 100644 --- a/src/core/Game.js +++ b/src/core/Game.js @@ -640,12 +640,19 @@ Phaser.Game.prototype = { * * @method Phaser.Game#update * @protected - * @param {number} time - The current time as provided by RequestAnimationFrame. + * @param {number} time - The current time as provided by Date.now (see updateRAF in RequestAnimationFrame.js) in milliseconds */ update: function (time) { this.time.update(time); + this.updateLogic(1.0 / this.time.desiredFps); + this.updateRender(this.time.elapsed); + + }, + + updateLogic: function (timeStep) { + if (!this._paused && !this.pendingStep) { if (this.stepping) @@ -687,6 +694,10 @@ Phaser.Game.prototype = { } } + }, + + updateRender: function (elapsedTime) { + if (this.renderType != Phaser.HEADLESS) { this.state.preRender(); diff --git a/src/system/RequestAnimationFrame.js b/src/system/RequestAnimationFrame.js index 4f6625b9a..6f98e5557 100644 --- a/src/system/RequestAnimationFrame.js +++ b/src/system/RequestAnimationFrame.js @@ -103,10 +103,11 @@ Phaser.RequestAnimationFrame.prototype = { /** * The update method for the requestAnimationFrame * @method Phaser.RequestAnimationFrame#updateRAF + * */ - updateRAF: function () { + updateRAF: function (rafTime) { - this.game.update(Date.now()); + this.game.update(Math.floor(rafTime)); this._timeOutID = window.requestAnimationFrame(this._onLoop); diff --git a/src/time/Time.js b/src/time/Time.js index eb658292d..66db61482 100644 --- a/src/time/Time.js +++ b/src/time/Time.js @@ -21,6 +21,7 @@ Phaser.Time = function (game) { /** * @property {number} time - Game time counter. If you need a value for in-game calculation please use Phaser.Time.now instead. + * - This always contains Date.now, but Phaser.Time.now will hold the high resolution RAF timer value (if RAF is available) * @protected */ this.time = 0; @@ -49,6 +50,11 @@ Phaser.Time = function (game) { */ this.pausedTime = 0; + /** + * @property {number} desiredFps = 60 - The desired frame-rate for this project. + */ + this.desiredFps = 60; + /** * @property {boolean} advancedTiming - If true Phaser.Time will perform advanced profiling including the fps rate, fps min/max and msMin and msMax. * @default @@ -93,9 +99,10 @@ Phaser.Time = function (game) { this.deltaCap = 0; /** - * @property {number} timeCap - If the difference in time between two frame updates exceeds this value, the frame time is reset to avoid huge elapsed counts. + * @property {number} timeCap - If the difference in time between two frame updates exceeds this value in ms, the frame time is reset to avoid huge elapsed counts. + * - assumes a desiredFps of 60 */ - this.timeCap = 1 / 60 * 1000; + this.timeCap = 1000 / 60; /** * @property {number} frames - The number of frames record in the last second. Only calculated if Time.advancedTiming is true. @@ -113,9 +120,9 @@ Phaser.Time = function (game) { this.timeToCall = 0; /** - * @property {number} lastTime - Internal value used by timeToCall as part of the setTimeout loop + * @property {number} timeExpected - The time when the next call is expected when using setTimer to control the update loop */ - this.lastTime = 0; + this.timeExpected = 0; /** * @property {Phaser.Timer} events - This is a Phaser.Timer object bound to the master clock to which you can add timed events. @@ -242,13 +249,20 @@ Phaser.Time.prototype = { */ update: function (time) { + // this.time always holds Date.now, this.now may hold the RAF high resolution time value if RAF is available (otherwise it also holds Date.now) + this.time = Date.now; + + // 'now' is currently still holding the time of the last call, move it into prevTime this.prevTime = this.now; - + // update 'now' to hold the current time this.now = time; + // elapsed time between previous call and now + this.elapsed = this.now - this.prevTime; - this.timeToCall = this.game.math.max(0, 16 - (time - this.lastTime)); - - this.elapsed = this.now - this.time; + // time to call this function again in ms in case we're using timers instead of RequestAnimationFrame to update the game + this.timeToCall = Math.floor(this.game.math.max(0, (1000.0 / this.desiredFps) - (this.timeCallExpected - time))); + // time when the next call is expected if using timers + this.timeCallExpected = time + this.timeToCall; // spike-dislike if (this.elapsed > this.timeCap) @@ -259,8 +273,8 @@ Phaser.Time.prototype = { this.elapsed = this.timeCap; } - // Calculate physics elapsed, ensure it's > 0, use 1/60 as a fallback - this.physicsElapsed = this.elapsed / 1000 || 1 / 60; + // Calculate physics elapsed, ensure it's > 0, use 1/this.desiredFps as a fallback + this.physicsElapsed = this.elapsed / 1000 || 1 / this.desiredFps; if (this.deltaCap > 0 && this.physicsElapsed > this.deltaCap) { @@ -284,9 +298,6 @@ Phaser.Time.prototype = { } } - this.time = this.now; - this.lastTime = time + this.timeToCall; - // Paused but still running? if (!this.game.paused) { @@ -346,7 +357,7 @@ Phaser.Time.prototype = { // Level out the elapsed timer to avoid spikes this.time = this.now = Date.now(); - this.pauseDuration = this.time - this._pauseStarted; + this.pauseDuration = this.now - this._pauseStarted; this.events.resume(); From 0da8c6cb1a385d5bce687f434aadef0fab6297f2 Mon Sep 17 00:00:00 2001 From: Pete Baron Date: Mon, 3 Nov 2014 12:02:43 +1300 Subject: [PATCH 02/10] Established fixed steps for logic update with catch-up for dropped frames. Render update runs every frame. Tweens moved into render update to maintain smooth motion. Added Time.slowMotion factor, integrated with logic/render updates and tweens. --- src/animation/FrameData.js | 5 +++-- src/core/Game.js | 30 +++++++++++++++++++++++++---- src/physics/Physics.js | 9 ++++++--- src/system/RequestAnimationFrame.js | 1 + src/time/Time.js | 6 ++++++ src/tween/Tween.js | 23 ++++++++++------------ 6 files changed, 52 insertions(+), 22 deletions(-) diff --git a/src/animation/FrameData.js b/src/animation/FrameData.js index e42b094fc..193b34685 100644 --- a/src/animation/FrameData.js +++ b/src/animation/FrameData.js @@ -121,9 +121,10 @@ Phaser.FrameData.prototype = { output._frames.push(this._frames[i].clone()); } - for (var i = 0; i < this._frameNames.length; i++) + for (var p in this._frameNames) { - output._frameNames.push(this._frameNames[i]); + if (this._frameNames.hasOwnProperty(p)) + output._frameNames.push(this._frameNames[p]); } return output; diff --git a/src/core/Game.js b/src/core/Game.js index 815a75dae..cd8234eaa 100644 --- a/src/core/Game.js +++ b/src/core/Game.js @@ -264,6 +264,13 @@ Phaser.Game = function (width, height, renderer, parent, state, transparent, ant */ this._codePaused = false; + /** + * @property {number} _deltaTime - accumulate elapsed time until a logic update is due + * @private + */ + this._deltaTime = 0; + + this._width = 800; this._height = 600; @@ -646,8 +653,20 @@ Phaser.Game.prototype = { this.time.update(time); - this.updateLogic(1.0 / this.time.desiredFps); - this.updateRender(this.time.elapsed); + // accumulate time until the _slowStep threshold is met or exceeded + this._deltaTime += Math.min(1000, this.time.elapsed); + + // call the game update logic multiple times if necessary to "catch up" with dropped frames + var step = 1000.0 / this.time.desiredFps; + var slowStep = this.time.slowMotion * step; + while(this._deltaTime >= slowStep) + { + this._deltaTime -= slowStep; + this.updateLogic(1.0 / this.time.desiredFps); + } + + // call the game render update exactly once every frame + this.updateRender(this._deltaTime / slowStep); }, @@ -674,10 +693,9 @@ Phaser.Game.prototype = { this.state.update(); this.stage.update(); - this.tweens.update(); this.sound.update(); this.input.update(); - this.physics.update(); + this.physics.update(timeStep); this.particles.update(); this.plugins.update(); @@ -698,6 +716,10 @@ Phaser.Game.prototype = { updateRender: function (elapsedTime) { + // update tweens once every frame along with the render logic (to keep them smooth in slowMotion scenarios) + if (!this._paused && !this.pendingStep) + this.tweens.update(elapsedTime); + if (this.renderType != Phaser.HEADLESS) { this.state.preRender(); diff --git a/src/physics/Physics.js b/src/physics/Physics.js index 08ae69158..454613e8b 100644 --- a/src/physics/Physics.js +++ b/src/physics/Physics.js @@ -229,19 +229,22 @@ Phaser.Physics.prototype = { * * @method Phaser.Physics#update * @protected + * + * @param {number} timeStep - the time step advance the physics model by + * */ - update: function () { + update: function (timeStep) { // ArcadePhysics / Ninja don't have a core to update if (this.p2) { - this.p2.update(); + this.p2.update(timeStep); } if (this.box2d) { - this.box2d.update(); + this.box2d.update(timeStep); } }, diff --git a/src/system/RequestAnimationFrame.js b/src/system/RequestAnimationFrame.js index 6f98e5557..b944314fb 100644 --- a/src/system/RequestAnimationFrame.js +++ b/src/system/RequestAnimationFrame.js @@ -107,6 +107,7 @@ Phaser.RequestAnimationFrame.prototype = { */ updateRAF: function (rafTime) { + // floor the rafTime to make it equivalent to the Date.now() provided by updateSetTimeout (just below) this.game.update(Math.floor(rafTime)); this._timeOutID = window.requestAnimationFrame(this._onLoop); diff --git a/src/time/Time.js b/src/time/Time.js index 66db61482..2a77f4693 100644 --- a/src/time/Time.js +++ b/src/time/Time.js @@ -55,6 +55,12 @@ Phaser.Time = function (game) { */ this.desiredFps = 60; + /** + * @property {number} slowMotion = 1.0 - Scaling factor to make the game move smoothly in slow motion (1.0 = normal speed, 2.0 = half speed) + * @type {Number} + */ + this.slowMotion = 1.0; + /** * @property {boolean} advancedTiming - If true Phaser.Time will perform advanced profiling including the fps rate, fps min/max and msMin and msMax. * @default diff --git a/src/tween/Tween.js b/src/tween/Tween.js index a741a0136..73ae7d50a 100644 --- a/src/tween/Tween.js +++ b/src/tween/Tween.js @@ -305,7 +305,9 @@ Phaser.Tween.prototype = { this._onStartCallbackFired = false; - this._startTime = this.game.time.now + this._delayTime; + // delays before the tween start are also affected by the time.slowMotion factor + // TODO: if the slowMotion factor changes during the delay, this will continue to use the original value until the delay expires! + this._startTime = this.game.time.now + this._delayTime * this.game.time.slowMotion; for (var property in this._valuesEnd) { @@ -389,10 +391,10 @@ Phaser.Tween.prototype = { { var property; - var elapsed = (time - this._startTime) / this._duration; - elapsed = elapsed > 1 ? 1 : elapsed; + var percent = (time - this._startTime) / this._duration; + percent = percent > 1 ? 1 : percent; - var value = this._easingFunction(elapsed); + var value = this._easingFunction(percent); var blob = {}; for (property in this._valuesEnd) @@ -688,21 +690,16 @@ Phaser.Tween.prototype = { var property; - if (time < this._startTime) - { - return true; - } - if (this._onStartCallbackFired === false) { this.onStart.dispatch(this._object); this._onStartCallbackFired = true; } - var elapsed = (time - this._startTime) / this._duration; - elapsed = elapsed > 1 ? 1 : elapsed; + var percent = (time - this._startTime) / (this._duration * this.game.time.slowMotion); + percent = percent > 1 ? 1 : percent; - var value = this._easingFunction(elapsed); + var value = this._easingFunction(percent); for (property in this._valuesEnd) { @@ -739,7 +736,7 @@ Phaser.Tween.prototype = { } } - if (elapsed == 1) + if (percent == 1) { if (this._repeat > 0) { From 497e919e439a80ee8944d986261e8c26a10e1626 Mon Sep 17 00:00:00 2001 From: Pete Baron Date: Mon, 3 Nov 2014 13:01:14 +1300 Subject: [PATCH 03/10] Adjusted particle emitter for slow-mo. Removed un-needed physics changes. --- src/core/Game.js | 4 ++-- src/particles/arcade/Emitter.js | 4 ++-- src/physics/Physics.js | 9 +++------ 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/core/Game.js b/src/core/Game.js index cd8234eaa..517d84000 100644 --- a/src/core/Game.js +++ b/src/core/Game.js @@ -654,7 +654,7 @@ Phaser.Game.prototype = { this.time.update(time); // accumulate time until the _slowStep threshold is met or exceeded - this._deltaTime += Math.min(1000, this.time.elapsed); + this._deltaTime += Math.max(Math.min(1000, this.time.elapsed), 0); // call the game update logic multiple times if necessary to "catch up" with dropped frames var step = 1000.0 / this.time.desiredFps; @@ -695,7 +695,7 @@ Phaser.Game.prototype = { this.stage.update(); this.sound.update(); this.input.update(); - this.physics.update(timeStep); + this.physics.update(); this.particles.update(); this.plugins.update(); diff --git a/src/particles/arcade/Emitter.js b/src/particles/arcade/Emitter.js index e17bbe5da..085b1e607 100644 --- a/src/particles/arcade/Emitter.js +++ b/src/particles/arcade/Emitter.js @@ -265,7 +265,7 @@ Phaser.Particles.Arcade.Emitter.prototype.update = function () { } } - this._timer = this.game.time.now + this.frequency; + this._timer = this.game.time.now + this.frequency * this.game.time.slowMotion; } var i = this.children.length; @@ -436,7 +436,7 @@ Phaser.Particles.Arcade.Emitter.prototype.start = function (explode, lifespan, f this.on = true; this._quantity += quantity; this._counter = 0; - this._timer = this.game.time.now + frequency; + this._timer = this.game.time.now + frequency * this.game.time.slowMotion; } }; diff --git a/src/physics/Physics.js b/src/physics/Physics.js index 454613e8b..08ae69158 100644 --- a/src/physics/Physics.js +++ b/src/physics/Physics.js @@ -229,22 +229,19 @@ Phaser.Physics.prototype = { * * @method Phaser.Physics#update * @protected - * - * @param {number} timeStep - the time step advance the physics model by - * */ - update: function (timeStep) { + update: function () { // ArcadePhysics / Ninja don't have a core to update if (this.p2) { - this.p2.update(timeStep); + this.p2.update(); } if (this.box2d) { - this.box2d.update(timeStep); + this.box2d.update(); } }, From c38f4802ea88f638dd7ec49d6d5a2f2268b77db8 Mon Sep 17 00:00:00 2001 From: Pete Baron Date: Mon, 3 Nov 2014 13:59:40 +1300 Subject: [PATCH 04/10] Fixed pause/resume time incompatibilities (RAF time and Date.now() can't be mixed) which has fixed the problem with tweens disappearing when paused. --- src/time/Time.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/time/Time.js b/src/time/Time.js index 2a77f4693..1dfe01a1d 100644 --- a/src/time/Time.js +++ b/src/time/Time.js @@ -279,8 +279,8 @@ Phaser.Time.prototype = { this.elapsed = this.timeCap; } - // Calculate physics elapsed, ensure it's > 0, use 1/this.desiredFps as a fallback - this.physicsElapsed = this.elapsed / 1000 || 1 / this.desiredFps; + // Set the physics elapsed time... this will always be 1 / this.desiredFps because we're using fixed time steps in game.update now + this.physicsElapsed = 1 / this.desiredFps; if (this.deltaCap > 0 && this.physicsElapsed > this.deltaCap) { @@ -339,7 +339,9 @@ Phaser.Time.prototype = { */ gamePaused: function () { - this._pauseStarted = this.now; + // use Date.now (instead of time.now) because the gameResumed function uses Date.now and the two values must be compatible + // (time.now may not be updated while the game is paused so we can't use that in both places) + this._pauseStarted = Date.now(); this.events.pause(); @@ -360,10 +362,13 @@ Phaser.Time.prototype = { */ gameResumed: function () { + // TODO: check if this is needed. It won't work if the game is using RAF timing + // (Date.now() values can't mix with RAF times) but the deltaCap should do the same thing anyway and the new fixed step timing will also help. + // Level out the elapsed timer to avoid spikes - this.time = this.now = Date.now(); +// this.time = this.now = Date.now(); - this.pauseDuration = this.now - this._pauseStarted; + this.pauseDuration = Date.now() - this._pauseStarted; this.events.resume(); From 64682857ac637868afcef66c7ebdbc317c67cf54 Mon Sep 17 00:00:00 2001 From: Pete Baron Date: Mon, 3 Nov 2014 16:25:43 +1300 Subject: [PATCH 05/10] Calculate suggestedFps. Deprecated timeCap. --- src/time/Time.js | 47 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/src/time/Time.js b/src/time/Time.js index 1dfe01a1d..ddfae9694 100644 --- a/src/time/Time.js +++ b/src/time/Time.js @@ -55,6 +55,24 @@ Phaser.Time = function (game) { */ this.desiredFps = 60; + /** + * @property {number} suggestedFps = null - The suggested frame-rate for this project. + * NOTE: not available until after a few frames have passed, it is recommended to use this after a few seconds (eg. after the menus) + */ + this.suggestedFps = null; + + /** + * @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} slowMotion = 1.0 - Scaling factor to make the game move smoothly in slow motion (1.0 = normal speed, 2.0 = half speed) * @type {Number} @@ -107,6 +125,8 @@ Phaser.Time = function (game) { /** * @property {number} timeCap - If the difference in time between two frame updates exceeds this value in ms, the frame time is reset to avoid huge elapsed counts. * - assumes a desiredFps of 60 + * + * DEPRECATED: this no longer has any effect since the change to fixed-time stepping in game.update 3rd November 2014 */ this.timeCap = 1000 / 60; @@ -270,13 +290,17 @@ Phaser.Time.prototype = { // time when the next call is expected if using timers this.timeCallExpected = time + this.timeToCall; - // spike-dislike - if (this.elapsed > this.timeCap) + // 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) { - // For some reason the time between now and the last time the game was updated was larger than our timeCap - // This can happen if the Stage.disableVisibilityChange is true and you swap tabs, which makes the raf pause. - // In this case we'll drop to some default values to stop the game timers going nuts. - this.elapsed = this.timeCap; + // this formula calculates suggestedFps in multiples of 5 fps + this.suggestedFps = Math.floor(200 / (this._elapsedAccumulator / this._frameCount)) * 5; + this._frameCount = 0; + this._elapsedAccumulator = 0; } // Set the physics elapsed time... this will always be 1 / this.desiredFps because we're using fixed time steps in game.update now @@ -339,8 +363,6 @@ Phaser.Time.prototype = { */ gamePaused: function () { - // use Date.now (instead of time.now) because the gameResumed function uses Date.now and the two values must be compatible - // (time.now may not be updated while the game is paused so we can't use that in both places) this._pauseStarted = Date.now(); this.events.pause(); @@ -362,13 +384,10 @@ Phaser.Time.prototype = { */ gameResumed: function () { - // TODO: check if this is needed. It won't work if the game is using RAF timing - // (Date.now() values can't mix with RAF times) but the deltaCap should do the same thing anyway and the new fixed step timing will also help. - - // Level out the elapsed timer to avoid spikes -// this.time = this.now = Date.now(); + // Set the parameter which stores Date.now() to make sure it's correct on resume + this.time = Date.now(); - this.pauseDuration = Date.now() - this._pauseStarted; + this.pauseDuration = this.time - this._pauseStarted; this.events.resume(); From 97604689290f87168e12ebaf0b736a61ed0aa9c7 Mon Sep 17 00:00:00 2001 From: Pete Baron Date: Mon, 3 Nov 2014 16:26:56 +1300 Subject: [PATCH 06/10] Detect "spiralling" due to CPU falling behind. Permanently skip frames when spiralling occurs. --- src/core/Game.js | 57 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/src/core/Game.js b/src/core/Game.js index 517d84000..6e4740bfc 100644 --- a/src/core/Game.js +++ b/src/core/Game.js @@ -270,6 +270,18 @@ Phaser.Game = function (width, height, renderer, parent, state, transparent, ant */ this._deltaTime = 0; + /** + * @property {number} _lastCount - remember how many 'catch-up' iterations were used on the logicUpdate last frame + * @private + */ + this._lastCount = 0; + + /** + * @property {number} _spiralling - if the 'catch-up' iterations are spiralling out of control, this counter is incremented + * @private + */ + this._spiralling = 0; + this._width = 800; this._height = 600; @@ -653,16 +665,43 @@ Phaser.Game.prototype = { this.time.update(time); - // accumulate time until the _slowStep threshold is met or exceeded - this._deltaTime += Math.max(Math.min(1000, this.time.elapsed), 0); - - // call the game update logic multiple times if necessary to "catch up" with dropped frames - var step = 1000.0 / this.time.desiredFps; - var slowStep = this.time.slowMotion * step; - while(this._deltaTime >= slowStep) + // if the logic time is spiralling upwards, skip a frame entirely + if (this._spiralling > 1) { - this._deltaTime -= slowStep; - this.updateLogic(1.0 / this.time.desiredFps); + // TODO: cause an event to warn the program that this CPU can't keep up at the current desiredFps rate + + // reset the _deltaTime accumulator which will cause all pending dropped frames to be permanently skipped + this._deltaTime = 0; + this._spiralling = 0; + } + else + { + // step size taking into account the slow motion speed + var slowStep = this.time.slowMotion * 1000.0 / this.time.desiredFps; + + // accumulate time until the slowStep threshold is met or exceeded + this._deltaTime += Math.max(Math.min(1000, this.time.elapsed), 0); + + // call the game update logic multiple times if necessary to "catch up" with dropped frames + var count = 0; + while(this._deltaTime >= slowStep) + { + this._deltaTime -= slowStep; + this.updateLogic(1.0 / this.time.desiredFps); + count++; + } + + // detect spiralling (if the catch-up loop isn't fast enough, the number of iterations will increase constantly) + if (count > this._lastCount) + { + this._spiralling++; + } + else if (count < this._lastCount) + { + // looks like it caught up successfully, reset the spiral alert counter + this._spiralling = 0; + } + this._lastCount = count; } // call the game render update exactly once every frame From 7d6126799eba268ab736383ffb8654406c33768f Mon Sep 17 00:00:00 2001 From: Pete Baron Date: Mon, 3 Nov 2014 17:00:10 +1300 Subject: [PATCH 07/10] Bug fix: this.time needs the Date.now() value, not the function! --- src/time/Time.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/time/Time.js b/src/time/Time.js index ddfae9694..565c93a68 100644 --- a/src/time/Time.js +++ b/src/time/Time.js @@ -276,7 +276,7 @@ Phaser.Time.prototype = { update: function (time) { // this.time always holds Date.now, this.now may hold the RAF high resolution time value if RAF is available (otherwise it also holds Date.now) - this.time = Date.now; + this.time = Date.now(); // 'now' is currently still holding the time of the last call, move it into prevTime this.prevTime = this.now; From 8d2cb71300c0f9c88df5118b0b45d40e1edb3053 Mon Sep 17 00:00:00 2001 From: Pete Baron Date: Mon, 3 Nov 2014 17:01:36 +1300 Subject: [PATCH 08/10] Created Phaser.Signal fpsProblemNotifier to warn the game program when the CPU starts spiralling out of control. Signal dispatch is limited to once per 10 seconds. --- src/core/Game.js | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/core/Game.js b/src/core/Game.js index 6e4740bfc..30b79eaa1 100644 --- a/src/core/Game.js +++ b/src/core/Game.js @@ -282,6 +282,19 @@ Phaser.Game = function (width, height, renderer, parent, state, transparent, ant */ this._spiralling = 0; + /** + * @property {Phaser.Signal} fpsProblemNotifier - if the game is struggling to maintain the desiredFps, this signal will be dispatched + * to suggest that the program adjust it's fps closer to the Time.suggestedFps value + * @public + */ + this.fpsProblemNotifier = new Phaser.Signal(); + + /** + * @property {number} _nextNotification - the soonest game.time.time value that the next fpsProblemNotifier can be dispatched + * @private + */ + this._nextFpsNotification = 0; + this._width = 800; this._height = 600; @@ -668,7 +681,14 @@ Phaser.Game.prototype = { // if the logic time is spiralling upwards, skip a frame entirely if (this._spiralling > 1) { - // TODO: cause an event to warn the program that this CPU can't keep up at the current desiredFps rate + // cause an event to warn the program that this CPU can't keep up with the current desiredFps rate + if (this.time.time > this._nextFpsNotification) + { + // only permit one fps notification per 10 seconds + this._nextFpsNotification = this.time.time + 1000 * 10; + // dispatch the notification signal + this.fpsProblemNotifier.dispatch(); + } // reset the _deltaTime accumulator which will cause all pending dropped frames to be permanently skipped this._deltaTime = 0; From af09290cbf5fc24c1a6c13cf32d8cbc7cfc450a9 Mon Sep 17 00:00:00 2001 From: Pete Baron Date: Wed, 12 Nov 2014 11:24:38 +1300 Subject: [PATCH 09/10] Add getObjectsAtLocation method in Arcade physics. --- src/physics/arcade/World.js | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/physics/arcade/World.js b/src/physics/arcade/World.js index 5b835fbc6..fbdd144b2 100644 --- a/src/physics/arcade/World.js +++ b/src/physics/arcade/World.js @@ -1407,24 +1407,43 @@ Phaser.Physics.Arcade.prototype = { return; } + return this.getObjectsAtLocation(pointer.x, pointer.y, group, callback, callbackContext, pointer); + + }, + + /** + * Given a Group and a location this will check to see which Group children overlap with the coordinates. + * Each child will be sent to the given callback for further processing. + * Note that the children are not checked for depth order, but simply if they overlap the coordinate or not. + * + * @method Phaser.Physics.Arcade#getObjectsAtLocation + * @param {Phaser.Pointer} pointer - The Pointer to check. + * @param {Phaser.Group} group - The Group to check. + * @param {function} [callback] - A callback function that is called if the object overlaps the coordinates. The callback will be sent two parameters: the callbackArg and the Object that overlapped the location. + * @param {object} [callbackContext] - The context in which to run the callback. + * @param {object} [callbackArg] - An argument to pass to the callback. + * @return {array} An array of the Sprites from the Group that overlapped the coordinates. + */ + getObjectsAtLocation: function (x, y, group, callback, callbackContext, callbackArg) { + this.quadTree.clear(); this.quadTree.reset(this.game.world.bounds.x, this.game.world.bounds.y, this.game.world.bounds.width, this.game.world.bounds.height, this.maxObjects, this.maxLevels); this.quadTree.populate(group); - var rect = new Phaser.Rectangle(pointer.x, pointer.y, 1, 1); + var rect = new Phaser.Rectangle(x, y, 1, 1); var output = []; this._potentials = this.quadTree.retrieve(rect); for (var i = 0, len = this._potentials.length; i < len; i++) { - if (this._potentials[i].hitTest(pointer.x, pointer.y)) + if (this._potentials[i].hitTest(x, y)) { if (callback) { - callback.call(callbackContext, pointer, this._potentials[i].sprite); + callback.call(callbackContext, callbackArg, this._potentials[i].sprite); } output.push(this._potentials[i].sprite); @@ -1432,7 +1451,7 @@ Phaser.Physics.Arcade.prototype = { } return output; - + }, /** From 4d0fcd2165e49414f9c2f45f89e968571f484610 Mon Sep 17 00:00:00 2001 From: Pete Baron Date: Wed, 12 Nov 2014 13:26:12 +1300 Subject: [PATCH 10/10] Paste over to fix unwanted HEAD entries --- src/time/Time.js | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/src/time/Time.js b/src/time/Time.js index 38d33c393..9309c1633 100644 --- a/src/time/Time.js +++ b/src/time/Time.js @@ -277,32 +277,13 @@ Phaser.Time.prototype = { // this.time always holds Date.now, this.now may hold the RAF high resolution time value if RAF is available (otherwise it also holds Date.now) this.time = Date.now(); -<<<<<<< HEAD - - // 'now' is currently still holding the time of the last call, move it into prevTime - this.prevTime = this.now; -======= // 'now' is currently still holding the time of the last call, move it into prevTime this.prevTime = this.now; ->>>>>>> phaser-github/dev // update 'now' to hold the current time this.now = time; - // elapsed time between previous call and now - this.elapsed = this.now - this.prevTime; -<<<<<<< HEAD - // time to call this function again in ms in case we're using timers instead of RequestAnimationFrame to update the game - this.timeToCall = Math.floor(this.game.math.max(0, (1000.0 / this.desiredFps) - (this.timeCallExpected - time))); - // time when the next call is expected if using timers - this.timeCallExpected = time + this.timeToCall; - - // count the number of time.update calls - this._frameCount++; - this._elapsedAccumulator += this.elapsed; - -======= // elapsed time between previous call and now this.elapsed = this.now - this.prevTime; @@ -316,7 +297,6 @@ Phaser.Time.prototype = { this._frameCount++; this._elapsedAccumulator += this.elapsed; ->>>>>>> phaser-github/dev // occasionally recalculate the suggestedFps based on the accumulated elapsed time if (this._frameCount >= this.desiredFps * 2) { @@ -469,4 +449,4 @@ Phaser.Time.prototype = { }; -Phaser.Time.prototype.constructor = Phaser.Time; +Phaser.Time.prototype.constructor = Phaser.Time; \ No newline at end of file