mirror of
https://github.com/photonstorm/phaser
synced 2024-11-27 23:20:59 +00:00
Experimenting with new MainLoop + position interpolation.
This commit is contained in:
parent
265501b4af
commit
e49d45e278
8 changed files with 431 additions and 6 deletions
|
@ -134,6 +134,7 @@ EOL;
|
|||
<script src="$path/src/core/FlexGrid.js"></script>
|
||||
<script src="$path/src/core/FlexLayer.js"></script>
|
||||
<script src="$path/src/core/ScaleManager.js"></script>
|
||||
<script src="$path/src/core/MainLoop.js"></script>
|
||||
<script src="$path/src/core/Game.js"></script>
|
||||
|
||||
<script src="$path/src/input/Input.js"></script>
|
||||
|
@ -296,10 +297,10 @@ EOL;
|
|||
}
|
||||
|
||||
echo <<<EOL
|
||||
<script src="$path/src/system/Device.js"></script>
|
||||
<script src="$path/src/system/DOM.js"></script>
|
||||
<script src="$path/src/system/Canvas.js"></script>
|
||||
<script src="$path/src/system/RequestAnimationFrame.js"></script>
|
||||
<script src="$path/src/utils/Device.js"></script>
|
||||
<script src="$path/src/utils/DOM.js"></script>
|
||||
<script src="$path/src/utils/Canvas.js"></script>
|
||||
<script src="$path/src/utils/RequestAnimationFrame.js"></script>
|
||||
|
||||
<script src="$path/src/math/Math.js"></script>
|
||||
<script src="$path/src/math/RandomDataGenerator.js"></script>
|
||||
|
|
|
@ -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();
|
||||
|
||||
},
|
||||
|
||||
|
|
342
src/core/MainLoop.js
Normal file
342
src/core/MainLoop.js
Normal file
|
@ -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
|
||||
};
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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'];
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
})();
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue