]> git.r.bdr.sh - rbdr/sumo/blob - lib/sumo.js
Add control via keyboard (#6)
[rbdr/sumo] / lib / sumo.js
1 import 'babel-polyfill';
2
3 // Systems
4 import ApplyForceSystem from './systems/apply_force';
5 import CreateCouplingLineSystem from './systems/create_coupling_line';
6 import ControlMapperSystem from './systems/control_mapper';
7 import DashSystem from './systems/dash';
8 import ElasticSystem from './systems/elastic';
9 import PhysicsWorldControlSystem from './systems/physics_world_control';
10 import PhysicsToAttributesSystem from './systems/physics_to_attributes';
11 import RenderSystem from './systems/render';
12 import AttributesToRenderableSystem from './systems/attributes_to_renderable';
13
14 // Factories
15
16 import SumoFactory from './factories/sumo';
17
18 // External Dependencies
19
20 import Serpentity from '@serpentity/serpentity';
21 import { Application } from 'pixi.js';
22 import { Engine } from 'matter-js';
23
24 /* global window document */
25
26 const internals = {
27 kBackgroundColor: 0xd8c590,
28 kNoElementError: 'No element found. Cannot render.',
29
30 // Handler for the window load event. Initializes and runs the app.
31
32 onLoad() {
33
34 const sumo = new internals.Sumo({
35 element: document.getElementById('sumo-app-entry-point')
36 });
37
38 sumo.startLoop();
39
40 internals.exports.sumo = sumo;
41 }
42 };
43
44 /**
45 * Sumo - main entry point. Attached to window->load
46 *
47 * @class Sumo
48 *
49 * @param {object} config the configuration to extend the object
50 *
51 * @property {HTMLElement} [element=null] the element in which to render.
52 * Required, will throw if not provided
53 * @property {Number} [fps=60] the fps target to maintain
54 * @property {Number} [verticalResolution=224] how many pixels to render in the vertical
55 * axis (gets scaled if the canvas is larger)
56 * @property {Array<Number>} [aspectRatio=[2.76, 1]] the aspect ratio experssed as
57 * an array of two numbers, where aspect ratio x:y is [x, y] (eg. [16, 9])
58 */
59
60 internals.Sumo = class Sumo {
61
62 constructor(config) {
63
64 this.fps = 60;
65 this.aspectRatio = [2.76, 1];
66 this.verticalResolution = 224;
67
68 Object.assign(this, config);
69
70 if (!this.element) {
71 throw new Error(internals.kNoElementError);
72 }
73
74 this._engine = new Serpentity();
75
76 this._previousTime = 0;
77 this._looping = false;
78
79 // Initialization functions
80 this._initializeCanvas();
81 this._initializeMatter();
82 this._initializePixi();
83 this._initializeSystems();
84 this._initializeEntities();
85 }
86
87 /**
88 * Starts the main loop. Resets the FPS (if you change it it won't go
89 * live until after you stop and start the loop)
90 *
91 * @function startLoop
92 * @instance
93 * @memberof Sumo
94 */
95 startLoop() {
96
97 this._looping = true;
98 this._frameDuration = 1000 / this.fps;
99 window.requestAnimationFrame(this._loop.bind(this));
100 }
101
102 /**
103 * Pauses the loop
104 *
105 * @function pauseLoop
106 * @instance
107 * @memberof Sumo
108 */
109 pauseLoop() {
110
111 this._looping = false;
112 }
113
114 // The main loop used above. Runs the serpentity update process and
115 // attempts to maintain FPS. The rest is handled by the engine.
116
117 _loop(currentTime) {
118
119 if (!this._looping) {
120 return;
121 }
122
123 window.requestAnimationFrame(this._loop.bind(this));
124
125 const currentFrameDuration = currentTime - this._previousTime;
126
127 if (currentFrameDuration > this._frameDuration) {
128
129 // We're sending the currentTime since it gives better results for
130 // this type of renderer, though usually we expect the delta
131 this._engine.update(currentFrameDuration);
132 this._previousTime = currentTime;
133 }
134 }
135
136 // Creates a canvas for rendering
137
138 _initializeCanvas() {
139
140 this._canvas = document.createElement('canvas');
141 this.element.appendChild(this._canvas);
142 this._resizeCanvas();
143 window.addEventListener('resize', this._resizeCanvas.bind(this));
144 }
145
146 // Initialize MatterJs
147
148 _initializeMatter() {
149
150 this._matterJs = Engine.create();
151
152 this._matterJs.world.gravity.y = 0;
153 }
154
155 // Initialize Pixi
156
157 _initializePixi() {
158
159 this._pixi = new Application({
160 backgroundColor: internals.kBackgroundColor,
161 view: this._canvas,
162 width: this._canvas.width,
163 height: this._canvas.height
164 });
165 }
166
167 // Resizes the canvas to a square the size of the smallest magnitude
168 // of the window.
169
170 _resizeCanvas() {
171
172 let width = window.innerWidth;
173 let height = Math.round(width * this.aspectRatio[1] / this.aspectRatio[0]);
174
175 if (window.innerHeight < height) {
176 height = window.innerHeight;
177 width = Math.round(height * this.aspectRatio[0] / this.aspectRatio[1]);
178 }
179
180 this._canvas.style.width = `${width}px`;
181 this._canvas.style.height = `${height}px`;
182
183 this._canvas.width = Math.round(this.verticalResolution * this.aspectRatio[0] / this.aspectRatio[1]);
184 this._canvas.height = this.verticalResolution;
185 }
186
187 // Initializes the serpentity systems
188
189 _initializeSystems() {
190
191 this._engine.addSystem(new ControlMapperSystem());
192
193 this._engine.addSystem(new DashSystem());
194
195 this._engine.addSystem(new ApplyForceSystem());
196
197 this._engine.addSystem(new PhysicsWorldControlSystem({
198 engine: this._matterJs
199 }));
200
201 this._engine.addSystem(new ElasticSystem());
202
203 this._engine.addSystem(new PhysicsToAttributesSystem());
204
205 this._engine.addSystem(new AttributesToRenderableSystem());
206
207 this._engine.addSystem(new CreateCouplingLineSystem());
208
209 this._engine.addSystem(new RenderSystem({
210 application: this._pixi
211 }));
212 }
213
214 // Initializes the serpentity entities
215
216 _initializeEntities() {
217
218 const sumoA = SumoFactory.createSumo(null, {
219 position: {
220 x: 50,
221 y: 50
222 }
223 });
224
225 const sumoB = SumoFactory.createControllableSumo(null, {
226 position: {
227 x: 500,
228 y: 78
229 }
230 });
231
232 const harness = SumoFactory.createHarness(null, {
233 position: {
234 x: 309,
235 y: 112
236 }
237 });
238
239 SumoFactory.createRubberBand(this._engine, {
240 entityA: sumoA,
241 entityB: harness
242 });
243
244 SumoFactory.createRubberBand(this._engine, {
245 entityA: sumoB,
246 entityB: harness
247 });
248
249 // To keep the coupling behind, we'll manually add the sumos later
250
251 this._engine.addEntity(sumoA);
252 this._engine.addEntity(sumoB);
253 this._engine.addEntity(harness);
254 }
255 };
256
257 export default internals.exports = {};
258
259 // autorun.bat
260 window.addEventListener('load', internals.onLoad);