UNPKG

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
/** * 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 */ })();