UNPKG

19.1 kBJavaScriptView Raw
1"use strict";
2var __extends = (this && this.__extends) || (function () {
3 var extendStatics = Object.setPrototypeOf ||
4 ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
5 function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
6 return function (d, b) {
7 extendStatics(d, b);
8 function __() { this.constructor = d; }
9 d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
10 };
11})();
12Object.defineProperty(exports, "__esModule", { value: true });
13var p2_1 = require("p2");
14var Tool_1 = require("./../Tool");
15var Entity_1 = require("./../Entity");
16var Module_1 = require("./../Module");
17var SideralObject_1 = require("./../SideralObject");
18/**
19 * A scene is a container of modules
20 */
21var Scene = (function (_super) {
22 __extends(Scene, _super);
23 /* LIFECYCLE */
24 /**
25 * @constructor
26 */
27 function Scene() {
28 var _this = _super.call(this) || this;
29 /**
30 * List of all children which is an instance of Entity
31 * @private
32 */
33 _this._entities = null;
34 /**
35 * The tilemap of the scene
36 */
37 _this.tilemap = null;
38 /**
39 * The p2 World physics
40 * @readonly
41 */
42 _this.world = null;
43 /**
44 * List of all materials of the world
45 * @readonly
46 */
47 _this.materials = [];
48 /**
49 * List of all physics in this scenes
50 * @readonly
51 */
52 _this.physics = [];
53 /**
54 * The amplitude of the screen shake
55 * @readonly
56 */
57 _this.shakeAmplitude = 0;
58 /**
59 * All physic in queue
60 * @private
61 */
62 _this._physicQueue = [];
63 _this.setProps({
64 scale: 1,
65 motionFactor: 1,
66 backgroundColor: Tool_1.Color.transparent,
67 backgroundAlpha: 1,
68 gravity: 0,
69 sizeAuto: true
70 });
71 _this.signals.progress = new SideralObject_1.Signal(_this);
72 _this.context.scene = _this;
73 return _this;
74 }
75 /**
76 * @initialize
77 * @lifecycle
78 * @override
79 */
80 Scene.prototype.initialize = function (props) {
81 _super.prototype.initialize.call(this, props);
82 this.setProps({
83 width: this.context.game.props.width,
84 height: this.context.game.props.height
85 });
86 this.onBackgroundChange();
87 this.connect(this.signals.propChange, "gravity", this.onGravityChange.bind(this))
88 .connect(this.signals.propChange, ["backgroundColor", "backgroundAlpha"], this.onBackgroundChange.bind(this));
89 };
90 /**
91 * @update
92 * @lifecycle
93 * @override
94 */
95 Scene.prototype.update = function (tick) {
96 var _this = this;
97 tick *= this.props.motionFactor;
98 _super.prototype.update.call(this, tick);
99 if (this.world) {
100 var fixedStep = (1 / 60) * this.props.motionFactor, maxStep = 3;
101 this.world.step(fixedStep, Tool_1.Util.limit(tick, 0, maxStep * fixedStep), maxStep);
102 this._physicQueue.forEach(function (physic) { return _this.physics.find(function (x) { return x.id === physic.id; }) && _this.addPhysic(physic); });
103 }
104 if (this.container && this.shakeAmplitude) {
105 this.container.pivot.set(this.shakeAmplitude * (Math.random() - 0.5), this.shakeAmplitude * (Math.random() - 0.5));
106 }
107 };
108 /* METHODS */
109 /**
110 * Create a fade effect
111 * @param fadeType - Type of fade ("in" or "out")
112 * @param color - The color of the fade effect
113 * @param duration - Duration of the effect
114 * @param onComplete - Function to be called on effect complete
115 * @returns The fade object
116 */
117 Scene.prototype.fade = function (fadeType, color, duration, onComplete) {
118 var _this = this;
119 if (!this._fade) {
120 this._fade = this.add(new Module_1.Shape(), {
121 width: this.props.width,
122 height: this.props.height,
123 fill: color,
124 fillAlpha: fadeType === "in" ? 1 : 0
125 });
126 }
127 this.timers.addTimer("fade", duration, function () {
128 _this._fade.props.fillAlpha = fadeType === "in" ? 0 : 1;
129 if (onComplete) {
130 onComplete();
131 }
132 }, {
133 update: function (tick, value, ratio) { return _this._fade.props.fillAlpha = fadeType === "in" ? 1 - ratio : ratio; }
134 });
135 return this._fade;
136 };
137 /**
138 * Enable world physics
139 * @param gravity - The power of gravity
140 */
141 Scene.prototype.enablePhysics = function (gravity) {
142 var _this = this;
143 this.disablePhysics();
144 gravity = typeof gravity === "undefined" ? this.props.gravity : gravity;
145 this.world = new p2_1.World({ gravity: [0, gravity] });
146 this.materials = [this.getDefaultMaterial()];
147 this.getAllEntities().filter(function (entity) { return entity.physic; }).forEach(function (entity) {
148 if (!entity.physic.shape.material) {
149 entity.physic.shape.material = _this.getDefaultMaterial();
150 }
151 var materialId = entity.physic.shape.material.id;
152 if (!_this.materials.find(function (material) { return material.id === materialId; })) {
153 _this.materials.push(entity.physic.shape.material);
154 }
155 _this.addPhysic(entity.physic);
156 });
157 this.setProps({ gravity: gravity });
158 this.world.setGlobalStiffness(Number.MAX_VALUE);
159 this.world.on("beginContact", this._onBeginContact.bind(this), false);
160 this.world.on("endContact", this._onEndContact.bind(this), false);
161 this.world.on("preSolve", this._onPreSolve.bind(this), false);
162 };
163 /**
164 * Disable and remove the world physics
165 */
166 Scene.prototype.disablePhysics = function () {
167 var _this = this;
168 if (this.world) {
169 this.physics.forEach(function (physic) { return _this.removePhysic(physic); });
170 this.materials = [];
171 this.physics = [];
172 this.world = null;
173 }
174 };
175 /**
176 * Get the default p2.Material
177 * @returns The default p2.Material
178 */
179 Scene.prototype.getDefaultMaterial = function () {
180 return this.world && this.world.defaultMaterial;
181 };
182 /**
183 * Add a new Physic object to the current scene
184 * @param physic - Physic object to add
185 */
186 Scene.prototype.addPhysic = function (physic) {
187 if (this.world) {
188 this.world.addBody(physic.body);
189 this.physics.push(physic);
190 }
191 };
192 /**
193 * Remove a Physic object to the current scene
194 * @param physic - Physic object to remove
195 */
196 Scene.prototype.removePhysic = function (physic) {
197 if (this.world) {
198 this.world.removeBody(physic.body);
199 }
200 };
201 /**
202 * Shake the current scene
203 * @access public
204 * @param amplitude - Amplitude of the shake
205 * @param duration - Duration of the shake
206 */
207 Scene.prototype.shake = function (amplitude, duration) {
208 var _this = this;
209 if (duration === void 0) { duration = 10; }
210 this.shakeAmplitude = amplitude;
211 this.timers.addTimer("shake", duration, function () {
212 _this.shakeAmplitude = 0;
213 if (_this.container) {
214 _this.container.pivot.set(0, 0);
215 }
216 });
217 };
218 /**
219 * Get the entity owner of the physic by its body id
220 * @param body - The body to check
221 * @returns The entity found
222 */
223 Scene.prototype.getPhysicOwnerByBody = function (body) {
224 var physic = this.physics.find(function (x) { return x.body.id === body.id; });
225 return physic && physic.owner;
226 };
227 /**
228 * Set a new bouncing factor for the entity
229 * @param physic - The physic to change the bounce factor
230 * @param bounce - Next value of bouncing
231 * @returns Bouncing factor
232 */
233 Scene.prototype.setPhysicBouncing = function (physic, bounce) {
234 var _this = this;
235 if (physic.shape.material.id !== this.getDefaultMaterial().id) {
236 var materialId_1 = physic.shape.material.id;
237 this.world.contactMaterials.filter(function (x) { return x.materialA.id === materialId_1 || x.materialB.id === materialId_1; })
238 .forEach(function (contactMaterial) { return _this.world.removeContactMaterial(contactMaterial); });
239 }
240 if (!bounce) {
241 physic.shape.material = this.getDefaultMaterial();
242 }
243 else {
244 var materialOptions_1 = {
245 restitution: bounce,
246 stiffness: Number.MAX_VALUE,
247 friction: this.world.defaultContactMaterial.friction
248 }, material_1 = physic.shape.material = new p2_1.Material(Scene.generateId());
249 this.materials.push(material_1);
250 var contactMaterials = this.materials.map(function (materialB) { return new p2_1.ContactMaterial(material_1, materialB, materialOptions_1); });
251 contactMaterials.forEach(function (contactMaterial) { return _this.world.addContactMaterial(contactMaterial); });
252 }
253 return bounce;
254 };
255 /**
256 * Set a tilemap for the current scene
257 * @param data: data of the tilemap (generaly provided by a json file)
258 * @returns Tilemap instance
259 */
260 Scene.prototype.setTilemap = function (data) {
261 this.tilemap = this.add(new Module_1.Tilemap(), {}, this._background ? 1 : 0);
262 this.tilemap.setData(data);
263 return this.tilemap;
264 };
265 /**
266 * Get all children instance of Entity
267 * @returns Array of all entities
268 */
269 Scene.prototype.getEntities = function () {
270 return this._entities || (this._entities = this.children.filter(function (child) { return child instanceof Entity_1.Entity; }));
271 };
272 /**
273 * Get all children instance of Entity and their children
274 * @returns Array of all entities (even their children)
275 */
276 Scene.prototype.getAllEntities = function () {
277 var findChildrenEntities = function (child) {
278 var childrenEntities = child instanceof Entity_1.Entity ? [child] : [];
279 child.children.forEach(function (subchild) { return childrenEntities = childrenEntities.concat(findChildrenEntities(subchild)); });
280 return childrenEntities;
281 };
282 return this.children.reduce(function (acc, entity) { return acc.concat(findChildrenEntities(entity)); }, []);
283 };
284 /**
285 * Get entities from the scene in range
286 * @param xmin: position x min
287 * @param xmax: position x max
288 * @param ymin: position y min
289 * @param ymax: position y max
290 * @param id: string of the id of an entity to filter
291 * @returns list of all entities in range
292 */
293 Scene.prototype.getEntitiesInRange = function (xmin, xmax, ymin, ymax, id) {
294 var entities = this.getEntities().
295 filter(function (entity) { return entity.props.x > (xmin - entity.props.width) && entity.props.x < xmax; }).
296 filter(function (entity) { return entity.props.y > (ymin - entity.props.height) && entity.props.y < ymax; });
297 return id ? entities.filter(function (entity) { return id !== entity.id; }) : entities;
298 };
299 /* EVENTS */
300 /**
301 * @override
302 */
303 Scene.prototype.onSizeChange = function () {
304 _super.prototype.onSizeChange.call(this);
305 if (this._background) {
306 this._background.props.width = this.props.width;
307 this._background.props.height = this.props.height;
308 }
309 };
310 /**
311 * Update the position of the camera related to the following entity
312 */
313 Scene.prototype.updateFollow = function () {
314 if (this.props.follow) {
315 var follow = this.props.follow, target = follow.target;
316 if (!target.killed) {
317 this.props.x = target.props.x + (follow.centered ? (target.props.width / 2) - (this.props.width / 2) : 0) - follow.offsetX;
318 this.props.y = target.props.y + (follow.centered ? (target.props.height / 2) - (this.props.height / 2) : 0) - follow.offsetY;
319 }
320 else {
321 this.props.follow = null;
322 }
323 }
324 };
325 /**
326 * When "gravity" property change
327 */
328 Scene.prototype.onGravityChange = function () {
329 if (this.world) {
330 this.world.gravity = [0, Tool_1.Util.pixelToMeter(this.props.gravity)];
331 }
332 };
333 /**
334 * When backgrounds attributes has changed
335 */
336 Scene.prototype.onBackgroundChange = function () {
337 var _a = this.props, backgroundColor = _a.backgroundColor, backgroundAlpha = _a.backgroundAlpha;
338 if (!this._background && backgroundColor && backgroundColor !== Tool_1.Color.transparent) {
339 this._background = this.add(new Module_1.Shape(), {
340 stroke: Tool_1.Color.transparent,
341 width: this.props.width,
342 height: this.props.height,
343 fill: backgroundColor,
344 fillAlpha: backgroundAlpha
345 }, 0);
346 }
347 else if (this._background && (!backgroundColor || backgroundColor === Tool_1.Color.transparent)) {
348 this._background.kill();
349 this._background = null;
350 }
351 else if (this._background) {
352 this._background.props.fill = backgroundColor;
353 this._background.props.fillAlpha = backgroundAlpha;
354 }
355 };
356 /**
357 * Event trigger by the game when all assets are loaded
358 * @param done - Function to call to end the loading
359 */
360 Scene.prototype.onAssetsLoaded = function (done) {
361 done();
362 };
363 /* PRIVATE */
364 /**
365 * p2 World presolving
366 * @param contactEquations - The contactEquations of p2 (see p2.js)
367 * @private
368 */
369 Scene.prototype._onPreSolve = function (_a) {
370 var _this = this;
371 var contactEquations = _a.contactEquations;
372 contactEquations.forEach(function (contactEquation) {
373 var ownerA = _this.getPhysicOwnerByBody(contactEquation.bodyA), ownerB = _this.getPhysicOwnerByBody(contactEquation.bodyB), wallA = (ownerA instanceof Entity_1.Wall) && ownerA, wallB = (ownerB instanceof Entity_1.Wall) && ownerB, entityA = ownerA && (wallA ? false : ownerA), entityB = ownerB && (wallB ? false : ownerB);
374 if ((entityA && entityA.props.type === Tool_1.Enum.TYPE.GHOST) || (entityB && entityB.props.type === Tool_1.Enum.TYPE.GHOST)) {
375 contactEquation.enabled = false;
376 }
377 else if ((!wallA && wallB) || (wallA && !wallB)) {
378 var wall = wallA || wallB, entity = wallA ? entityB : entityA;
379 if (entity) {
380 contactEquation.enabled = !wall.isConstrainedByDirection(entity);
381 }
382 }
383 });
384 };
385 /**
386 * p2 JS event when two shapes starts to overlap
387 * @param bodyA: Body A entered in collision
388 * @param bodyB: Body B entered in collision
389 * @private
390 */
391 Scene.prototype._onBeginContact = function (_a) {
392 var bodyA = _a.bodyA, bodyB = _a.bodyB;
393 var contact = this._resolveContact(bodyA, bodyB), entityA = contact.entityA, entityB = contact.entityB;
394 if (entityA && contact.contactA) {
395 entityA.collides.push(contact.contactA);
396 }
397 if (entityB && contact.contactB) {
398 entityB.collides.push(contact.contactB);
399 }
400 if (entityA instanceof Entity_1.Wall) {
401 entityB.signals.wallCollision.dispatch();
402 }
403 else if (entityB instanceof Entity_1.Wall) {
404 entityA.signals.wallCollision.dispatch();
405 }
406 else if (entityA && entityB) {
407 contact.entityA.signals.beginCollision.dispatch(contact.entityB.name, contact.entityB);
408 contact.entityB.signals.beginCollision.dispatch(contact.entityA.name, contact.entityA);
409 }
410 };
411 /**
412 * p2 JS event when two shapes ends to overlap
413 * @param bodyA - Body A entered in collision
414 * @param bodyB - Body B entered in collision
415 * @private
416 */
417 Scene.prototype._onEndContact = function (_a) {
418 var bodyA = _a.bodyA, bodyB = _a.bodyB;
419 var contact = this._resolveContact(bodyA, bodyB);
420 if (!contact) {
421 return null;
422 }
423 var entityA = contact.entityA, entityB = contact.entityB;
424 if (entityA) {
425 entityA.collides = entityA.collides.filter(function (collide) { return collide.bodyId !== contact.contactA.bodyId; });
426 }
427 if (entityB) {
428 entityB.collides = entityB.collides.filter(function (collide) { return collide.bodyId !== contact.contactB.bodyId; });
429 }
430 if (entityA && entityB) {
431 entityA.signals.endCollision.dispatch(entityB.name, entityB);
432 entityB.signals.endCollision.dispatch(entityA.name, entityA);
433 this.physics = this.physics.filter(function (physic) { return physic.enabled; });
434 }
435 };
436 /**
437 * p2 JS event when two shapes is overlaping
438 * @private
439 * @param bodyA - Body A entered in collision
440 * @param bodyB - Body B entered in collision
441 * @returns Contact resolver
442 */
443 Scene.prototype._resolveContact = function (bodyA, bodyB) {
444 var ownerA = this.getPhysicOwnerByBody(bodyA), ownerB = this.getPhysicOwnerByBody(bodyB);
445 var contactA = null, contactB = null;
446 var isAbove = function (xA, yA, widthA, xB, yB, widthB) { return yB >= yA && (xA > xB - widthA) && (xA < xB + widthB); };
447 switch (true) {
448 case Boolean(ownerB instanceof Entity_1.Wall) && Boolean(ownerA):
449 contactA = { bodyId: bodyB.id, isAbove: isAbove(ownerA.props.x, ownerA.props.y, ownerA.props.height, ownerB.props.x, ownerB.props.y, ownerB.props.width) };
450 break;
451 case Boolean(ownerA instanceof Entity_1.Wall) && Boolean(ownerB):
452 contactB = { bodyId: bodyA.id, isAbove: isAbove(ownerB.props.x, ownerB.props.y, ownerB.props.height, ownerA.props.x, ownerA.props.y, ownerA.props.width) };
453 break;
454 case Boolean(ownerA) && Boolean(ownerB):
455 contactA = { bodyId: bodyB.id, entity: ownerB, isAbove: isAbove(ownerA.props.x, ownerA.props.y, ownerA.props.height, ownerB.props.x, ownerB.props.y, ownerB.props.width) };
456 contactB = { bodyId: bodyA.id, entity: ownerA, isAbove: isAbove(ownerB.props.x, ownerB.props.y, ownerB.props.height, ownerA.props.x, ownerA.props.y, ownerA.props.width) };
457 break;
458 }
459 return (contactA || contactB) && { entityA: (ownerA instanceof Entity_1.Entity) && ownerA, entityB: (ownerB instanceof Entity_1.Entity) && ownerB, contactA: contactA, contactB: contactB };
460 };
461 return Scene;
462}(Module_1.Module));
463exports.Scene = Scene;