]>
Commit | Line | Data |
---|---|---|
1 | import { Bodies, Constraint } from 'matter-js'; | |
2 | import { Entity } from '@serpentity/serpentity'; | |
3 | ||
4 | // Components | |
5 | ||
6 | import AngleComponent from '@serpentity/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 '@serpentity/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 | }; |