phaser
Version:
A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.
300 lines (260 loc) • 10.6 kB
JavaScript
/**
* The `Matter.Runner` module is an optional utility which provides a game loop,
* that handles continuously updating a `Matter.Engine` for you within a browser.
* It is intended for development and debugging purposes, but may also be suitable for simple games.
* If you are using your own game loop instead, then you do not need the `Matter.Runner` module.
* Instead just call `Engine.update(engine, delta)` in your own loop.
*
* See the included usage [examples](https://github.com/liabru/matter-js/tree/master/examples).
*
* @class Runner
*/
var Runner = {};
module.exports = Runner;
var Events = require('./Events');
var Engine = require('./Engine');
var Common = require('./Common');
(function() {
Runner._maxFrameDelta = 1000 / 15;
Runner._frameDeltaFallback = 1000 / 60;
Runner._timeBufferMargin = 1.5;
Runner._elapsedNextEstimate = 1;
Runner._smoothingLowerBound = 0.1;
Runner._smoothingUpperBound = 0.9;
/**
* Creates a new Runner. The options parameter is an object that specifies any properties you wish to override the defaults.
* @method create
* @param {} options
*/
Runner.create = function(options) {
var defaults = {
delta: 1000 / 60,
frameDelta: null,
frameDeltaSmoothing: true,
frameDeltaSnapping: true,
frameDeltaHistory: [],
frameDeltaHistorySize: 100,
frameRequestId: null,
timeBuffer: 0,
timeLastTick: null,
maxUpdates: null,
maxFrameTime: 1000 / 30,
lastUpdatesDeferred: 0,
enabled: true
};
var runner = Common.extend(defaults, options);
// for temporary back compatibility only
runner.fps = 0;
return runner;
};
/**
* Continuously ticks a `Matter.Engine` by calling `Runner.tick` on the `requestAnimationFrame` event.
* @method run
* @param {engine} engine
*/
Runner.run = function(runner, engine) {
// create runner if engine is first argument
runner.timeBuffer = Runner._frameDeltaFallback;
(function onFrame(time){
runner.frameRequestId = Runner._onNextFrame(runner, onFrame);
if (time && runner.enabled) {
Runner.tick(runner, engine, time);
}
})();
return runner;
};
/**
* A game loop utility that updates the engine and renderer by one step (a 'tick').
* Features delta smoothing, time correction and fixed or dynamic timing.
* Consider just `Engine.update(engine, delta)` if you're using your own loop.
* @method tick
* @param {runner} runner
* @param {engine} engine
* @param {number} time
*/
Runner.tick = function(runner, engine, time) {
var tickStartTime = Common.now(),
engineDelta = runner.delta,
updateCount = 0;
// fixed timestep
var frameDelta = time - runner.timeLastTick;
if (!frameDelta || !runner.timeLastTick || frameDelta > Math.max(Runner._maxFrameDelta, runner.maxFrameTime)) {
// dynamic timestep based on wall clock between calls
frameDelta = runner.frameDelta || Runner._frameDeltaFallback;
}
if (runner.frameDeltaSmoothing) {
// optimistically filter delta over a few frames, to improve stability
runner.frameDeltaHistory.push(frameDelta);
runner.frameDeltaHistory = runner.frameDeltaHistory.slice(-runner.frameDeltaHistorySize);
var deltaHistorySorted = runner.frameDeltaHistory.slice(0).sort();
// limit delta
var deltaHistoryWindow = runner.frameDeltaHistory.slice(
deltaHistorySorted.length * Runner._smoothingLowerBound,
deltaHistorySorted.length * Runner._smoothingUpperBound
);
// update engine timing object
var frameDeltaSmoothed = _mean(deltaHistoryWindow);
frameDelta = frameDeltaSmoothed || frameDelta;
}
if (runner.frameDeltaSnapping) {
frameDelta = 1000 / Math.round(1000 / frameDelta);
}
runner.frameDelta = frameDelta;
runner.timeLastTick = time;
runner.timeBuffer += runner.frameDelta;
runner.timeBuffer = Common.clamp(
runner.timeBuffer, 0, runner.frameDelta + engineDelta * Runner._timeBufferMargin
);
runner.lastUpdatesDeferred = 0;
var maxUpdates = runner.maxUpdates || Math.ceil(runner.maxFrameTime / engineDelta);
// create an event object
var event = {
timestamp: engine.timing.timestamp
};
Events.trigger(runner, 'beforeTick', event);
// fps counter
Events.trigger(runner, 'tick', event);
var updateStartTime = Common.now();
while (engineDelta > 0 && runner.timeBuffer >= engineDelta * Runner._timeBufferMargin) {
// update
Events.trigger(runner, 'beforeUpdate', event);
Engine.update(engine, engineDelta);
Events.trigger(runner, 'afterUpdate', event);
runner.timeBuffer -= engineDelta;
updateCount += 1;
var elapsedTimeTotal = Common.now() - tickStartTime,
elapsedTimeUpdates = Common.now() - updateStartTime,
elapsedNextEstimate = elapsedTimeTotal + Runner._elapsedNextEstimate * elapsedTimeUpdates / updateCount;
if (updateCount >= maxUpdates || elapsedNextEstimate > runner.maxFrameTime) {
runner.lastUpdatesDeferred = Math.round(Math.max(0, (runner.timeBuffer / engineDelta) - Runner._timeBufferMargin));
break;
}
}
engine.timing.lastUpdatesPerFrame = updateCount;
Events.trigger(runner, 'afterTick', event);
if (runner.frameDeltaHistory.length >= 100) {
if (runner.lastUpdatesDeferred && Math.round(runner.frameDelta / engineDelta) > maxUpdates) {
Common.warnOnce('Matter.Runner: runner reached runner.maxUpdates, see docs.');
} else if (runner.lastUpdatesDeferred) {
Common.warnOnce('Matter.Runner: runner reached runner.maxFrameTime, see docs.');
}
if (typeof runner.isFixed !== 'undefined') {
Common.warnOnce('Matter.Runner: runner.isFixed is now redundant, see docs.');
}
if (runner.deltaMin || runner.deltaMax) {
Common.warnOnce('Matter.Runner: runner.deltaMin and runner.deltaMax were removed, see docs.');
}
if (runner.fps !== 0) {
Common.warnOnce('Matter.Runner: runner.fps was replaced by runner.delta, see docs.');
}
}
};
/**
* Ends execution of `Runner.run` on the given `runner`, by canceling the animation frame request event loop.
* If you wish to only temporarily pause the engine, see `engine.enabled` instead.
* @method stop
* @param {runner} runner
*/
Runner.stop = function(runner) {
Runner._cancelNextFrame(runner);
};
/**
* Alias for `Runner.run`.
* @method start
* @param {runner} runner
* @param {engine} engine
*/
Runner._onNextFrame = function(runner, callback) {
if (typeof window !== 'undefined' && window.requestAnimationFrame) {
runner.frameRequestId = window.requestAnimationFrame(callback);
} else {
throw new Error('Matter.Runner: missing required global window.requestAnimationFrame.');
}
return runner.frameRequestId;
};
Runner._cancelNextFrame = function(runner) {
if (typeof window !== 'undefined' && window.cancelAnimationFrame) {
window.cancelAnimationFrame(runner.frameRequestId);
} else {
throw new Error('Matter.Runner: missing required global window.cancelAnimationFrame.');
}
};
var _mean = function(values) {
var result = 0,
valuesLength = values.length;
for (var i = 0; i < valuesLength; i += 1) {
result += values[i];
}
return (result / valuesLength) || 0;
};
Runner._mean = _mean;
/*
*
* Events Documentation
*
*/
/**
* Fired at the start of a tick, before any updates to the engine or timing
*
* @event beforeTick
* @param {} event An event object
* @param {number} event.timestamp The engine.timing.timestamp of the event
* @param {} event.source The source object of the event
* @param {} event.name The name of the event
*/
/**
* Fired after engine timing updated, but just before update
*
* @event tick
* @param {} event An event object
* @param {number} event.timestamp The engine.timing.timestamp of the event
* @param {} event.source The source object of the event
* @param {} event.name The name of the event
*/
/**
* Fired at the end of a tick, after engine update and after rendering
*
* @event afterTick
* @param {} event An event object
* @param {number} event.timestamp The engine.timing.timestamp of the event
* @param {} event.source The source object of the event
* @param {} event.name The name of the event
*/
/**
* Fired before update
*
* @event beforeUpdate
* @param {} event An event object
* @param {number} event.timestamp The engine.timing.timestamp of the event
* @param {} event.source The source object of the event
* @param {} event.name The name of the event
*/
/**
* Fired after update
*
* @event afterUpdate
* @param {} event An event object
* @param {number} event.timestamp The engine.timing.timestamp of the event
* @param {} event.source The source object of the event
* @param {} event.name The name of the event
*/
/*
*
* Properties Documentation
*
*/
/**
* A flag that specifies whether the runner is running or not.
*
* @property enabled
* @type boolean
* @default true
*/
/**
* A `Number` that specifies the time step between updates in milliseconds.
*
* @property delta
* @type number
* @default 1000 / 60
*/
})();