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