-
-
Notifications
You must be signed in to change notification settings - Fork 189
/
Scene.ts
556 lines (494 loc) · 18.1 KB
/
Scene.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
import { isScreenElement, ScreenElement } from './ScreenElement';
import {
InitializeEvent,
ActivateEvent,
DeactivateEvent,
PreUpdateEvent,
PostUpdateEvent,
PreDrawEvent,
PostDrawEvent,
PreDebugDrawEvent,
PostDebugDrawEvent,
GameEvent
} from './Events';
import { Logger } from './Util/Log';
import { Timer } from './Timer';
import { Engine } from './Engine';
import { TileMap } from './TileMap';
import { Camera } from './Camera';
import { Actor } from './Actor';
import { Class } from './Class';
import { CanInitialize, CanActivate, CanDeactivate, CanUpdate, CanDraw } from './Interfaces/LifecycleEvents';
import * as Util from './Util/Util';
import * as Events from './Events';
import { Trigger } from './Trigger';
import { SystemType } from './EntityComponentSystem/System';
import { World } from './EntityComponentSystem/World';
import { MotionSystem } from './Collision/MotionSystem';
import { CollisionSystem } from './Collision/CollisionSystem';
import { Entity } from './EntityComponentSystem/Entity';
import { GraphicsSystem } from './Graphics/GraphicsSystem';
import { DebugSystem } from './Debug/DebugSystem';
import { PointerSystem } from './Input/PointerSystem';
import { ActionsSystem } from './Actions/ActionsSystem';
import { IsometricEntitySystem } from './TileMap/IsometricEntitySystem';
import { OffscreenSystem } from './Graphics/OffscreenSystem';
import { ExcaliburGraphicsContext } from './Graphics';
/**
* [[Actor|Actors]] are composed together into groupings called Scenes in
* Excalibur. The metaphor models the same idea behind real world
* actors in a scene. Only actors in scenes will be updated and drawn.
*
* Typical usages of a scene include: levels, menus, loading screens, etc.
*/
export class Scene extends Class implements CanInitialize, CanActivate, CanDeactivate, CanUpdate, CanDraw {
private _logger: Logger = Logger.getInstance();
/**
* Gets or sets the current camera for the scene
*/
public camera: Camera = new Camera();
/**
* The ECS world for the scene
*/
public world = new World(this);
/**
* The actors in the current scene
*/
public get actors(): readonly Actor[] {
return this.world.entityManager.entities.filter((e) => {
return e instanceof Actor;
}) as Actor[];
}
/**
* The entities in the current scene
*/
public get entities(): readonly Entity[] {
return this.world.entityManager.entities;
}
/**
* The triggers in the current scene
*/
public get triggers(): readonly Trigger[] {
return this.world.entityManager.entities.filter((e) => {
return e instanceof Trigger;
}) as Trigger[];
}
/**
* The [[TileMap]]s in the scene, if any
*/
public get tileMaps(): readonly TileMap[] {
return this.world.entityManager.entities.filter((e) => {
return e instanceof TileMap;
}) as TileMap[];
}
/**
* Access to the Excalibur engine
*/
public engine: Engine;
private _isInitialized: boolean = false;
private _timers: Timer[] = [];
public get timers(): readonly Timer[] {
return this._timers;
}
private _cancelQueue: Timer[] = [];
constructor() {
super();
// Initialize systems
// Update
this.world.add(new ActionsSystem());
this.world.add(new MotionSystem());
this.world.add(new CollisionSystem());
this.world.add(new PointerSystem());
this.world.add(new IsometricEntitySystem());
// Draw
this.world.add(new OffscreenSystem());
this.world.add(new GraphicsSystem());
this.world.add(new DebugSystem());
}
public on(eventName: Events.initialize, handler: (event: InitializeEvent<Scene>) => void): void;
public on(eventName: Events.activate, handler: (event: ActivateEvent) => void): void;
public on(eventName: Events.deactivate, handler: (event: DeactivateEvent) => void): void;
public on(eventName: Events.preupdate, handler: (event: PreUpdateEvent<Scene>) => void): void;
public on(eventName: Events.postupdate, handler: (event: PostUpdateEvent<Scene>) => void): void;
public on(eventName: Events.predraw, handler: (event: PreDrawEvent) => void): void;
public on(eventName: Events.postdraw, handler: (event: PostDrawEvent) => void): void;
public on(eventName: Events.predebugdraw, handler: (event: PreDebugDrawEvent) => void): void;
public on(eventName: Events.postdebugdraw, handler: (event: PostDebugDrawEvent) => void): void;
public on(eventName: string, handler: (event: GameEvent<any>) => void): void;
public on(eventName: string, handler: (event: any) => void): void {
super.on(eventName, handler);
}
public once(eventName: Events.initialize, handler: (event: InitializeEvent<Scene>) => void): void;
public once(eventName: Events.activate, handler: (event: ActivateEvent) => void): void;
public once(eventName: Events.deactivate, handler: (event: DeactivateEvent) => void): void;
public once(eventName: Events.preupdate, handler: (event: PreUpdateEvent<Scene>) => void): void;
public once(eventName: Events.postupdate, handler: (event: PostUpdateEvent<Scene>) => void): void;
public once(eventName: Events.predraw, handler: (event: PreDrawEvent) => void): void;
public once(eventName: Events.postdraw, handler: (event: PostDrawEvent) => void): void;
public once(eventName: Events.predebugdraw, handler: (event: PreDebugDrawEvent) => void): void;
public once(eventName: Events.postdebugdraw, handler: (event: PostDebugDrawEvent) => void): void;
public once(eventName: string, handler: (event: GameEvent<any>) => void): void;
public once(eventName: string, handler: (event: any) => void): void {
super.once(eventName, handler);
}
public off(eventName: Events.initialize, handler?: (event: InitializeEvent<Scene>) => void): void;
public off(eventName: Events.activate, handler?: (event: ActivateEvent) => void): void;
public off(eventName: Events.deactivate, handler?: (event: DeactivateEvent) => void): void;
public off(eventName: Events.preupdate, handler?: (event: PreUpdateEvent<Scene>) => void): void;
public off(eventName: Events.postupdate, handler?: (event: PostUpdateEvent<Scene>) => void): void;
public off(eventName: Events.predraw, handler?: (event: PreDrawEvent) => void): void;
public off(eventName: Events.postdraw, handler?: (event: PostDrawEvent) => void): void;
public off(eventName: Events.predebugdraw, handler?: (event: PreDebugDrawEvent) => void): void;
public off(eventName: Events.postdebugdraw, handler?: (event: PostDebugDrawEvent) => void): void;
public off(eventName: string, handler?: (event: GameEvent<any>) => void): void;
public off(eventName: string, handler?: (event: any) => void): void {
super.off(eventName, handler);
}
/**
* This is called before the first update of the [[Scene]]. Initializes scene members like the camera. This method is meant to be
* overridden. This is where initialization of child actors should take place.
*/
public onInitialize(_engine: Engine): void {
// will be overridden
}
/**
* This is called when the scene is made active and started. It is meant to be overridden,
* this is where you should setup any DOM UI or event handlers needed for the scene.
*/
public onActivate(_oldScene: Scene, _newScene: Scene): void {
// will be overridden
}
/**
* This is called when the scene is made transitioned away from and stopped. It is meant to be overridden,
* this is where you should cleanup any DOM UI or event handlers needed for the scene.
*/
public onDeactivate(_oldScene: Scene, _newScene: Scene): void {
// will be overridden
}
/**
* Safe to override onPreUpdate lifecycle event handler. Synonymous with `.on('preupdate', (evt) =>{...})`
*
* `onPreUpdate` is called directly before a scene is updated.
*/
public onPreUpdate(_engine: Engine, _delta: number): void {
// will be overridden
}
/**
* Safe to override onPostUpdate lifecycle event handler. Synonymous with `.on('preupdate', (evt) =>{...})`
*
* `onPostUpdate` is called directly after a scene is updated.
*/
public onPostUpdate(_engine: Engine, _delta: number): void {
// will be overridden
}
/**
* Safe to override onPreDraw lifecycle event handler. Synonymous with `.on('preupdate', (evt) =>{...})`
*
* `onPreDraw` is called directly before a scene is drawn.
*
*/
public onPreDraw(_ctx: ExcaliburGraphicsContext, _delta: number): void {
// will be overridden
}
/**
* Safe to override onPostDraw lifecycle event handler. Synonymous with `.on('preupdate', (evt) =>{...})`
*
* `onPostDraw` is called directly after a scene is drawn.
*
*/
public onPostDraw(_ctx: ExcaliburGraphicsContext, _delta: number): void {
// will be overridden
}
/**
* Initializes actors in the scene
*/
private _initializeChildren(): void {
for (const child of this.entities) {
child._initialize(this.engine);
}
}
/**
* Gets whether or not the [[Scene]] has been initialized
*/
public get isInitialized(): boolean {
return this._isInitialized;
}
/**
* It is not recommended that internal excalibur methods be overridden, do so at your own risk.
*
* Initializes the scene before the first update, meant to be called by engine not by users of
* Excalibur
* @internal
*/
public _initialize(engine: Engine) {
if (!this.isInitialized) {
this.engine = engine;
// Initialize camera first
this.camera._initialize(engine);
this.world.systemManager.initialize();
// This order is important! we want to be sure any custom init that add actors
// fire before the actor init
this.onInitialize.call(this, engine);
this._initializeChildren();
this._logger.debug('Scene.onInitialize', this, engine);
this.eventDispatcher.emit('initialize', new InitializeEvent(engine, this));
this._isInitialized = true;
}
}
/**
* It is not recommended that internal excalibur methods be overridden, do so at your own risk.
*
* Activates the scene with the base behavior, then calls the overridable `onActivate` implementation.
* @internal
*/
public _activate(oldScene: Scene, newScene: Scene): void {
this._logger.debug('Scene.onActivate', this);
this.onActivate(oldScene, newScene);
}
/**
* It is not recommended that internal excalibur methods be overridden, do so at your own risk.
*
* Deactivates the scene with the base behavior, then calls the overridable `onDeactivate` implementation.
* @internal
*/
public _deactivate(oldScene: Scene, newScene: Scene): void {
this._logger.debug('Scene.onDeactivate', this);
this.onDeactivate(oldScene, newScene);
}
/**
* It is not recommended that internal excalibur methods be overridden, do so at your own risk.
*
* Internal _preupdate handler for [[onPreUpdate]] lifecycle event
* @internal
*/
public _preupdate(_engine: Engine, delta: number): void {
this.emit('preupdate', new PreUpdateEvent(_engine, delta, this));
this.onPreUpdate(_engine, delta);
}
/**
* It is not recommended that internal excalibur methods be overridden, do so at your own risk.
*
* Internal _preupdate handler for [[onPostUpdate]] lifecycle event
* @internal
*/
public _postupdate(_engine: Engine, delta: number): void {
this.emit('postupdate', new PostUpdateEvent(_engine, delta, this));
this.onPostUpdate(_engine, delta);
}
/**
* It is not recommended that internal excalibur methods be overridden, do so at your own risk.
*
* Internal _predraw handler for [[onPreDraw]] lifecycle event
*
* @internal
*/
public _predraw(_ctx: ExcaliburGraphicsContext, _delta: number): void {
this.emit('predraw', new PreDrawEvent(_ctx, _delta, this));
this.onPreDraw(_ctx, _delta);
}
/**
* It is not recommended that internal excalibur methods be overridden, do so at your own risk.
*
* Internal _postdraw handler for [[onPostDraw]] lifecycle event
*
* @internal
*/
public _postdraw(_ctx: ExcaliburGraphicsContext, _delta: number): void {
this.emit('postdraw', new PostDrawEvent(_ctx, _delta, this));
this.onPostDraw(_ctx, _delta);
}
/**
* Updates all the actors and timers in the scene. Called by the [[Engine]].
* @param engine Reference to the current Engine
* @param delta The number of milliseconds since the last update
*/
public update(engine: Engine, delta: number) {
this._preupdate(engine, delta);
// TODO differed entity removal for timers
let i: number, len: number;
// Remove timers in the cancel queue before updating them
for (i = 0, len = this._cancelQueue.length; i < len; i++) {
this.removeTimer(this._cancelQueue[i]);
}
this._cancelQueue.length = 0;
// Cycle through timers updating timers
for (const timer of this._timers) {
timer.update(delta);
}
this.world.update(SystemType.Update, delta);
// Camera last keeps renders smooth that are based on entity/actor
if (this.camera) {
this.camera.update(engine, delta);
}
this._collectActorStats(engine);
this._postupdate(engine, delta);
}
/**
* Draws all the actors in the Scene. Called by the [[Engine]].
*
* @param ctx The current rendering context
* @param delta The number of milliseconds since the last draw
*/
public draw(ctx: ExcaliburGraphicsContext, delta: number) {
this._predraw(ctx, delta);
this.world.update(SystemType.Draw, delta);
if (this.engine?.isDebug) {
this.debugDraw(ctx);
}
this._postdraw(ctx, delta);
}
/**
* Draws all the actors' debug information in the Scene. Called by the [[Engine]].
* @param ctx The current rendering context
*/
/* istanbul ignore next */
public debugDraw(ctx: ExcaliburGraphicsContext) {
this.emit('predebugdraw', new PreDebugDrawEvent(ctx, this));
// pass
this.emit('postdebugdraw', new PostDebugDrawEvent(ctx, this));
}
/**
* Checks whether an actor is contained in this scene or not
*/
public contains(actor: Actor): boolean {
return this.actors.indexOf(actor) > -1;
}
/**
* Adds a [[Timer]] to the current [[Scene]].
* @param timer The timer to add to the current [[Scene]].
*/
public add(timer: Timer): void;
/**
* Adds a [[TileMap]] to the [[Scene]], once this is done the [[TileMap]] will be drawn and updated.
*/
public add(tileMap: TileMap): void;
/**
* Adds a [[Trigger]] to the [[Scene]], once this is done the [[Trigger]] will listen for interactions with other actors.
* @param trigger
*/
public add(trigger: Trigger): void;
/**
* Adds an actor to the scene, once this is done the [[Actor]] will be drawn and updated.
* @param actor The actor to add to the current scene
*/
public add(actor: Actor): void;
public add(entity: Entity): void;
/**
* Adds a [[ScreenElement]] to the scene.
* @param screenElement The ScreenElement to add to the current scene
*/
public add(screenElement: ScreenElement): void;
public add(entity: any): void {
this.emit('entityadded', { target: entity } as any);
this.world.add(entity);
entity.scene = this;
if (entity instanceof Timer) {
if (!Util.contains(this._timers, entity)) {
this.addTimer(entity);
}
return;
}
}
/**
* Removes a [[Timer]] from the current scene, it will no longer be updated.
* @param timer The timer to remove to the current scene.
*/
public remove(timer: Timer): void;
/**
* Removes a [[TileMap]] from the scene, it will no longer be drawn or updated.
* @param tileMap {TileMap}
*/
public remove(tileMap: TileMap): void;
/**
* Removes an actor from the scene, it will no longer be drawn or updated.
* @param actor The actor to remove from the current scene.
*/
public remove(actor: Actor): void;
public remove(entity: Entity): void;
/**
* Removes a [[ScreenElement]] to the scene, it will no longer be drawn or updated
* @param screenElement The ScreenElement to remove from the current scene
*/
public remove(screenElement: ScreenElement): void;
public remove(entity: any): void {
if (entity instanceof Entity) {
this.emit('entityremoved', { target: entity } as any);
this.world.remove(entity);
}
if (entity instanceof Timer) {
this.removeTimer(entity);
}
}
/**
* Removes all entities and timers from the scene, optionally indicate whether deferred should or shouldn't be used.
*
* By default entities use deferred removal
* @param deferred
*/
public clear(deferred: boolean = true): void {
for (const entity of this.entities) {
this.world.remove(entity, deferred);
}
for (const timer of this.timers) {
this.removeTimer(timer);
}
}
/**
* Adds a [[Timer]] to the scene
* @param timer The timer to add
*/
public addTimer(timer: Timer): Timer {
this._timers.push(timer);
timer.scene = this;
return timer;
}
/**
* Removes a [[Timer]] from the scene.
* @warning Can be dangerous, use [[cancelTimer]] instead
* @param timer The timer to remove
*/
public removeTimer(timer: Timer): Timer {
const i = this._timers.indexOf(timer);
if (i !== -1) {
this._timers.splice(i, 1);
}
return timer;
}
/**
* Cancels a [[Timer]], removing it from the scene nicely
* @param timer The timer to cancel
*/
public cancelTimer(timer: Timer): Timer {
this._cancelQueue.push(timer);
return timer;
}
/**
* Tests whether a [[Timer]] is active in the scene
*/
public isTimerActive(timer: Timer): boolean {
return this._timers.indexOf(timer) > -1 && !timer.complete;
}
public isCurrentScene(): boolean {
if (this.engine) {
return this.engine.currentScene === this;
}
return false;
}
private _collectActorStats(engine: Engine) {
const screenElements = this.actors.filter((a) => a instanceof ScreenElement) as ScreenElement[];
for (const _ui of screenElements) {
engine.stats.currFrame.actors.ui++;
}
for (const actor of this.actors) {
engine.stats.currFrame.actors.alive++;
for (const child of actor.children) {
if (isScreenElement(child as Actor)) {
// TODO not true
engine.stats.currFrame.actors.ui++;
} else {
engine.stats.currFrame.actors.alive++;
}
}
}
}
}