UNPKG

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