UNPKG

phaser

Version:

A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.

627 lines (532 loc) 19.7 kB
/** * The `Matter.Engine` module contains methods for creating and manipulating engines. * An engine is a controller that manages updating the simulation of the world. * See `Matter.Runner` for an optional game loop utility. * * See the included usage [examples](https://github.com/liabru/matter-js/tree/master/examples). * * @class Engine */ var Engine = {}; module.exports = Engine; var Sleeping = require('./Sleeping'); var Resolver = require('../collision/Resolver'); var Detector = require('../collision/Detector'); var Pairs = require('../collision/Pairs'); var Events = require('./Events'); var Composite = require('../body/Composite'); var Constraint = require('../constraint/Constraint'); var Common = require('./Common'); var Body = require('../body/Body'); (function() { Engine._deltaMax = 1000 / 60; /** * Creates a new engine. The options parameter is an object that specifies any properties you wish to override the defaults. * All properties have default values, and many are pre-calculated automatically based on other properties. * See the properties section below for detailed information on what you can pass via the `options` object. * @method create * @param {object} [options] * @return {engine} engine */ Engine.create = function(options) { options = options || {}; var defaults = { positionIterations: 6, velocityIterations: 4, constraintIterations: 2, enableSleeping: false, events: [], plugin: {}, gravity: { x: 0, y: 1, scale: 0.001 }, timing: { timestamp: 0, timeScale: 1, lastDelta: 0, lastElapsed: 0, lastUpdatesPerFrame: 0 } }; var engine = Common.extend(defaults, options); engine.world = options.world || Composite.create({ label: 'World' }); engine.pairs = options.pairs || Pairs.create(); engine.detector = options.detector || Detector.create(); engine.detector.pairs = engine.pairs; // for temporary back compatibility only engine.grid = { buckets: [] }; engine.world.gravity = engine.gravity; engine.broadphase = engine.grid; engine.metrics = {}; return engine; }; /** * Moves the simulation forward in time by `delta` ms. * Triggers `beforeUpdate` and `afterUpdate` events. * Triggers `collisionStart`, `collisionActive` and `collisionEnd` events. * @method update * @param {engine} engine * @param {number} [delta=16.666] */ Engine.update = function(engine, delta) { var startTime = Common.now(); var world = engine.world, detector = engine.detector, pairs = engine.pairs, timing = engine.timing, timestamp = timing.timestamp, i; if (delta > Engine._deltaMax) { Common.warnOnce( 'Matter.Engine.update: delta argument is recommended to be less than or equal to', Engine._deltaMax.toFixed(3), 'ms.' ); } delta = typeof delta !== 'undefined' ? delta : Common._baseDelta; delta *= timing.timeScale; // increment timestamp timing.timestamp += delta; timing.lastDelta = delta; // create an event object var event = { timestamp: timing.timestamp, delta: delta }; Events.trigger(engine, 'beforeUpdate', event); // get all bodies and all constraints in the world var allBodies = Composite.allBodies(world), allConstraints = Composite.allConstraints(world), allComposites = Composite.allComposites(world); // if the world has changed if (world.isModified) { // update the detector bodies Detector.setBodies(detector, allBodies); // reset all composite modified flags Composite.setModified(world, false, false, true); } // update sleeping if enabled if (engine.enableSleeping) Sleeping.update(allBodies, delta); // apply gravity to all bodies Engine._bodiesApplyGravity(allBodies, engine.gravity); Engine.wrap(allBodies, allComposites); Engine.attractors(allBodies); // update all body position and rotation by integration if (delta > 0) { Engine._bodiesUpdate(allBodies, delta); } Events.trigger(engine, 'beforeSolve', event); // update all constraints (first pass) Constraint.preSolveAll(allBodies); for (i = 0; i < engine.constraintIterations; i++) { Constraint.solveAll(allConstraints, delta); } Constraint.postSolveAll(allBodies); // find all collisions var collisions = Detector.collisions(detector); // update collision pairs Pairs.update(pairs, collisions, timestamp); // wake up bodies involved in collisions if (engine.enableSleeping) Sleeping.afterCollisions(pairs.list); // trigger collision events if (pairs.collisionStart.length > 0) { Events.trigger(engine, 'collisionStart', { pairs: pairs.collisionStart, timestamp: timing.timestamp, delta: delta }); } // iteratively resolve position between collisions var positionDamping = Common.clamp(20 / engine.positionIterations, 0, 1); Resolver.preSolvePosition(pairs.list); for (i = 0; i < engine.positionIterations; i++) { Resolver.solvePosition(pairs.list, delta, positionDamping); } Resolver.postSolvePosition(allBodies); // update all constraints (second pass) Constraint.preSolveAll(allBodies); for (i = 0; i < engine.constraintIterations; i++) { Constraint.solveAll(allConstraints, delta); } Constraint.postSolveAll(allBodies); // iteratively resolve velocity between collisions Resolver.preSolveVelocity(pairs.list); for (i = 0; i < engine.velocityIterations; i++) { Resolver.solveVelocity(pairs.list, delta); } // update body speed and velocity properties Engine._bodiesUpdateVelocities(allBodies); // trigger collision events if (pairs.collisionActive.length > 0) { Events.trigger(engine, 'collisionActive', { pairs: pairs.collisionActive, timestamp: timing.timestamp, delta: delta }); } if (pairs.collisionEnd.length > 0) { Events.trigger(engine, 'collisionEnd', { pairs: pairs.collisionEnd, timestamp: timing.timestamp, delta: delta }); } // clear force buffers Engine._bodiesClearForces(allBodies); Events.trigger(engine, 'afterUpdate', event); // log the time elapsed computing this update engine.timing.lastElapsed = Common.now() - startTime; return engine; }; /** * Merges two engines by keeping the configuration of `engineA` but replacing the world with the one from `engineB`. * @method merge * @param {engine} engineA * @param {engine} engineB */ Engine.merge = function(engineA, engineB) { Common.extend(engineA, engineB); if (engineB.world) { engineA.world = engineB.world; Engine.clear(engineA); var bodies = Composite.allBodies(engineA.world); for (var i = 0; i < bodies.length; i++) { var body = bodies[i]; Sleeping.set(body, false); body.id = Common.nextId(); } } }; /** * Clears the engine pairs and detector. * @method clear * @param {engine} engine */ Engine.clear = function(engine) { Pairs.clear(engine.pairs); Detector.clear(engine.detector); }; /** * Zeroes the `body.force` and `body.torque` force buffers. * @method _bodiesClearForces * @private * @param {body[]} bodies */ Engine._bodiesClearForces = function(bodies) { var bodiesLength = bodies.length; for (var i = 0; i < bodiesLength; i++) { var body = bodies[i]; // reset force buffers body.force.x = 0; body.force.y = 0; body.torque = 0; } }; /** * Applys a mass dependant force to all given bodies. * @method _bodiesApplyGravity * @private * @param {body[]} bodies * @param {vector} gravity */ Engine._bodiesApplyGravity = function(bodies, gravity) { var gravityScale = typeof gravity.scale !== 'undefined' ? gravity.scale : 0.001, bodiesLength = bodies.length; if ((gravity.x === 0 && gravity.y === 0) || gravityScale === 0) { return; } for (var i = 0; i < bodiesLength; i++) { var body = bodies[i]; if (body.ignoreGravity || body.isStatic || body.isSleeping) continue; // add the resultant force of gravity body.force.y += body.mass * gravity.y * gravityScale; body.force.x += body.mass * gravity.x * gravityScale; } }; /** * Applies `Body.update` to all given `bodies`. * @method _bodiesUpdate * @private * @param {body[]} bodies * @param {number} delta The amount of time elapsed between updates */ Engine._bodiesUpdate = function(bodies, delta) { var bodiesLength = bodies.length; for (var i = 0; i < bodiesLength; i++) { var body = bodies[i]; if (body.isStatic || body.isSleeping) continue; Body.update(body, delta); } }; /** * Applies `Body.updateVelocities` to all given `bodies`. * @method _bodiesUpdateVelocities * @private * @param {body[]} bodies */ Engine._bodiesUpdateVelocities = function(bodies) { var bodiesLength = bodies.length; for (var i = 0; i < bodiesLength; i++) { Body.updateVelocities(bodies[i]); } }; /** * Applies `Body.wrap` and `Composite.wrap` and to all given `bodies`. * @method wrap * @private * @param {body[]} bodies */ Engine.wrap = function(bodies, composites) { // wrap bodies within the wrapBounds parameters for (var i = 0; i < bodies.length; i += 1) { var body = bodies[i]; if (body.wrapBounds !== null) { Body.wrap(body, body.wrapBounds); } } // wrap composites within the wrapBounds parameters for (i = 0; i < composites.length; i += 1) { var composite = composites[i]; if (composite.wrapBounds !== null) { Composite.wrap(composite, composite.wrapBounds); } } }; /** * Applies all attractors for all bodies in the `engine`. * This is called automatically. * @method attractors * @private * @param {body[]} bodies */ Engine.attractors = function(bodies) { for (var i = 0; i < bodies.length; i++) { var bodyA = bodies[i]; var attractors = bodyA.attractors; if (attractors && attractors.length > 0) { for (var j = 0; j < bodies.length; j++) { var bodyB = bodies[j]; if (i !== j) { for (var k = 0; k < attractors.length; k++) { var attractor = attractors[k]; var forceVector = attractor; if (Common.isFunction(attractor)) { forceVector = attractor(bodyA, bodyB); } if (forceVector) { Body.applyForce(bodyB, bodyB.position, forceVector); } } } } } } }; /** * A deprecated alias for `Runner.run`, use `Matter.Runner.run(engine)` instead and see `Matter.Runner` for more information. * @deprecated use Matter.Runner.run(engine) instead * @method run * @param {engine} engine */ /** * Fired just before an update * * @event beforeUpdate * @param {object} event An event object * @param {number} event.timestamp The engine.timing.timestamp of the event * @param {engine} event.source The source object of the event * @param {string} event.name The name of the event */ /** * Fired after engine update and all collision events * * @event afterUpdate * @param {object} event An event object * @param {number} event.timestamp The engine.timing.timestamp of the event * @param {engine} event.source The source object of the event * @param {string} event.name The name of the event */ /** * Fired after engine update, provides a list of all pairs that have started to collide in the current tick (if any) * * @event collisionStart * @param {object} event An event object * @param {pair[]} event.pairs List of affected pairs * @param {number} event.timestamp The engine.timing.timestamp of the event * @param {engine} event.source The source object of the event * @param {string} event.name The name of the event */ /** * Fired after engine update, provides a list of all pairs that are colliding in the current tick (if any) * * @event collisionActive * @param {object} event An event object * @param {pair[]} event.pairs List of affected pairs * @param {number} event.timestamp The engine.timing.timestamp of the event * @param {engine} event.source The source object of the event * @param {string} event.name The name of the event */ /** * Fired after engine update, provides a list of all pairs that have ended collision in the current tick (if any) * * @event collisionEnd * @param {object} event An event object * @param {pair[]} event.pairs List of affected pairs * @param {number} event.timestamp The engine.timing.timestamp of the event * @param {engine} event.source The source object of the event * @param {string} event.name The name of the event */ /* * * Properties Documentation * */ /** * An integer `Number` that specifies the number of position iterations to perform each update. * The higher the value, the higher quality the simulation will be at the expense of performance. * * @property positionIterations * @type number * @default 6 */ /** * An integer `Number` that specifies the number of velocity iterations to perform each update. * The higher the value, the higher quality the simulation will be at the expense of performance. * * @property velocityIterations * @type number * @default 4 */ /** * An integer `Number` that specifies the number of constraint iterations to perform each update. * The higher the value, the higher quality the simulation will be at the expense of performance. * The default value of `2` is usually very adequate. * * @property constraintIterations * @type number * @default 2 */ /** * A flag that specifies whether the engine should allow sleeping via the `Matter.Sleeping` module. * Sleeping can improve stability and performance, but often at the expense of accuracy. * * @property enableSleeping * @type boolean * @default false */ /** * An `Object` containing properties regarding the timing systems of the engine. * * @property timing * @type object */ /** * A `Number` that specifies the global scaling factor of time for all bodies. * A value of `0` freezes the simulation. * A value of `0.1` gives a slow-motion effect. * A value of `1.2` gives a speed-up effect. * * @property timing.timeScale * @type number * @default 1 */ /** * A `Number` that specifies the current simulation-time in milliseconds starting from `0`. * It is incremented on every `Engine.update` by the given `delta` argument. * * @property timing.timestamp * @type number * @default 0 */ /** * A `Number` that represents the total execution time elapsed during the last `Engine.update` in milliseconds. * It is updated by timing from the start of the last `Engine.update` call until it ends. * * This value will also include the total execution time of all event handlers directly or indirectly triggered by the engine update. * * @property timing.lastElapsed * @type number * @default 0 */ /** * A `Number` that represents the `delta` value used in the last engine update. * * @property timing.lastDelta * @type number * @default 0 */ /** * A `Matter.Detector` instance. * * @property detector * @type detector * @default a Matter.Detector instance */ /** * A `Matter.Grid` instance. * * @deprecated replaced by `engine.detector` * @property grid * @type grid * @default a Matter.Grid instance */ /** * Replaced by and now alias for `engine.grid`. * * @deprecated replaced by `engine.detector` * @property broadphase * @type grid * @default a Matter.Grid instance */ /** * The root `Matter.Composite` instance that will contain all bodies, constraints and other composites to be simulated by this engine. * * @property world * @type composite * @default a Matter.Composite instance */ /** * An object reserved for storing plugin-specific properties. * * @property plugin * @type {} */ /** * The gravity to apply on all bodies in `engine.world`. * * @property gravity * @type object */ /** * The gravity x component. * * @property gravity.x * @type object * @default 0 */ /** * The gravity y component. * * @property gravity.y * @type object * @default 1 */ /** * The gravity scale factor. * * @property gravity.scale * @type object * @default 0.001 */ })();