- Matter-js powered physics system
- Scripts to setup hooks, linting, docs, and bundling
- Keyboard Support
+- Dash & grab mechanics
[Unreleased]: https://github.com/rbdr/sumo/compare/master...develop
--- /dev/null
+import { Component } from '@serpentity/serpentity';
+
+/**
+ * Component that stores a dash skill
+ *
+ * @extends {external:Serpentity.Component}
+ * @class GrabComponent
+ * @param {object} config a configuration object to extend.
+ */
+export default class GrabComponent extends Component {
+ constructor(config) {
+
+ super(config);
+
+ /**
+ * The properthy that holds the grab state
+ *
+ * @property {boolean} grabbing
+ * @instance
+ * @memberof GrabComponent
+ */
+ this.dashing = this.grabbing || false;
+
+ /**
+ * The constraint used for the grab
+ *
+ * @property {external:MatterJs.Constraint} constraint
+ * @instance
+ * @memberof GrabComponent
+ */
+ this.constraint = this.constraint || null;
+
+ /**
+ * Whether the grab is locked from occuring
+ *
+ * @property {boolean} locked
+ * @instance
+ * @memberof GrabComponent
+ */
+ this.locked = this.locked || false;
+
+ /**
+ * Cooldown before lock is removed
+ *
+ * @property {number} cooldown
+ * @instance
+ * @memberof GrabComponent
+ */
+ this.cooldown = this.cooldown || 3000;
+
+ /**
+ * Current cooldown
+ *
+ * @property {number} currentCooldown
+ * @instance
+ * @memberof GrabComponent
+ */
+ this.currentCooldown = 0;
+ }
+};
--- /dev/null
+import { Component } from '@serpentity/serpentity';
+
+/**
+ * Component that stores an area used to grab things
+ *
+ * @extends {external:Serpentity.Component}
+ * @class GrabAreaComponent
+ * @param {object} config a configuration object to extend.
+ */
+export default class GrabAreaComponent extends Component {
+ constructor(config) {
+
+ super(config);
+
+ /**
+ * The properthy that holds the angle. Assumes the area behaves as a sensor
+ *
+ * @property {external:MatterJs.Body} area
+ * @instance
+ * @memberof GrabAreaComponent
+ */
+ this.area = this.area || null;
+
+ /**
+ * The constraint that ties this area to its parent
+ *
+ * @property {external:MatterJs.Constraint} constraint
+ * @instance
+ * @memberof GrabAreaComponent
+ */
+ this.constraint = this.constraint || null;
+ }
+};
+
--- /dev/null
+import { Component } from '@serpentity/serpentity';
+
+/**
+ * Component that stores grab status
+ *
+ * @extends {external:Serpentity.Component}
+ * @class GrabbableComponent
+ * @param {object} config a configuration object to extend.
+ */
+export default class GrabbableComponent extends Component {};
+
body.addChild(this.createBlush({ radius }));
+ body.addChild(this.createEffortMark({ radius }));
+ body.addChild(this.createShadow({ radius }));
+
// The group
body.addChild(smile);
body.addChild(frown);
blush.addChild(rightBlush);
return blush;
+ },
+
+ /**
+ * Creates an effort mark
+ *
+ * @function createEffortMark
+ * @memberof PixiFactory
+ * @return {external:CreateJs.Container} the created container
+ */
+ createEffortMark(config) {
+
+ const radius = config.radius;
+
+ const effortMark = new Graphics();
+
+ const centerX = -3 * radius / 4;
+ const centerY = -3 * radius / 4;
+
+ effortMark.name = 'effort';
+ effortMark.visible = false;
+
+ const color = 0xff00ff;
+ const lineWidth = 2;
+
+ const topRightArch = new Graphics();
+ topRightArch.lineStyle(lineWidth, color, 1)
+ .arc(
+ centerX + 2 * radius / 7, centerY - 2 * radius / 7,
+ radius / 5,
+ Math.PI / 2,
+ Math.PI
+ );
+
+ const topLeftArch = new Graphics();
+ topLeftArch.lineStyle(lineWidth, color, 1)
+ .arc(
+ centerX - 2 * radius / 7, centerY - radius / 4,
+ radius / 5,
+ 0,
+ Math.PI / 2
+ );
+
+ const bottomRightArch = new Graphics();
+ bottomRightArch.lineStyle(lineWidth, color, 1)
+ .arc(
+ centerX + 2 * radius / 7, centerY + radius / 4,
+ radius / 5,
+ Math.PI,
+ 3 * Math.PI / 2
+ );
+
+ const bottomLeftArch = new Graphics();
+ bottomLeftArch.lineStyle(lineWidth, color, 1)
+ .arc(
+ centerX - 2 * radius / 7, centerY + 2 * radius / 7,
+ radius / 5,
+ 3 * Math.PI / 2,
+ 0
+ );
+
+ effortMark.addChild(topRightArch);
+ effortMark.addChild(topLeftArch);
+ effortMark.addChild(bottomRightArch);
+ effortMark.addChild(bottomLeftArch);
+
+ return effortMark;
+ },
+
+ /**
+ * Creates a shadow for the eye
+ *
+ * @function createShadow
+ * @memberof PixiFactory
+ * @return {external:CreateJs.Container} the created container
+ */
+ createShadow(config) {
+
+ const radius = config.radius;
+
+ const shadow = new Graphics();
+
+ const centerX = radius / 2;
+ const centerY = -3 * radius / 4;
+
+ shadow.name = 'shadow';
+ shadow.visible = false;
+
+ const color = 0x9900ff;
+ const lineWidth = 2;
+
+ shadow.lineStyle(lineWidth, color, 1)
+ .moveTo(centerX - radius / 4, centerY - radius / 3)
+ .lineTo(centerX - radius / 4, centerY + radius / 5)
+ .moveTo(centerX - radius / 8, centerY - radius / 4)
+ .lineTo(centerX - radius / 8, centerY + radius / 5)
+ .moveTo(centerX, centerY - radius / 5)
+ .lineTo(centerX, centerY + radius / 5)
+ .moveTo(centerX + radius / 8, centerY - radius / 6)
+ .lineTo(centerX + radius / 8, centerY + radius / 6)
+ .moveTo(centerX + radius / 4, centerY - radius / 5)
+ .lineTo(centerX + radius / 4, centerY + radius / 5);
+
+ return shadow;
}
};
import DashComponent from '../components/dash';
import ElasticComponent from '../components/elastic';
import ForceComponent from '../components/force';
+import GrabAreaComponent from '../components/grab_area';
+import GrabbableComponent from '../components/grabbable';
+import GrabComponent from '../components/grab';
import MaxVelocityComponent from '../components/max_velocity';
import PositionComponent from '@serpentity/components.position';
import PixiContainerComponent from '../components/pixi_container';
});
entity.addComponent(new BodyComponent({ body }));
+ // GRAB
+
+ const areaSizeFactor = 2; // Multiplier vs the radius
+ const area = Bodies.circle(position.x / Config.meterSize, position.y / Config.meterSize, (radius * areaSizeFactor) / Config.meterSize, {
+ label: 'Sumo Grab Area',
+ isSensor: true
+ });
+
+ entity.addComponent(new GrabAreaComponent({ area }));
+ entity.addComponent(new GrabComponent({ body }));
+ entity.addComponent(new GrabbableComponent({ body }));
+
if (engine) {
engine.addEntity(entity);
}
component: DashComponent,
property: 'dashing'
}
+ },
+ {
+ source: {
+ type: 'keyboard',
+ index: 88 // X
+ },
+ target: {
+ component: GrabComponent,
+ property: 'grabbing'
+ }
}
]
}));
--- /dev/null
+import { Node } from '@serpentity/serpentity';
+
+import GrabComponent from '../components/grab';
+import PixiContainerComponent from '../components/pixi_container';
+
+/**
+ * Node identifying an entity that can grab and is drawn
+ *
+ * @extends {external:Serpentity.Node}
+ * @class DrawnGrabberNode
+ */
+export default class DrawnGrabberNode extends Node {
+
+};
+
+/**
+ * Holds the types that are used to identify a drawn grabber entity
+ *
+ * @property {object} types
+ * @name types
+ * @memberof DrawnGrabberNode
+ */
+DrawnGrabberNode.types = {
+ container: PixiContainerComponent,
+ grab: GrabComponent
+};
+
--- /dev/null
+import { Node } from '@serpentity/serpentity';
+
+import GrabAreaComponent from '../components/grab_area';
+
+/**
+ * Node identifying an entity that have a physical grab area
+ *
+ * @extends {external:Serpentity.Node}
+ * @class GrabAreaNode
+ */
+export default class GrabAreaNode extends Node {
+
+};
+
+/**
+ * Holds the types that are used to identify an entity with a grab
+ * area
+ *
+ * @property {object} types
+ * @name types
+ * @memberof GrabAreaNode
+ */
+GrabAreaNode.types = {
+ grabArea: GrabAreaComponent
+};
--- /dev/null
+import { Node } from '@serpentity/serpentity';
+
+import BodyComponent from '../components/body';
+import GrabbableComponent from '../components/grabbable';
+
+/**
+ * Node identifying an entity that can grab another
+ *
+ * @extends {external:Serpentity.Node}
+ * @class GrabbableNode
+ */
+export default class GrabbableNode extends Node {
+
+};
+
+/**
+ * Holds the types that are used to identify a grabbable entity
+ *
+ * @property {object} types
+ * @name types
+ * @memberof GrabbableNode
+ */
+GrabbableNode.types = {
+ body: BodyComponent,
+ grabbable: GrabbableComponent
+};
+
--- /dev/null
+import { Node } from '@serpentity/serpentity';
+
+import BodyComponent from '../components/body';
+import GrabAreaComponent from '../components/grab_area';
+import GrabComponent from '../components/grab';
+
+/**
+ * Node identifying an entity that can grab another
+ *
+ * @extends {external:Serpentity.Node}
+ * @class GrabberNode
+ */
+export default class GrabberNode extends Node {
+
+};
+
+/**
+ * Holds the types that are used to identify a grabber entity
+ *
+ * @property {object} types
+ * @name types
+ * @memberof GrabberNode
+ */
+GrabberNode.types = {
+ body: BodyComponent,
+ grabArea: GrabAreaComponent,
+ grab: GrabComponent
+};
+
import ControlMapperSystem from './systems/control_mapper';
import DashSystem from './systems/dash';
import DrawDashSystem from './systems/draw_dash';
+import DrawGrabSystem from './systems/draw_grab';
import ElasticSystem from './systems/elastic';
+import GrabSystem from './systems/grab';
import PhysicsWorldControlSystem from './systems/physics_world_control';
import PhysicsToAttributesSystem from './systems/physics_to_attributes';
import RenderSystem from './systems/render';
this._engine.addSystem(new DashSystem());
+ this._engine.addSystem(new GrabSystem({
+ engine: this._matterJs
+ }));
+
this._engine.addSystem(new ApplyForceSystem());
this._engine.addSystem(new PhysicsWorldControlSystem({
this._engine.addSystem(new DrawDashSystem());
+ this._engine.addSystem(new DrawGrabSystem());
+
this._engine.addSystem(new RenderSystem({
application: this._pixi
}));
import DrawnDasherNode from '../nodes/drawn_dasher';
-const internals = {
- kBlushRadius: 25
-};
-
/**
* Shows a different graphic during the duration of lock
*
--- /dev/null
+import { System } from '@serpentity/serpentity';
+
+import DrawnGrabberNode from '../nodes/drawn_grabber';
+
+/**
+ * Shows a different graphic during the duration of lock
+ *
+ * @extends {external:Serpentity.System}
+ * @class DrawGrabSystem
+ * @param {object} config a configuration object to extend.
+ */
+export default class DrawGrabSystem extends System {
+
+ constructor(config = {}) {
+
+ super();
+
+ /**
+ * The node collection of grabbers
+ *
+ * @property {external:Serpentity.NodeCollection} drawnGrabbers
+ * @instance
+ * @memberof DrawGrabSystem
+ */
+ this.drawnGrabbers = null;
+ }
+
+ /**
+ * Initializes system when added. Requests drawn grabber nodes
+ *
+ * @function added
+ * @memberof DrawGrabSystem
+ * @instance
+ * @param {external:Serpentity.Engine} engine the serpentity engine to
+ * which we are getting added
+ */
+ added(engine) {
+
+ this.drawnGrabbers = engine.getNodes(DrawnGrabberNode);
+ }
+
+ /**
+ * Clears system resources when removed.
+ *
+ * @function removed
+ * @instance
+ * @memberof DrawGrabSystem
+ */
+ removed() {
+
+ this.drawnGrabbers = null;
+ }
+
+ /**
+ * Runs on every update of the loop. Updates image depending on if
+ * grab is locked and active
+ *
+ * @function update
+ * @instance
+ * @param {Number} currentFrameDuration the duration of the current
+ * frame
+ * @memberof DrawGrabSystem
+ */
+ update(currentFrameDuration) {
+
+ for (const drawnGrabber of this.drawnGrabbers) {
+
+ const grab = drawnGrabber.grab;
+ const container = drawnGrabber.container.container;
+
+ if (grab.locked && grab.constraint) {
+ this._drawGrabFace(container);
+ continue;
+ }
+
+ if (grab.locked) {
+ this._drawGrabCooldownFace(container);
+ continue;
+ }
+
+ this._removeGrabFace(container);
+ }
+ }
+
+ // Draws the grab face
+
+ _drawGrabFace(container) {
+
+ const effort = container.getChildByName('effort');
+ const shadow = container.getChildByName('shadow');
+ effort.visible = true;
+ shadow.visible = false;
+ }
+
+ // Draws the grab cooldown face
+
+ _drawGrabCooldownFace(container) {
+
+ const effort = container.getChildByName('effort');
+ const shadow = container.getChildByName('shadow');
+ effort.visible = false;
+ shadow.visible = true;
+ }
+
+ // Removes the dash face
+
+ _removeGrabFace(container) {
+
+ const effort = container.getChildByName('effort');
+ const shadow = container.getChildByName('shadow');
+ effort.visible = false;
+ shadow.visible = false;
+ }
+};
+
--- /dev/null
+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);
+
+ console.log('Grab!');
+
+ 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);
+ console.log('Grabbing', grab.constraint);
+ }
+ }
+ }
+
+ // Executes the unlock action
+
+ _unlock(grabber) {
+
+ grabber.grab.locked = false;
+ }
+
+ // Releases a constraint
+
+ _release(grabber) {
+
+ console.log('Releasing', grabber.grab.constraint);
+ 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;
+ }
+};
import { Engine, World } from 'matter-js';
import PhysicalNode from '../nodes/physical';
+import GrabAreaNode from '../nodes/grab_area';
const internals = {
kNoEngine: 'No matter js physics engine found. Make sure you set the `engine` key in the config object when initializing.'
added(engine) {
this.physicalEntities = engine.getNodes(PhysicalNode);
+ this.grabAreaEntities = engine.getNodes(GrabAreaNode);
+
this.physicalEntities.on('nodeAdded', (event) => {
World.add(this.engine.world, [event.node.body.body]);
World.remove(this.engine.world, [event.node.body.body]);
});
+
+ this.grabAreaEntities.on('nodeAdded', (event) => {
+
+ World.add(this.engine.world, [event.node.grabArea.area]);
+ });
+ this.grabAreaEntities.on('nodeRemoved', (event) => {
+
+ World.remove(this.engine.world, [event.node.grabArea.area]);
+ });
}
/**
this.physicalEntities.removeAllListeners('nodeAdded');
this.physicalEntities.removeAllListeners('nodeRemoved');
this.physicalEntities = null;
+
+ this.grabAreaEntities.removeAllListeners('nodeAdded');
+ this.grabAreaEntities.removeAllListeners('nodeRemoved');
+ this.grabAreaEntities = null;
}
/**
}
};
-