diff --git a/build/config.php b/build/config.php
index 2d79165b6..808903c4f 100644
--- a/build/config.php
+++ b/build/config.php
@@ -134,6 +134,7 @@ EOL;
+
@@ -296,10 +297,10 @@ EOL;
}
echo <<
-
-
-
+
+
+
+
diff --git a/src/core/Game.js b/src/core/Game.js
index e6870e379..476f6708d 100644
--- a/src/core/Game.js
+++ b/src/core/Game.js
@@ -144,11 +144,17 @@ Phaser.Game = function (width, height, renderer, parent, state, transparent, ant
*/
this.isRunning = false;
+ /**
+ * @property {Phaser.MainLoop} mainloop - Automatically handles the core game loop via requestAnimationFrame or setTimeout.
+ * @protected
+ */
+ this.mainloop = null;
+
/**
* @property {Phaser.RequestAnimationFrame} raf - Automatically handles the core game loop via requestAnimationFrame or setTimeout
* @protected
*/
- this.raf = null;
+ // this.raf = null;
/**
* @property {Phaser.GameObjectFactory} add - Reference to the Phaser.GameObjectFactory.
@@ -583,6 +589,7 @@ Phaser.Game.prototype = {
this.isRunning = true;
+ /*
if (this.config && this.config['forceSetTimeOut'])
{
this.raf = new Phaser.RequestAnimationFrame(this, this.config['forceSetTimeOut']);
@@ -593,6 +600,7 @@ Phaser.Game.prototype = {
}
this._kickstart = true;
+ */
if (window['focus'])
{
@@ -602,7 +610,11 @@ Phaser.Game.prototype = {
}
}
- this.raf.start();
+ this.mainloop = new Phaser.MainLoop(this);
+
+ this.mainloop.start();
+
+ // this.raf.start();
},
diff --git a/src/core/MainLoop.js b/src/core/MainLoop.js
new file mode 100644
index 000000000..2a82a7839
--- /dev/null
+++ b/src/core/MainLoop.js
@@ -0,0 +1,342 @@
+Phaser.MainLoop = function (game, framerate, forceSetTimeOut) {
+
+ if (framerate === undefined) { framerate = 60; }
+ if (forceSetTimeOut === undefined) { forceSetTimeOut = false; }
+
+ this.game = game;
+
+ // Move to external file once tested
+ /*
+ this.getTime = function () { return Date.now; };
+
+ if (window.performance)
+ {
+ if (window.performance.now)
+ {
+ this.getTime = window.performance.now;
+ }
+ else if (window.performance.webkitNow)
+ {
+ this.getTime = window.performance.webkitNow;
+ }
+ }
+ */
+
+ this.timestep = 1000 / framerate;
+ this.physicsStep = 1 / framerate;
+
+ // The cumulative amount of in-app time that hasn't been simulated yet.
+ this.frameDelta = 0;
+
+ // The timestamp in milliseconds of the last time the main loop was run.
+ // Used to compute the time elapsed between frames.
+ this.lastFrameTimeMs = 0;
+
+ // An exponential moving average of the frames per second.
+ this.framerate = framerate;
+ this.fps = framerate;
+
+ // The timestamp (in milliseconds) of the last time the `fps` moving
+ // average was updated.
+ this.lastFpsUpdate = 0;
+
+ // The number of frames delivered in the current second.
+ this.framesThisSecond = 0;
+
+ // The number of times update() is called in a given frame. This is only
+ // relevant inside of animate(), but a reference is held externally so that
+ // this variable is not marked for garbage collection every time the main
+ // loop runs.
+ this.numUpdateSteps = 0;
+
+ // The minimum amount of time in milliseconds that must pass since the last
+ // frame was executed before another frame can be executed. The
+ // multiplicative inverse caps the FPS (the default of zero means there is
+ // no cap).
+ this.minFrameDelay = 0;
+
+ // Whether the main loop is running.
+ this.running = false;
+
+ // `true` if `MainLoop.start()` has been called and the most recent time it
+ // was called has not been followed by a call to `MainLoop.stop()`. This is
+ // different than `running` because there is a delay of a few milliseconds
+ // after `MainLoop.start()` is called before the application is considered
+ // "running." This delay is due to waiting for the next frame.
+ this.started = false;
+
+ // Whether the simulation has fallen too far behind real time.
+ // Specifically, `panic` will be set to `true` if too many updates occur in
+ // one frame. This is only relevant inside of animate(), but a reference is
+ // held externally so that this variable is not marked for garbage
+ // collection every time the main loop runs.
+ this.panic = false;
+
+ /**
+ * @property {boolean} _isSetTimeOut - true if the browser is using setTimeout instead of raf.
+ * @private
+ */
+ this._isSetTimeOut = false;
+
+ /**
+ * @property {number} _handleID - The callback ID used when calling cancel.
+ * @private
+ */
+ this._handleID = null;
+
+};
+
+Phaser.MainLoop.prototype = {
+
+ start: function () {
+
+ if (this.started)
+ {
+ return this;
+ }
+
+ this.started = true;
+ this.running = true;
+
+ // draw once?
+
+ this.lastFrameTimeMs = window.performance.now();
+ this.lastFpsUpdate = window.performance.now();
+ this.framesThisSecond = 0;
+
+ if (!window.requestAnimationFrame || this.forceSetTimeOut)
+ {
+ // var _this = this;
+
+ // The function that runs the main loop. The unprefixed version of
+ // `window.requestAnimationFrame()` is available in all modern browsers
+ // now, but node.js doesn't have it, so fall back to timers. The polyfill
+ // is adapted from the MIT-licensed
+ // https://github.com/underscorediscovery/realtime-multiplayer-in-html5
+
+
+ /*
+ this.raf = window.requestAnimationFrame || (function() {
+ var lastTimestamp = Date.now(),
+ now,
+ timeout;
+ return function(callback) {
+ now = Date.now();
+ // The next frame should run no sooner than the simulation allows,
+ // but as soon as possible if the current frame has already taken
+ // more time to run than is simulated in one timestep.
+ timeout = Math.max(0, _this.timestep - (now - lastTimestamp));
+ lastTimestamp = now + timeout;
+ return setTimeout(function() {
+ callback(now + timeout);
+ }, timeout);
+ };
+ })();
+ */
+ this._isSetTimeOut = true;
+
+ this._handleID = window.setTimeout(this.step.bind(this), 0);
+ }
+ else
+ {
+ this._isSetTimeOut = false;
+
+ this._handleID = window.requestAnimationFrame(this.step.bind(this));
+ }
+
+ },
+
+ step: function (timestamp) {
+
+ // console.log(timestamp);
+ // debugger;
+
+ // Throttle the frame rate (if minFrameDelay is set to a non-zero value by
+ // `MainLoop.setMaxAllowedFPS()`).
+ // if (timestamp < this.lastFrameTimeMs + this.minFrameDelay)
+ // {
+ // Run the loop again the next time the browser is ready to render.
+ // this._handleID = window.requestAnimationFrame(this.step.bind(this));
+ // return;
+ // }
+
+ // frameDelta is the cumulative amount of in-app time that hasn't been
+ // simulated yet. Add the time since the last frame. We need to track total
+ // not-yet-simulated time (as opposed to just the time elapsed since the
+ // last frame) because not all actually elapsed time is guaranteed to be
+ // simulated each frame. See the comments below for details.
+ this.frameDelta += timestamp - this.lastFrameTimeMs;
+ this.lastFrameTimeMs = timestamp;
+
+ // Run any updates that are not dependent on time in the simulation. See
+ // `MainLoop.setBegin()` for additional details on how to use this.
+
+ // BEGIN ---------------------------------------------------------------
+
+ this.begin(timestamp);
+
+ // UPDATE ---------------------------------------------------------------
+
+ // Update the estimate of the frame rate, `fps`. Every second, the number
+ // of frames that occurred in that second are included in an exponential
+ // moving average of all frames per second, with an alpha of 0.25. This
+ // means that more recent seconds affect the estimated frame rate more than
+ // older seconds.
+ if (timestamp > this.lastFpsUpdate + 1000)
+ {
+ // Compute the new exponential moving average with an alpha of 0.25.
+ // Using constants inline is okay here.
+ this.fps = 0.25 * this.framesThisSecond + 0.75 * this.fps;
+
+ this.lastFpsUpdate = timestamp;
+ this.framesThisSecond = 0;
+ }
+
+ this.framesThisSecond++;
+
+ this.numUpdateSteps = 0;
+
+ while (this.frameDelta >= this.timestep)
+ {
+ this.update(this.timestep);
+
+ this.frameDelta -= this.timestep;
+
+ if (++this.numUpdateSteps >= 240)
+ {
+ this.panic = true;
+ break;
+ }
+ }
+
+ // RENDER ---------------------------------------------------------------
+
+ this.render(this.frameDelta / this.timestep);
+
+ // END ---------------------------------------------------------------
+
+ // Run any updates that are not dependent on time in the simulation.
+ // this.end(this.fps, this.panic);
+
+ this.panic = false;
+
+ this._handleID = window.requestAnimationFrame(this.step.bind(this));
+
+ },
+
+ begin: function (timestamp) {
+
+ this.game.time.update(timestamp);
+
+ this.game.scale.preUpdate(timestamp, this.frameDelta);
+ this.game.debug.preUpdate(timestamp, this.frameDelta);
+ this.game.camera.preUpdate(timestamp, this.frameDelta);
+ this.game.physics.preUpdate(timestamp, this.frameDelta);
+ this.game.state.preUpdate(timestamp, this.frameDelta);
+ this.game.plugins.preUpdate(timestamp, this.frameDelta);
+ this.game.stage.preUpdate(timestamp, this.frameDelta);
+
+ },
+
+ update: function (timestep) {
+
+ this.game.state.update(timestep);
+ this.game.stage.update(timestep);
+ this.game.tweens.update(timestep);
+ this.game.sound.update(timestep);
+ this.game.input.update(timestep);
+ this.game.physics.update(timestep);
+ this.game.particles.update(timestep);
+ this.game.plugins.update(timestep);
+
+ this.game.stage.postUpdate(timestep);
+ this.game.plugins.postUpdate(timestep);
+
+ this.game.stage.updateTransform();
+ // this.game.time.update(timestamp);
+
+ },
+
+ render: function (dt) {
+
+ this.game.renderer.renderSession.interpolation = dt;
+
+ // this.game.stage.updateTransform();
+
+ this.game.state.preRender(dt);
+
+ if (this.game.renderType !== Phaser.HEADLESS)
+ {
+ this.game.renderer.render(this.game.stage);
+
+ this.game.plugins.render(dt);
+
+ this.game.state.render(dt);
+ }
+
+ this.game.plugins.postRender(dt);
+
+ },
+
+ stop: function () {
+
+ this.running = false;
+ this.started = false;
+
+ if (this._isSetTimeOut)
+ {
+ clearTimeout(this._handleID);
+ }
+ else
+ {
+ window.cancelAnimationFrame(this._handleID);
+ }
+
+ return this;
+
+ },
+
+ resetFrameDelta: function () {
+
+ var oldFrameDelta = this.frameDelta;
+
+ this.frameDelta = 0;
+
+ return oldFrameDelta;
+
+ }
+
+};
+
+/**
+* @name Phaser.MainLoop#maxFPS
+* @property {number} maxFPS - The maximum frame rate.
+*/
+Object.defineProperty(Phaser.MainLoop.prototype, 'maxFPS', {
+
+ get: function() {
+
+ return 1000 / this.minFrameDelay;
+
+ },
+
+ set: function (value) {
+
+ if (fps === 0)
+ {
+ this.stop();
+ }
+ else
+ {
+ this.minFrameDelay = 1000 / value;
+ }
+
+ }
+
+});
+
+Phaser.MainLoop.prototype.constructor = Phaser.MainLoop;
+
+Phaser.NOOP = function () {
+ // No-operation
+};
diff --git a/src/gameobjects/components/Core.js b/src/gameobjects/components/Core.js
index 2cb45d06a..248ff1503 100644
--- a/src/gameobjects/components/Core.js
+++ b/src/gameobjects/components/Core.js
@@ -101,6 +101,9 @@ Phaser.Component.Core.preUpdate = function () {
this.previousPosition.set(this.world.x, this.world.y);
this.previousRotation = this.rotation;
+ // TEST
+ this.prevPosition.set(this.worldPosition.x, this.worldPosition.y);
+
if (!this.exists || !this.parent.exists)
{
this.renderOrderID = -1;
diff --git a/src/pixi/display/DisplayObject.js b/src/pixi/display/DisplayObject.js
index b1f327ef2..0e198fe74 100644
--- a/src/pixi/display/DisplayObject.js
+++ b/src/pixi/display/DisplayObject.js
@@ -28,6 +28,8 @@ PIXI.DisplayObject = function() {
*/
this.position = new PIXI.Point(0, 0);
+ this.interpolate = true;
+
/**
* The scale of this DisplayObject. A scale of 1:1 represents the DisplayObject
* at its default size. A value of 0.5 would scale this DisplayObject by half, and so on.
@@ -171,6 +173,8 @@ PIXI.DisplayObject = function() {
*/
this.worldPosition = new PIXI.Point(0, 0);
+ this.prevPosition = new PIXI.Point(0, 0);
+
/**
* The global scale of this DisplayObject.
*
diff --git a/src/pixi/display/Sprite.js b/src/pixi/display/Sprite.js
index 2494e3acc..1d895fe33 100644
--- a/src/pixi/display/Sprite.js
+++ b/src/pixi/display/Sprite.js
@@ -337,6 +337,13 @@ PIXI.Sprite.prototype._renderWebGL = function(renderSession, matrix)
wt = matrix;
}
+ // if interpolation
+ if (this.interpolate)
+ {
+ wt.tx = this.prevPosition.x + (this.worldPosition.x - this.prevPosition.x) * renderSession.interpolation;
+ wt.ty = this.prevPosition.y + (this.worldPosition.y - this.prevPosition.y) * renderSession.interpolation;
+ }
+
// A quick check to see if this element has a mask or a filter.
if (this._mask || this._filters)
{
@@ -442,6 +449,13 @@ PIXI.Sprite.prototype._renderCanvas = function(renderSession, matrix)
var tx = (wt.tx * renderSession.resolution) + renderSession.shakeX;
var ty = (wt.ty * renderSession.resolution) + renderSession.shakeY;
+ // if interpolation
+ if (this.interpolate)
+ {
+ tx = this.prevPosition.x + (this.worldPosition.x - this.prevPosition.x) * renderSession.interpolation;
+ ty = this.prevPosition.y + (this.worldPosition.y - this.prevPosition.y) * renderSession.interpolation;
+ }
+
// Allow for pixel rounding
if (renderSession.roundPixels)
{
diff --git a/src/polyfills.js b/src/polyfills.js
index b22eb1d1d..7b0fb150f 100644
--- a/src/polyfills.js
+++ b/src/polyfills.js
@@ -151,3 +151,50 @@ if (!window.console)
window.console.log = window.console.assert = function(){};
window.console.warn = window.console.assert = function(){};
}
+
+/*
+ * window.performance polyfill for IE9 and other lesser browsers.
+ * Based on code by Paul Irish (MIT)
+ */
+
+(function(){
+
+ if ("performance" in window === false)
+ {
+ window.performance = {};
+ }
+
+ if ("now" in window.performance === false)
+ {
+ var nowOffset = Date.now();
+
+ if (performance.timing && performance.timing.navigationStart)
+ {
+ nowOffset = performance.timing.navigationStart;
+ }
+
+ window.performance.now = function now() {
+ return Date.now() - nowOffset;
+ }
+
+ }
+
+})();
+
+/*
+ * window.requestAnimationFrame polyfill.
+ */
+
+(function(){
+
+ if (!window.requestAnimationFrame)
+ {
+ ['ms', 'moz', 'webkit', 'o'].forEach(function(prefix) {
+
+ window.requestAnimationFrame = window[prefix + 'RequestAnimationFrame'];
+ window.cancelAnimationFrame = window[prefix + 'CancelAnimationFrame'];
+
+ });
+ }
+
+})();
diff --git a/src/time/Time.js b/src/time/Time.js
index 1df9be424..c8e296c6b 100644
--- a/src/time/Time.js
+++ b/src/time/Time.js
@@ -403,6 +403,7 @@ Phaser.Time.prototype = {
// elapsed time between previous call and now - this could be a high resolution value
this.elapsed = this.now - this.prevTime;
+ /*
if (this.game.raf._isSetTimeOut)
{
// console.log('Time isSet', this._desiredFps, 'te', this.timeExpected, 'time', time);
@@ -415,6 +416,7 @@ Phaser.Time.prototype = {
// console.log('Time expect', this.timeExpected);
}
+ */
if (this.advancedTiming)
{