]> git.r.bdr.sh - rbdr/sumo/blob - lib/factories/sumo.js
Add gamepad support (#10)
[rbdr/sumo] / lib / factories / sumo.js
1 import { Bodies, Constraint } from 'matter-js';
2 import { Entity } from '@serpentity/serpentity';
3
4 // Components
5
6 import AngleComponent from '../components/angle';
7 import BodyComponent from '../components/body';
8 import ControlMapComponent from '../components/control_map';
9 import CoupledEntitiesComponent from '../components/coupled_entities';
10 import DashComponent from '../components/dash';
11 import ElasticComponent from '../components/elastic';
12 import ForceComponent from '../components/force';
13 import GrabAreaComponent from '../components/grab_area';
14 import GrabbableComponent from '../components/grabbable';
15 import GrabComponent from '../components/grab';
16 import MaxVelocityComponent from '../components/max_velocity';
17 import PositionComponent from '@serpentity/components.position';
18 import PixiContainerComponent from '../components/pixi_container';
19
20 import PixiFactory from '../factories/pixi';
21 import Config from '../config';
22
23 const internals = {
24 kNoEntityError: 'Entity Not Found: This method requires entityA and entityB to be set in the config.',
25 kEntityHasNoBodyError: 'Entity Has No Body: This method requires entities have a BodyComponent.'
26 };
27
28 /**
29 * Factory object that contains many methods to create prefab entities.
30 *
31 * @type object
32 * @name SumoFactory
33 */
34 export default {
35
36 /**
37 * Creates a sumo entity and adds it to the engine. Can override
38 * position in the config object
39 *
40 * @function createSumo
41 * @memberof SumoFactory
42 * @param {external:Serpentity} [engine] the serpentity engine to attach
43 * to. If not sent, it will not be attached.
44 * @param {object} [config] the config to override the entity, accepts
45 * the key `position` as an object with an x and y property.
46 * @return {external:Serpentity.Entity} the created entity
47 */
48 createSumo(engine, config = {}) {
49
50 const entity = new Entity();
51
52 // POSITION
53
54 entity.addComponent(new PositionComponent(config.position));
55 const position = entity.getComponent(PositionComponent);
56
57 entity.addComponent(new AngleComponent(config.angle));
58 const angle = entity.getComponent(AngleComponent);
59
60 entity.addComponent(new ForceComponent(config.force));
61
62 config.maxVelocity = {
63 maxVelocity: 12
64 };
65 entity.addComponent(new MaxVelocityComponent(config.maxVelocity));
66
67 // CONTROLS & ABILITIES
68
69 entity.addComponent(new DashComponent(config.dash));
70
71 // RENDERING
72
73 const radius = 25;
74 const pixiConfig = Object.assign({
75 radius
76 }, config.pixi);
77
78 const container = config.container || {
79 container: PixiFactory.createSumo(pixiConfig)
80 };
81 container.container.position.x = position.x;
82 container.container.position.y = position.y;
83 container.container.rotation = angle.angle;
84 entity.addComponent(new PixiContainerComponent(container));
85
86 // PHYSICS
87
88 const frictionAir = 0.02;
89 const friction = 0.01;
90 const frictionStatic = 0.01;
91 const restitution = 1;
92 const density = 1.5;
93
94 const body = Bodies.circle(position.x / Config.meterSize, position.y / Config.meterSize, radius / Config.meterSize, {
95 label: 'Sumo body',
96 angle: angle.angle,
97 density,
98 frictionAir,
99 frictionStatic,
100 friction,
101 restitution
102 });
103 entity.addComponent(new BodyComponent({ body }));
104
105 // GRAB
106
107 const areaSizeFactor = 2; // Multiplier vs the radius
108 const area = Bodies.circle(position.x / Config.meterSize, position.y / Config.meterSize, (radius * areaSizeFactor) / Config.meterSize, {
109 label: 'Sumo Grab Area',
110 isSensor: true
111 });
112
113 entity.addComponent(new GrabAreaComponent({ area }));
114 entity.addComponent(new GrabComponent({ body }));
115 entity.addComponent(new GrabbableComponent({ body }));
116
117 if (engine) {
118 engine.addEntity(entity);
119 }
120
121 return entity;
122 },
123
124 /**
125 * Creates a rubber band entity and adds it to the engine.
126 *
127 * @function createRubberBand
128 * @memberof SumoFactory
129 * @param {external:Serpentity} [engine] the serpentity engine to attach
130 * to. If not sent, it will not be attached.
131 * @param {object} [config] the config to override the entity, it
132 * must include entityA and entityB which will be tied by it. If they
133 * are not sent or don't have a physics body, this will throw.
134 * @return {external:Serpentity.Entity} the created entity
135 */
136 createRubberBand(engine, config = {}) {
137
138 const entity = new Entity();
139
140 if (!config.entityA || !config.entityB) {
141 throw new Error(internals.kNoEntityError);
142 }
143
144 if (!config.entityA.hasComponent(BodyComponent) || !config.entityB.hasComponent(BodyComponent)) {
145 throw new Error(internals.kEntityHasNoBodyError);
146 }
147
148 // RENDERING
149
150 const container = config.container || {
151 container: PixiFactory.createEmptyGraphic()
152 };
153 entity.addComponent(new PixiContainerComponent(container));
154
155 // PHYSICS
156
157 const bodyA = config.entityA.getComponent(BodyComponent).body;
158 const bodyB = config.entityB.getComponent(BodyComponent).body;
159 const damping = 0;
160 const length = 100 / Config.meterSize;
161 const stiffness = 0.001;
162
163 const body = Constraint.create({
164 bodyA,
165 bodyB,
166 damping,
167 length,
168 stiffness
169 });
170 entity.addComponent(new BodyComponent({ body }));
171
172 entity.addComponent(new CoupledEntitiesComponent({
173 coupledEntities: [config.entityA, config.entityB]
174 }));
175
176 entity.addComponent(new ElasticComponent());
177
178 if (engine) {
179 engine.addEntity(entity);
180 }
181
182 return entity;
183 },
184
185 /**
186 * Creates a controllable sumo entity and adds it to the engine. Can override
187 * position in the config object
188 *
189 * @function createControllableSumo
190 * @memberof SumoFactory
191 * @param {external:Serpentity} [engine] the serpentity engine to attach
192 * to. If not sent, it will not be attached.
193 * @param {object} [config] the config to override the entity, accepts
194 * the key `position` as an object with an x and y property.
195 * @return {external:Serpentity.Entity} the created entity
196 */
197 createControllableSumo(engine, config = {}) {
198
199 const entity = this.createSumo(null, config);
200
201 entity.addComponent(new ControlMapComponent(config.controlMap));
202
203 if (engine) {
204 engine.addEntity(entity);
205 }
206
207 return entity;
208 },
209
210 /**
211 * Creates a controllable sumo entity and adds it to the engine. Can override
212 * position in the config object. Has contrrol scheme defaults for
213 * player 1
214 *
215 * @function createPlayer1Sumo
216 * @memberof SumoFactory
217 * @param {external:Serpentity} [engine] the serpentity engine to attach
218 * to. If not sent, it will not be attached.
219 * @param {object} [config] the config to override the entity, accepts
220 * the key `position` as an object with an x and y property.
221 * @return {external:Serpentity.Entity} the created entity
222 */
223 createPlayer1Sumo(engine, config = {}) {
224
225 const playerConfig = Object.assign({
226 controlMap: {
227 map: [
228 {
229 source: {
230 type: 'keyboard',
231 index: 65 // a
232 },
233 target: {
234 component: ForceComponent,
235 property: 'x',
236 value: (value) => -Number(value)
237 }
238 },
239 {
240 source: {
241 type: 'keyboard',
242 index: 68 // d
243 },
244 target: {
245 component: ForceComponent,
246 property: 'x',
247 value: (value) => Number(value)
248 }
249 },
250 {
251 source: {
252 type: 'keyboard',
253 index: 87 // w
254 },
255 target: {
256 component: ForceComponent,
257 property: 'y',
258 value: (value) => -Number(value)
259 }
260 },
261 {
262 source: {
263 type: 'keyboard',
264 index: 83 // s
265 },
266 target: {
267 component: ForceComponent,
268 property: 'y',
269 value: (value) => Number(value)
270 }
271 },
272 {
273 source: {
274 type: 'keyboard',
275 index: 90 // Z
276 },
277 target: {
278 component: DashComponent,
279 property: 'dashing'
280 }
281 },
282 {
283 source: {
284 type: 'keyboard',
285 index: 88 // X
286 },
287 target: {
288 component: GrabComponent,
289 property: 'grabbing'
290 }
291 },
292 {
293 source: {
294 type: 'gamepad',
295 gamepadNumber: 0,
296 gamepadInputSource: 'axes',
297 index: 0 // left stick horizontal
298 },
299 target: {
300 component: ForceComponent,
301 property: 'x',
302 value: (value) => Number(value)
303 }
304 },
305 {
306 source: {
307 type: 'gamepad',
308 gamepadNumber: 0,
309 gamepadInputSource: 'axes',
310 index: 1 // left stick vertical
311 },
312 target: {
313 component: ForceComponent,
314 property: 'y',
315 value: (value) => Number(value)
316 }
317 },
318 {
319 source: {
320 type: 'gamepad',
321 gamepadNumber: 0,
322 gamepadInputSource: 'buttons',
323 index: 2 // left face button
324 },
325 target: {
326 component: DashComponent,
327 property: 'dashing',
328 value: (value) => value.value
329 }
330 },
331 {
332 source: {
333 type: 'gamepad',
334 gamepadNumber: 0,
335 gamepadInputSource: 'buttons',
336 index: 0 // bottom face button
337 },
338 target: {
339 component: GrabComponent,
340 property: 'grabbing',
341 value: (value) => value.value
342 }
343 }
344 ]
345 }
346 }, config);
347
348 const entity = this.createControllableSumo(null, playerConfig);
349
350 if (engine) {
351 engine.addEntity(entity);
352 }
353
354 return entity;
355 },
356
357 /**
358 * Creates a controllable sumo entity and adds it to the engine. Can override
359 * position in the config object. Has contrrol scheme defaults for
360 * player 2
361 *
362 * @function createPlayer2Sumo
363 * @memberof SumoFactory
364 * @param {external:Serpentity} [engine] the serpentity engine to attach
365 * to. If not sent, it will not be attached.
366 * @param {object} [config] the config to override the entity, accepts
367 * the key `position` as an object with an x and y property.
368 * @return {external:Serpentity.Entity} the created entity
369 */
370 createPlayer2Sumo(engine, config = {}) {
371
372 const playerConfig = Object.assign({
373 pixi: {
374 color: 0xeaacac
375 },
376 controlMap: {
377 map: [
378 {
379 source: {
380 type: 'keyboard',
381 index: 37 // left arrow
382 },
383 target: {
384 component: ForceComponent,
385 property: 'x',
386 value: (value) => -Number(value)
387 }
388 },
389 {
390 source: {
391 type: 'keyboard',
392 index: 39 // right arrow
393 },
394 target: {
395 component: ForceComponent,
396 property: 'x',
397 value: (value) => Number(value)
398 }
399 },
400 {
401 source: {
402 type: 'keyboard',
403 index: 38 // up arrow
404 },
405 target: {
406 component: ForceComponent,
407 property: 'y',
408 value: (value) => -Number(value)
409 }
410 },
411 {
412 source: {
413 type: 'keyboard',
414 index: 40 // down arrow
415 },
416 target: {
417 component: ForceComponent,
418 property: 'y',
419 value: (value) => Number(value)
420 }
421 },
422 {
423 source: {
424 type: 'keyboard',
425 index: 188 // ,
426 },
427 target: {
428 component: DashComponent,
429 property: 'dashing'
430 }
431 },
432 {
433 source: {
434 type: 'keyboard',
435 index: 190 // .
436 },
437 target: {
438 component: GrabComponent,
439 property: 'grabbing'
440 }
441 },
442 {
443 source: {
444 type: 'gamepad',
445 gamepadNumber: 1,
446 gamepadInputSource: 'axes',
447 index: 0 // left stick horizontal
448 },
449 target: {
450 component: ForceComponent,
451 property: 'x',
452 value: (value) => Number(value)
453 }
454 },
455 {
456 source: {
457 type: 'gamepad',
458 gamepadNumber: 1,
459 gamepadInputSource: 'axes',
460 index: 1 // left stick vertical
461 },
462 target: {
463 component: ForceComponent,
464 property: 'y',
465 value: (value) => Number(value)
466 }
467 },
468 {
469 source: {
470 type: 'gamepad',
471 gamepadNumber: 1,
472 gamepadInputSource: 'buttons',
473 index: 2 // left face button
474 },
475 target: {
476 component: DashComponent,
477 property: 'dashing',
478 value: (value) => value.value
479 }
480 },
481 {
482 source: {
483 type: 'gamepad',
484 gamepadNumber: 1,
485 gamepadInputSource: 'buttons',
486 index: 0 // bottom face button
487 },
488 target: {
489 component: GrabComponent,
490 property: 'grabbing',
491 value: (value) => value.value
492 }
493 }
494 ]
495 }
496 }, config);
497
498 const entity = this.createControllableSumo(null, playerConfig);
499
500 if (engine) {
501 engine.addEntity(entity);
502 }
503
504 return entity;
505 },
506
507 /**
508 * Creates a static harness entity
509 *
510 * @function createHarness
511 * @memberof SumoFactory
512 * @param {external:Serpentity} [engine] the serpentity engine to attach
513 * to. If not sent, it will not be attached.
514 * @param {object} [config] the config to override the entity, accepts
515 * the key `position` as an object with an x and y property.
516 * @return {external:Serpentity.Entity} the created entity
517 */
518 createHarness(engine, config = {}) {
519
520 const entity = new Entity();
521
522 // POSITION
523
524 entity.addComponent(new PositionComponent(config.position));
525 const position = entity.getComponent(PositionComponent);
526
527 // RENDERING
528
529 const radius = 15;
530
531 const container = config.container || {
532 container: PixiFactory.createHarness({ radius })
533 };
534 container.container.position.x = position.x;
535 container.container.position.y = position.y;
536 entity.addComponent(new PixiContainerComponent(container));
537
538 // PHYSICS
539
540 const friction = 0;
541 const frictionStatic = 0;
542 const restitution = 1;
543
544 const body = Bodies.circle(position.x / Config.meterSize, position.y / Config.meterSize, radius / Config.meterSize, {
545 isStatic: true,
546 label: 'Harness body',
547 friction,
548 restitution,
549 frictionStatic
550 });
551 entity.addComponent(new BodyComponent({ body }));
552
553 if (engine) {
554 engine.addEntity(entity);
555 }
556
557 return entity;
558 },
559
560 /**
561 * Creates a static arena entity
562 *
563 * @function createArena
564 * @memberof SumoFactory
565 * @param {external:Serpentity} [engine] the serpentity engine to attach
566 * to. If not sent, it will not be attached.
567 * @param {object} [config] the config to override the entity, accepts
568 * the key `position` as an object with an x and y property.
569 * @return {external:Serpentity.Entity} the created entity
570 */
571 createArena(engine, config = {}) {
572
573 const entity = new Entity();
574
575 // POSITION
576
577 entity.addComponent(new PositionComponent(config.position));
578 const position = entity.getComponent(PositionComponent);
579
580 // RENDERING
581
582 const radius = 300;
583
584 const container = config.container || {
585 container: PixiFactory.createArena({ radius })
586 };
587 container.container.position.x = position.x;
588 container.container.position.y = position.y;
589 entity.addComponent(new PixiContainerComponent(container));
590
591 if (engine) {
592 engine.addEntity(entity);
593 }
594
595 return entity;
596 },
597
598 /**
599 * Creates an invisible block
600 *
601 * @function createInvisibleBlock
602 * @memberof SumoFactory
603 * @param {external:Serpentity} [engine] the serpentity engine to attach
604 * to. If not sent, it will not be attached.
605 * @param {object} [config] the config to override the entity, accepts
606 * the key `position` as an object with an x and y property.
607 * @return {external:Serpentity.Entity} the created entity
608 */
609 createInvisibleBlock(engine, config = {}) {
610
611 const entity = new Entity();
612
613 // POSITION
614
615 entity.addComponent(new PositionComponent(config.position));
616 const position = entity.getComponent(PositionComponent);
617
618 // PHYSICS
619
620 const friction = 0;
621 const frictionStatic = 0;
622 const restitution = 1;
623
624 const body = Bodies.rectangle(position.x / Config.meterSize,
625 position.y / Config.meterSize,
626 config.width / Config.meterSize,
627 config.height / Config.meterSize,
628 {
629 isStatic: true,
630 label: 'Invisible Block',
631 friction,
632 restitution,
633 frictionStatic
634 });
635 entity.addComponent(new BodyComponent({ body }));
636
637 if (engine) {
638 engine.addEntity(entity);
639 }
640
641 return entity;
642 }
643 };