]>
Commit | Line | Data |
---|---|---|
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 | }; |