import { System } from '@serpentity/serpentity'; import { Body, Constraint, SAT, World } from 'matter-js'; import GrabberNode from '../nodes/grabber'; import GrabbableNode from '../nodes/grabbable'; import Config from '../config'; const internals = { kGrabRadius: 50, kNoEngine: 'No matter js physics engine found. Make sure you set the `engine` key in the config object when initializing.' }; /** * Handles grabbing between entities * * @extends {external:Serpentity.System} * @class GrabSystem * @param {object} config a configuration object to extend. */ export default class GrabSystem extends System { constructor(config = {}) { super(); /** * The node collection of grabbers * * @property {external:Serpentity.NodeCollection} grabbers * @instance * @memberof GrabSystem */ this.grabbers = null; /** * The node collection of grabbables * * @property {external:Serpentity.NodeCollection} grabbables * @instance * @memberof GrabSystem */ this.grabbables = null; /** * The matter-js engine we will use to add and remove constraints * * @property {external:MatterJs.Engine} engine * @instance * @memberof GrabSystem */ this.engine = config.engine; if (!this.engine) { throw new Error(internals.kNoEngine); } } /** * Initializes system when added. Requests grabber and grabbable nodes * * @function added * @memberof GrabSystem * @instance * @param {external:Serpentity.Engine} engine the serpentity engine to * which we are getting added */ added(engine) { this.grabbers = engine.getNodes(GrabberNode); this.grabbables = engine.getNodes(GrabbableNode); } /** * Clears system resources when removed. * * @function removed * @instance * @memberof GrabSystem */ removed() { this.grabbers = null; this.grabbables = null; } /** * Runs on every update of the loop. Triggers grab and manages cooldown * * @function update * @instance * @param {Number} currentFrameDuration the duration of the current * frame * @memberof GrabSystem */ update(currentFrameDuration) { for (const grabber of this.grabbers) { const grab = grabber.grab; if (grab.grabbing && !grab.locked) { this._grab(grabber); } const isGrabReleased = !grab.grabbing || grab.currentCooldown >= grab.cooldown; if (grab.constraint && isGrabReleased) { this._release(grabber); } if (!grab.grabbing && grab.locked && grab.currentCooldown >= grab.cooldown) { this._unlock(grabber); } if (grab.locked) { grab.currentCooldown += currentFrameDuration; } grab.grabbing = 0; } } // Executes the dash action _grab(grabber) { const grab = grabber.grab; grab.locked = true; grab.currentCooldown = 0; Body.setPosition(grabber.grabArea.area, grabber.body.body.position); for (const grabbable of this.grabbables) { if (grabbable.entity === grabber.entity) { continue; } const collision = SAT.collides(grabber.grabArea.area, grabbable.body.body); if (collision.collided) { grab.constraint = this._createConstraint(grabber.body.body, grabbable.body.body); } } } // Executes the unlock action _unlock(grabber) { grabber.grab.locked = false; } // Releases a constraint _release(grabber) { World.remove(this.engine.world, grabber.grab.constraint); grabber.grab.currentCooldown = 0; grabber.grab.constraint = null; } // Performs a grab between two entities _createConstraint(grabber, grabbable) { const constraint = Constraint.create({ // Attach the sensor to the body bodyA: grabber, bodyB: grabbable, damping: 0, length: internals.kGrabRadius / Config.meterSize, stiffness: 1 }); World.add(this.engine.world, [constraint]); return constraint; } }