UNPKG

kontra

Version:

Kontra HTML5 game development library

171 lines (154 loc) 4.34 kB
import { noop } from './utils.js' import { emit } from './events.js' import { getContext, getCanvas } from './core.js' /** * Clear the canvas. */ function clear() { let canvas = getCanvas(); getContext().clearRect(0, 0, canvas.width, canvas.height); } /** * The game loop updates and renders the game every frame. The game loop is stopped by default and will not start until the loops `start()` function is called. * * The game loop uses a time-based animation with a fixed `dt` to [avoid frame rate issues](http://blog.sklambert.com/using-time-based-animation-implement/). Each update call is guaranteed to equal 1/60 of a second. * * This means that you can avoid having to do time based calculations in your update functions and instead do fixed updates. * * ```js * import { Sprite, GameLoop } from 'kontra'; * * let sprite = Sprite({ * x: 100, * y: 200, * width: 20, * height: 40, * color: 'red' * }); * * let loop = GameLoop({ * update: function(dt) { * // no need to determine how many pixels you want to * // move every second and multiple by dt * // sprite.x += 180 * dt; * * // instead just update by how many pixels you want * // to move every frame and the loop will ensure 60FPS * sprite.x += 3; * }, * render: function() { * sprite.render(); * } * }); * * loop.start(); * ``` * @sectionName GameLoop * * @param {Object} properties - Properties of the game loop. * @param {Function} properties.update - Function called every frame to update the game. Is passed the fixed `dt` as a parameter. * @param {Function} properties.render - Function called every frame to render the game. * @param {Number} [properties.fps=60] - Desired frame rate. * @param {Boolean} [properties.clearCanvas=true] - Clear the canvas every frame before the `render()` function is called. */ export default function GameLoop({fps = 60, clearCanvas = true, update, render} = {}) { // check for required functions // @if DEBUG if ( !(update && render) ) { throw Error('You must provide update() and render() functions'); } // @endif // animation variables let accumulator = 0; let delta = 1E3 / fps; // delta between performance.now timings (in ms) let step = 1 / fps; let clearFn = clearCanvas ? clear : noop let last, rAF, now, dt, loop; /** * Called every frame of the game loop. */ function frame() { rAF = requestAnimationFrame(frame); now = performance.now(); dt = now - last; last = now; // prevent updating the game with a very large dt if the game were to lose focus // and then regain focus later if (dt > 1E3) { return; } emit('tick'); accumulator += dt; while (accumulator >= delta) { loop.update(step); accumulator -= delta; } clearFn(); loop.render(); } // game loop object loop = { /** * Called every frame to update the game. Put all of your games update logic here. * @memberof GameLoop * @function update * * @param {Number} dt - The fixed dt time of 1/60 of a frame. */ update, /** * Called every frame to render the game. Put all of your games render logic here. * @memberof GameLoop * @function render */ render, /** * If the game loop is currently stopped. * * ```js * import { GameLoop } from 'kontra'; * * let loop = GameLoop({ * // ... * }); * console.log(loop.isStopped); //=> true * * loop.start(); * console.log(loop.isStopped); //=> false * * loop.stop(); * console.log(loop.isStopped); //=> true * ``` * @memberof GameLoop * @property {Boolean} isStopped */ isStopped: true, /** * Start the game loop. * @memberof GameLoop * @function start */ start() { last = performance.now(); this.isStopped = false; requestAnimationFrame(frame); }, /** * Stop the game loop. * @memberof GameLoop * @function stop */ stop() { this.isStopped = true; cancelAnimationFrame(rAF); }, // expose properties for testing // @if DEBUG _frame: frame, set _last(value) { last = value; } // @endif }; return loop; };