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