import { Bodies, Constraint } from 'matter-js'; import { Entity } from '@serpentity/serpentity'; // Components import AngleComponent from '@serpentity/components.angle'; import BodyComponent from '../components/body'; import ControlMapComponent from '../components/control_map'; import CoupledEntitiesComponent from '../components/coupled_entities'; import DashComponent from '../components/dash'; import ElasticComponent from '../components/elastic'; import ForceComponent from '@serpentity/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 PointsColliderComponent from '../components/points_collider'; import PointsComponent from '../components/points'; import PositionComponent from '@serpentity/components.position'; import PixiContainerComponent from '../components/pixi_container'; import WinnerComponent from '../components/winner'; import PixiFactory from '../factories/pixi'; import Config from '../config'; const internals = { kNoEntityError: 'Entity Not Found: This method requires entityA and entityB to be set in the config.', kEntityHasNoBodyError: 'Entity Has No Body: This method requires entities have a BodyComponent.' }; /** * Factory object that contains many methods to create prefab entities. * * @type object * @name SumoFactory */ export default { /** * Creates a sumo entity and adds it to the engine. Can override * position in the config object * * @function createSumo * @memberof SumoFactory * @param {external:Serpentity} [engine] the serpentity engine to attach * to. If not sent, it will not be attached. * @param {object} [config] the config to override the entity, accepts * the key `position` as an object with an x and y property. * @return {external:Serpentity.Entity} the created entity */ createSumo(engine, config = {}) { const entity = new Entity(); // POSITION entity.addComponent(new PositionComponent(config.position)); const position = entity.getComponent(PositionComponent); entity.addComponent(new AngleComponent(config.angle)); const angle = entity.getComponent(AngleComponent); entity.addComponent(new ForceComponent(config.force)); config.maxVelocity = { maxVelocity: 12 }; entity.addComponent(new MaxVelocityComponent(config.maxVelocity)); // CONTROLS & ABILITIES entity.addComponent(new DashComponent(config.dash)); // RENDERING const radius = 25; const pixiConfig = Object.assign({ radius }, config.pixi); const container = config.container || { container: PixiFactory.createSumo(pixiConfig) }; container.container.position.x = position.x; container.container.position.y = position.y; container.container.rotation = angle.angle; entity.addComponent(new PixiContainerComponent(container)); // PHYSICS const frictionAir = 0.02; const friction = 0.01; const frictionStatic = 0.01; const restitution = 1; const density = 1.5; const body = Bodies.circle(position.x / Config.meterSize, position.y / Config.meterSize, radius / Config.meterSize, { label: 'Sumo body', angle: angle.angle, density, frictionAir, frictionStatic, friction, restitution }); 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); } return entity; }, /** * Creates a rubber band entity and adds it to the engine. * * @function createRubberBand * @memberof SumoFactory * @param {external:Serpentity} [engine] the serpentity engine to attach * to. If not sent, it will not be attached. * @param {object} [config] the config to override the entity, it * must include entityA and entityB which will be tied by it. If they * are not sent or don't have a physics body, this will throw. * @return {external:Serpentity.Entity} the created entity */ createRubberBand(engine, config = {}) { const entity = new Entity(); if (!config.entityA || !config.entityB) { throw new Error(internals.kNoEntityError); } if (!config.entityA.hasComponent(BodyComponent) || !config.entityB.hasComponent(BodyComponent)) { throw new Error(internals.kEntityHasNoBodyError); } // RENDERING const container = config.container || { container: PixiFactory.createEmptyGraphic() }; entity.addComponent(new PixiContainerComponent(container)); // PHYSICS const bodyA = config.entityA.getComponent(BodyComponent).body; const bodyB = config.entityB.getComponent(BodyComponent).body; const damping = 0; const length = 100 / Config.meterSize; const stiffness = 0.001; const body = Constraint.create({ bodyA, bodyB, damping, length, stiffness }); entity.addComponent(new BodyComponent({ body })); entity.addComponent(new CoupledEntitiesComponent({ coupledEntities: [config.entityA, config.entityB] })); entity.addComponent(new ElasticComponent()); if (engine) { engine.addEntity(entity); } return entity; }, /** * Creates a controllable sumo entity and adds it to the engine. Can override * position in the config object * * @function createControllableSumo * @memberof SumoFactory * @param {external:Serpentity} [engine] the serpentity engine to attach * to. If not sent, it will not be attached. * @param {object} [config] the config to override the entity, accepts * the key `position` as an object with an x and y property. * @return {external:Serpentity.Entity} the created entity */ createControllableSumo(engine, config = {}) { const entity = this.createSumo(null, config); entity.addComponent(new ControlMapComponent(config.controlMap)); if (engine) { engine.addEntity(entity); } return entity; }, /** * Creates a controllable sumo entity and adds it to the engine. Can override * position in the config object. Has contrrol scheme defaults for * player 1 * * @function createPlayer1Sumo * @memberof SumoFactory * @param {external:Serpentity} [engine] the serpentity engine to attach * to. If not sent, it will not be attached. * @param {object} [config] the config to override the entity, accepts * the key `position` as an object with an x and y property. * @return {external:Serpentity.Entity} the created entity */ createPlayer1Sumo(engine, config = {}) { const playerConfig = Object.assign({ controlMap: { map: [ { source: { type: 'keyboard', index: 65 // a }, target: { component: ForceComponent, property: 'x', value: (value) => -Number(value) } }, { source: { type: 'keyboard', index: 68 // d }, target: { component: ForceComponent, property: 'x', value: (value) => Number(value) } }, { source: { type: 'keyboard', index: 87 // w }, target: { component: ForceComponent, property: 'y', value: (value) => -Number(value) } }, { source: { type: 'keyboard', index: 83 // s }, target: { component: ForceComponent, property: 'y', value: (value) => Number(value) } }, { source: { type: 'keyboard', index: 90 // Z }, target: { component: DashComponent, property: 'dashing' } }, { source: { type: 'keyboard', index: 88 // X }, target: { component: GrabComponent, property: 'grabbing' } }, { source: { type: 'gamepad', gamepadNumber: 0, gamepadInputSource: 'axes', index: 0 // left stick horizontal }, target: { component: ForceComponent, property: 'x', value: (value) => Number(value) } }, { source: { type: 'gamepad', gamepadNumber: 0, gamepadInputSource: 'axes', index: 1 // left stick vertical }, target: { component: ForceComponent, property: 'y', value: (value) => Number(value) } }, { source: { type: 'gamepad', gamepadNumber: 0, gamepadInputSource: 'buttons', index: 2 // left face button }, target: { component: DashComponent, property: 'dashing', value: (value) => value.value } }, { source: { type: 'gamepad', gamepadNumber: 0, gamepadInputSource: 'buttons', index: 0 // bottom face button }, target: { component: GrabComponent, property: 'grabbing', value: (value) => value.value } } ] } }, config); const entity = this.createControllableSumo(null, playerConfig); if (engine) { engine.addEntity(entity); } return entity; }, /** * Creates a controllable sumo entity and adds it to the engine. Can override * position in the config object. Has contrrol scheme defaults for * player 2 * * @function createPlayer2Sumo * @memberof SumoFactory * @param {external:Serpentity} [engine] the serpentity engine to attach * to. If not sent, it will not be attached. * @param {object} [config] the config to override the entity, accepts * the key `position` as an object with an x and y property. * @return {external:Serpentity.Entity} the created entity */ createPlayer2Sumo(engine, config = {}) { const playerConfig = Object.assign({ pixi: { color: 0xeaacac }, controlMap: { map: [ { source: { type: 'keyboard', index: 37 // left arrow }, target: { component: ForceComponent, property: 'x', value: (value) => -Number(value) } }, { source: { type: 'keyboard', index: 39 // right arrow }, target: { component: ForceComponent, property: 'x', value: (value) => Number(value) } }, { source: { type: 'keyboard', index: 38 // up arrow }, target: { component: ForceComponent, property: 'y', value: (value) => -Number(value) } }, { source: { type: 'keyboard', index: 40 // down arrow }, target: { component: ForceComponent, property: 'y', value: (value) => Number(value) } }, { source: { type: 'keyboard', index: 188 // , }, target: { component: DashComponent, property: 'dashing' } }, { source: { type: 'keyboard', index: 190 // . }, target: { component: GrabComponent, property: 'grabbing' } }, { source: { type: 'gamepad', gamepadNumber: 1, gamepadInputSource: 'axes', index: 0 // left stick horizontal }, target: { component: ForceComponent, property: 'x', value: (value) => Number(value) } }, { source: { type: 'gamepad', gamepadNumber: 1, gamepadInputSource: 'axes', index: 1 // left stick vertical }, target: { component: ForceComponent, property: 'y', value: (value) => Number(value) } }, { source: { type: 'gamepad', gamepadNumber: 1, gamepadInputSource: 'buttons', index: 2 // left face button }, target: { component: DashComponent, property: 'dashing', value: (value) => value.value } }, { source: { type: 'gamepad', gamepadNumber: 1, gamepadInputSource: 'buttons', index: 0 // bottom face button }, target: { component: GrabComponent, property: 'grabbing', value: (value) => value.value } } ] } }, config); const entity = this.createControllableSumo(null, playerConfig); if (engine) { engine.addEntity(entity); } return entity; }, /** * Creates a static harness entity * * @function createHarness * @memberof SumoFactory * @param {external:Serpentity} [engine] the serpentity engine to attach * to. If not sent, it will not be attached. * @param {object} [config] the config to override the entity, accepts * the key `position` as an object with an x and y property. * @return {external:Serpentity.Entity} the created entity */ createHarness(engine, config = {}) { const entity = new Entity(); // POSITION entity.addComponent(new PositionComponent(config.position)); const position = entity.getComponent(PositionComponent); // RENDERING const radius = 15; const container = config.container || { container: PixiFactory.createHarness({ radius }) }; container.container.position.x = position.x; container.container.position.y = position.y; entity.addComponent(new PixiContainerComponent(container)); // PHYSICS const friction = 0; const frictionStatic = 0; const restitution = 1; const body = Bodies.circle(position.x / Config.meterSize, position.y / Config.meterSize, radius / Config.meterSize, { isStatic: true, label: 'Harness body', friction, restitution, frictionStatic }); entity.addComponent(new BodyComponent({ body })); if (engine) { engine.addEntity(entity); } return entity; }, /** * Creates a static arena entity * * @function createArena * @memberof SumoFactory * @param {external:Serpentity} [engine] the serpentity engine to attach * to. If not sent, it will not be attached. * @param {object} [config] the config to override the entity, accepts * the key `position` as an object with an x and y property. * @return {external:Serpentity.Entity} the created entity */ createArena(engine, config = {}) { const entity = new Entity(); // POSITION entity.addComponent(new PositionComponent(config.position)); const position = entity.getComponent(PositionComponent); // RENDERING const radius = 300; const container = config.container || { container: PixiFactory.createArena({ radius }) }; container.container.position.x = position.x; container.container.position.y = position.y; entity.addComponent(new PixiContainerComponent(container)); if (engine) { engine.addEntity(entity); } return entity; }, /** * Creates an invisible block * * @function createInvisibleBlock * @memberof SumoFactory * @param {external:Serpentity} [engine] the serpentity engine to attach * to. If not sent, it will not be attached. * @param {object} [config] the config to override the entity, accepts * the key `position` as an object with an x and y property. * @return {external:Serpentity.Entity} the created entity */ createInvisibleBlock(engine, config = {}) { const entity = new Entity(); // POSITION entity.addComponent(new PositionComponent(config.position)); const position = entity.getComponent(PositionComponent); // PHYSICS const friction = 0; const frictionStatic = 0; const restitution = 1; const label = config.label || 'Invisible Block'; const isSensor = !!(config.isSensor); const body = Bodies.rectangle(position.x / Config.meterSize, position.y / Config.meterSize, config.width / Config.meterSize, config.height / Config.meterSize, { isSensor, isStatic: true, label, friction, restitution, frictionStatic }); entity.addComponent(new BodyComponent({ body })); if (engine) { engine.addEntity(entity); } return entity; }, /** * Creates an invisible block that accumulates points if certain * entity collids with it * * @function createPointsCollider * @memberof SumoFactory * @param {external:Serpentity} [engine] the serpentity engine to attach * to. If not sent, it will not be attached. * @param {object} [config] the config to override the entity, accepts * the key `position` as an object with an x and y property. * @return {external:Serpentity.Entity} the created entity */ createPointsCollider(engine, config = {}) { const entity = this.createInvisibleBlock(null, Object.assign({ isSensor: true, label: 'Points Detector' }, config)); // Points Collider entity.addComponent(new PointsColliderComponent(config)); if (engine) { engine.addEntity(entity); } return entity; }, /** * Creates an entity representing the game state * * @function createGameState * @memberof SumoFactory * @param {external:Serpentity} [engine] the serpentity engine to attach * to. If not sent, it will not be attached. * @param {object} [config] the config to override the entity, accepts * the key `position` as an object with an x and y property. * @return {external:Serpentity.Entity} the created entity */ createGameState(engine, config = {}) { const entity = new Entity(); entity.addComponent(new PointsComponent(config)); entity.addComponent(new WinnerComponent(config)); if (engine) { engine.addEntity(entity); } return entity; } };