• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

excaliburjs / Excalibur / 14804036802

02 May 2025 09:58PM UTC coverage: 5.927% (-83.4%) from 89.28%
14804036802

Pull #3404

github

web-flow
Merge 5c103d7f8 into 0f2ccaeb2
Pull Request #3404: feat: added Graph module to Math

234 of 8383 branches covered (2.79%)

229 of 246 new or added lines in 1 file covered. (93.09%)

13145 existing lines in 208 files now uncovered.

934 of 15759 relevant lines covered (5.93%)

4.72 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.72
/src/engine/Scene.ts
1
import { isScreenElement, ScreenElement } from './ScreenElement';
2
import {
3
  InitializeEvent,
4
  ActivateEvent,
5
  DeactivateEvent,
6
  PreUpdateEvent,
7
  PostUpdateEvent,
8
  PreDrawEvent,
9
  PostDrawEvent,
10
  PreDebugDrawEvent,
11
  PostDebugDrawEvent
12
} from './Events';
13
import { Logger } from './Util/Log';
14
import { Timer } from './Timer';
15
import { Engine } from './Engine';
16
import { TileMap } from './TileMap';
17
import { Camera } from './Camera';
18
import { Actor } from './Actor';
19
import { CanInitialize, CanActivate, CanDeactivate, CanUpdate, CanDraw, SceneActivationContext } from './Interfaces/LifecycleEvents';
20
import * as Util from './Util/Util';
21
import { Trigger } from './Trigger';
22
import { SystemType } from './EntityComponentSystem/System';
23
import { World } from './EntityComponentSystem/World';
24
import { MotionSystem } from './Collision/MotionSystem';
25
import { CollisionSystem } from './Collision/CollisionSystem';
26
import { Entity } from './EntityComponentSystem/Entity';
27
import { GraphicsSystem } from './Graphics/GraphicsSystem';
28
import { DebugSystem } from './Debug/DebugSystem';
29
import { PointerSystem } from './Input/PointerSystem';
30
import { ActionsSystem } from './Actions/ActionsSystem';
31
import { IsometricEntitySystem } from './TileMap/IsometricEntitySystem';
32
import { OffscreenSystem } from './Graphics/OffscreenSystem';
33
import { ExcaliburGraphicsContext } from './Graphics';
34
import { PhysicsWorld } from './Collision/PhysicsWorld';
35
import { EventEmitter, EventKey, Handler, Subscription } from './EventEmitter';
36
import { Color } from './Color';
37
import { DefaultLoader } from './Director/DefaultLoader';
38
import { Transition } from './Director';
39
import { InputHost } from './Input/InputHost';
40
import { PointerScope } from './Input/PointerScope';
41
import { getDefaultPhysicsConfig } from './Collision/PhysicsConfig';
42

43
export class PreLoadEvent {
44
  loader: DefaultLoader;
45
}
46

47
export type SceneEvents = {
48
  initialize: InitializeEvent<Scene>;
49
  activate: ActivateEvent;
50
  deactivate: DeactivateEvent;
51
  preupdate: PreUpdateEvent;
52
  postupdate: PostUpdateEvent;
53
  predraw: PreDrawEvent;
54
  postdraw: PostDrawEvent;
55
  predebugdraw: PreDebugDrawEvent;
56
  postdebugdraw: PostDebugDrawEvent;
57
  preload: PreLoadEvent;
58
  transitionstart: Transition;
59
  transitionend: Transition;
60
};
61

62
export const SceneEvents = {
1✔
63
  Initialize: 'initialize',
64
  Activate: 'activate',
65
  Deactivate: 'deactivate',
66
  PreUpdate: 'preupdate',
67
  PostUpdate: 'postupdate',
68
  PreDraw: 'predraw',
69
  PostDraw: 'postdraw',
70
  PreDebugDraw: 'predebugdraw',
71
  PostDebugDraw: 'postdebugdraw',
72
  PreLoad: 'preload',
73
  TransitionStart: 'transitionstart',
74
  TransitionEnd: 'transitionend'
75
} as const;
76

77
export type SceneConstructor = new (...args: any[]) => Scene;
78
/**
79
 *
80
 */
81
export function isSceneConstructor(x: any): x is SceneConstructor {
UNCOV
82
  return !!x?.prototype && !!x?.prototype?.constructor?.name;
×
83
}
84

85
/**
86
 * {@apilink Actor | `Actors`} are composed together into groupings called Scenes in
87
 * Excalibur. The metaphor models the same idea behind real world
88
 * actors in a scene. Only actors in scenes will be updated and drawn.
89
 *
90
 * Typical usages of a scene include: levels, menus, loading screens, etc.
91
 */
92
export class Scene<TActivationData = unknown> implements CanInitialize, CanActivate<TActivationData>, CanDeactivate, CanUpdate, CanDraw {
UNCOV
93
  private _logger: Logger = Logger.getInstance();
×
UNCOV
94
  public events = new EventEmitter<SceneEvents>();
×
95

96
  /**
97
   * Gets or sets the current camera for the scene
98
   */
UNCOV
99
  public camera: Camera = new Camera();
×
100

101
  /**
102
   * Scene specific background color
103
   */
104
  public backgroundColor?: Color;
105

106
  /**
107
   * The ECS world for the scene
108
   */
UNCOV
109
  public world: World = new World(this);
×
110

111
  /**
112
   * The Excalibur physics world for the scene. Used to interact
113
   * with colliders included in the scene.
114
   *
115
   * Can be used to perform scene ray casts, track colliders, broadphase, and narrowphase.
116
   */
UNCOV
117
  public physics = new PhysicsWorld(getDefaultPhysicsConfig());
×
118

119
  /**
120
   * The actors in the current scene
121
   */
122
  public get actors(): readonly Actor[] {
UNCOV
123
    return this.world.entityManager.entities.filter((e: Entity<any>) => {
×
UNCOV
124
      return e instanceof Actor;
×
125
    }) as Actor[];
126
  }
127

128
  /**
129
   * The entities in the current scene
130
   */
131
  public get entities(): readonly Entity[] {
UNCOV
132
    return this.world.entityManager.entities;
×
133
  }
134

135
  /**
136
   * The triggers in the current scene
137
   */
138
  public get triggers(): readonly Trigger[] {
139
    return this.world.entityManager.entities.filter((e: Entity<any>) => {
×
140
      return e instanceof Trigger;
×
141
    }) as Trigger[];
142
  }
143

144
  /**
145
   * The {@apilink TileMap}s in the scene, if any
146
   */
147
  public get tileMaps(): readonly TileMap[] {
UNCOV
148
    return this.world.entityManager.entities.filter((e: Entity<any>) => {
×
UNCOV
149
      return e instanceof TileMap;
×
150
    }) as TileMap[];
151
  }
152

153
  /**
154
   * Access to the Excalibur engine
155
   */
156
  public engine: Engine;
157

158
  /**
159
   * Access scene specific input, handlers on this only fire when this scene is active.
160
   */
161
  public input: InputHost;
162

UNCOV
163
  private _isInitialized: boolean = false;
×
UNCOV
164
  private _timers: Timer[] = [];
×
165
  public get timers(): readonly Timer[] {
UNCOV
166
    return this._timers;
×
167
  }
UNCOV
168
  private _cancelQueue: Timer[] = [];
×
169

170
  constructor() {
171
    // Initialize systems
172

173
    // Update
UNCOV
174
    this.world.add(ActionsSystem);
×
UNCOV
175
    this.world.add(new MotionSystem(this.world, this.physics));
×
UNCOV
176
    this.world.add(new CollisionSystem(this.world, this.physics));
×
UNCOV
177
    this.world.add(PointerSystem);
×
UNCOV
178
    this.world.add(IsometricEntitySystem);
×
179
    // Draw
UNCOV
180
    this.world.add(OffscreenSystem);
×
UNCOV
181
    this.world.add(GraphicsSystem);
×
UNCOV
182
    this.world.add(DebugSystem);
×
183
  }
184

185
  public emit<TEventName extends EventKey<SceneEvents>>(eventName: TEventName, event: SceneEvents[TEventName]): void;
186
  public emit(eventName: string, event?: any): void;
187
  public emit<TEventName extends EventKey<SceneEvents> | string>(eventName: TEventName, event?: any): void {
UNCOV
188
    this.events.emit(eventName, event);
×
189
  }
190

191
  public on<TEventName extends EventKey<SceneEvents>>(eventName: TEventName, handler: Handler<SceneEvents[TEventName]>): Subscription;
192
  public on(eventName: string, handler: Handler<unknown>): Subscription;
193
  public on<TEventName extends EventKey<SceneEvents> | string>(eventName: TEventName, handler: Handler<any>): Subscription {
UNCOV
194
    return this.events.on(eventName, handler);
×
195
  }
196

197
  public once<TEventName extends EventKey<SceneEvents>>(eventName: TEventName, handler: Handler<SceneEvents[TEventName]>): Subscription;
198
  public once(eventName: string, handler: Handler<unknown>): Subscription;
199
  public once<TEventName extends EventKey<SceneEvents> | string>(eventName: TEventName, handler: Handler<any>): Subscription {
UNCOV
200
    return this.events.once(eventName, handler);
×
201
  }
202

203
  public off<TEventName extends EventKey<SceneEvents>>(eventName: TEventName, handler: Handler<SceneEvents[TEventName]>): void;
204
  public off(eventName: string, handler: Handler<unknown>): void;
205
  public off(eventName: string): void;
206
  public off<TEventName extends EventKey<SceneEvents> | string>(eventName: TEventName, handler?: Handler<any>): void {
207
    this.events.off(eventName, handler);
×
208
  }
209

210
  /**
211
   * Event hook to provide Scenes a way of loading scene specific resources.
212
   *
213
   * This is called before the Scene.onInitialize during scene transition. It will only ever fire once for a scene.
214
   * @param loader
215
   */
216
  public onPreLoad(loader: DefaultLoader) {
217
    // will be overridden
218
  }
219

220
  /**
221
   * Event hook fired directly before transition, either "in" or "out" of the scene
222
   *
223
   * This overrides the Engine scene definition. However transitions specified in goToScene take highest precedence
224
   *
225
   * ```typescript
226
   * // Overrides all
227
   * Engine.goToScene('scene', { destinationIn: ..., sourceOut: ... });
228
   * ```
229
   *
230
   * This can be used to configure custom transitions for a scene dynamically
231
   */
232
  public onTransition(direction: 'in' | 'out'): Transition | undefined {
233
    // will be overridden
UNCOV
234
    return undefined;
×
235
  }
236

237
  /**
238
   * This is called before the first update of the {@apilink Scene}. Initializes scene members like the camera. This method is meant to be
239
   * overridden. This is where initialization of child actors should take place.
240
   */
241
  public onInitialize(engine: Engine): void {
242
    // will be overridden
243
  }
244

245
  /**
246
   * This is called when the scene is made active and started. It is meant to be overridden,
247
   * this is where you should setup any DOM UI or event handlers needed for the scene.
248
   */
249
  public onActivate(context: SceneActivationContext<TActivationData>): void {
250
    // will be overridden
251
  }
252

253
  /**
254
   * This is called when the scene is made transitioned away from and stopped. It is meant to be overridden,
255
   * this is where you should cleanup any DOM UI or event handlers needed for the scene.
256
   */
257
  public onDeactivate(context: SceneActivationContext): void {
258
    // will be overridden
259
  }
260

261
  /**
262
   * Safe to override onPreUpdate lifecycle event handler. Synonymous with `.on('preupdate', (evt) =>{...})`
263
   *
264
   * `onPreUpdate` is called directly before a scene is updated.
265
   * @param engine reference to the engine
266
   * @param elapsed  Number of milliseconds elapsed since the last draw.
267
   */
268
  public onPreUpdate(engine: Engine, elapsed: number): void {
269
    // will be overridden
270
  }
271

272
  /**
273
   * Safe to override onPostUpdate lifecycle event handler. Synonymous with `.on('preupdate', (evt) =>{...})`
274
   *
275
   * `onPostUpdate` is called directly after a scene is updated.
276
   * @param engine reference to the engine
277
   * @param elapsed  Number of milliseconds elapsed since the last draw.
278
   */
279
  public onPostUpdate(engine: Engine, elapsed: number): void {
280
    // will be overridden
281
  }
282

283
  /**
284
   * Safe to override onPreDraw lifecycle event handler. Synonymous with `.on('preupdate', (evt) =>{...})`
285
   *
286
   * `onPreDraw` is called directly before a scene is drawn.
287
   *
288
   */
289
  public onPreDraw(ctx: ExcaliburGraphicsContext, elapsed: number): void {
290
    // will be overridden
291
  }
292

293
  /**
294
   * Safe to override onPostDraw lifecycle event handler. Synonymous with `.on('preupdate', (evt) =>{...})`
295
   *
296
   * `onPostDraw` is called directly after a scene is drawn.
297
   *
298
   */
299
  public onPostDraw(ctx: ExcaliburGraphicsContext, elapsed: number): void {
300
    // will be overridden
301
  }
302

303
  /**
304
   * Initializes actors in the scene
305
   */
306
  private _initializeChildren() {
UNCOV
307
    for (const child of this.entities) {
×
UNCOV
308
      child._initialize(this.engine);
×
309
    }
310
  }
311

312
  /**
313
   * Gets whether or not the {@apilink Scene} has been initialized
314
   */
315
  public get isInitialized(): boolean {
UNCOV
316
    return this._isInitialized;
×
317
  }
318

319
  /**
320
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
321
   *
322
   * Initializes the scene before the first update, meant to be called by engine not by users of
323
   * Excalibur
324
   * @internal
325
   */
326
  public async _initialize(engine: Engine) {
UNCOV
327
    if (!this.isInitialized) {
×
UNCOV
328
      try {
×
UNCOV
329
        this.engine = engine;
×
330
        // PhysicsWorld config is watched so things will automagically update
UNCOV
331
        this.physics.config = this.engine.physics;
×
UNCOV
332
        this.input = new InputHost({
×
333
          pointerTarget: engine.pointerScope === PointerScope.Canvas ? engine.canvas : document,
×
334
          grabWindowFocus: engine.grabWindowFocus,
335
          engine
336
        });
337
        // Initialize camera first
UNCOV
338
        this.camera._initialize(engine);
×
339

UNCOV
340
        this.world.systemManager.initialize();
×
341

342
        // This order is important! we want to be sure any custom init that add actors
343
        // fire before the actor init
UNCOV
344
        await this.onInitialize(engine);
×
UNCOV
345
        this._initializeChildren();
×
346

UNCOV
347
        this._logger.debug('Scene.onInitialize', this, engine);
×
UNCOV
348
        this.events.emit('initialize', new InitializeEvent(engine, this));
×
349
      } catch (e) {
350
        this._logger.error(`Error during scene initialization for scene ${engine.director?.getSceneName(this)}!`);
×
351
        throw e;
×
352
      }
UNCOV
353
      this._isInitialized = true;
×
354
    }
355
  }
356

357
  /**
358
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
359
   *
360
   * Activates the scene with the base behavior, then calls the overridable `onActivate` implementation.
361
   * @internal
362
   */
363
  public async _activate(context: SceneActivationContext<TActivationData>) {
UNCOV
364
    try {
×
UNCOV
365
      this._logger.debug('Scene.onActivate', this);
×
UNCOV
366
      this.input.toggleEnabled(true);
×
UNCOV
367
      await this.onActivate(context);
×
368
    } catch (e) {
369
      this._logger.error(`Error during scene activation for scene ${this.engine?.director?.getSceneName(this)}!`);
×
370
      throw e;
×
371
    }
372
  }
373

374
  /**
375
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
376
   *
377
   * Deactivates the scene with the base behavior, then calls the overridable `onDeactivate` implementation.
378
   * @internal
379
   */
380
  public async _deactivate(context: SceneActivationContext<never>) {
UNCOV
381
    this._logger.debug('Scene.onDeactivate', this);
×
UNCOV
382
    this.input.toggleEnabled(false);
×
UNCOV
383
    await this.onDeactivate(context);
×
384
  }
385

386
  /**
387
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
388
   *
389
   * Internal _preupdate handler for {@apilink onPreUpdate} lifecycle event
390
   * @internal
391
   */
392
  public _preupdate(engine: Engine, elapsed: number): void {
UNCOV
393
    this.emit('preupdate', new PreUpdateEvent(engine, elapsed, this));
×
UNCOV
394
    this.onPreUpdate(engine, elapsed);
×
395
  }
396

397
  /**
398
   *  It is not recommended that internal excalibur methods be overridden, do so at your own risk.
399
   *
400
   * Internal _preupdate handler for {@apilink onPostUpdate} lifecycle event
401
   * @internal
402
   */
403
  public _postupdate(engine: Engine, elapsed: number): void {
UNCOV
404
    this.emit('postupdate', new PostUpdateEvent(engine, elapsed, this));
×
UNCOV
405
    this.onPostUpdate(engine, elapsed);
×
406
  }
407

408
  /**
409
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
410
   *
411
   * Internal _predraw handler for {@apilink onPreDraw} lifecycle event
412
   * @internal
413
   */
414
  public _predraw(ctx: ExcaliburGraphicsContext, elapsed: number): void {
UNCOV
415
    this.emit('predraw', new PreDrawEvent(ctx, elapsed, this));
×
UNCOV
416
    this.onPreDraw(ctx, elapsed);
×
417
  }
418

419
  /**
420
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
421
   *
422
   * Internal _postdraw handler for {@apilink onPostDraw} lifecycle event
423
   * @internal
424
   */
425
  public _postdraw(ctx: ExcaliburGraphicsContext, elapsed: number): void {
UNCOV
426
    this.emit('postdraw', new PostDrawEvent(ctx, elapsed, this));
×
UNCOV
427
    this.onPostDraw(ctx, elapsed);
×
428
  }
429

430
  /**
431
   * Updates all the actors and timers in the scene. Called by the {@apilink Engine}.
432
   * @param engine  Reference to the current Engine
433
   * @param elapsed   The number of milliseconds since the last update
434
   */
435
  public update(engine: Engine, elapsed: number) {
UNCOV
436
    if (!this.isInitialized) {
×
UNCOV
437
      this._logger.warnOnce(`Scene update called before initialize for scene ${engine.director?.getSceneName(this)}!`);
×
UNCOV
438
      return;
×
439
    }
UNCOV
440
    this._preupdate(engine, elapsed);
×
441

442
    // TODO differed entity removal for timers
443
    let i: number, len: number;
444
    // Remove timers in the cancel queue before updating them
UNCOV
445
    for (i = 0, len = this._cancelQueue.length; i < len; i++) {
×
UNCOV
446
      this.removeTimer(this._cancelQueue[i]);
×
447
    }
UNCOV
448
    this._cancelQueue.length = 0;
×
449

450
    // Cycle through timers updating timers
UNCOV
451
    for (const timer of this._timers) {
×
UNCOV
452
      timer.update(elapsed);
×
453
    }
454

UNCOV
455
    this.world.update(SystemType.Update, elapsed);
×
456

457
    // Camera last keeps renders smooth that are based on entity/actor
UNCOV
458
    if (this.camera) {
×
UNCOV
459
      this.camera.update(engine, elapsed);
×
460
    }
461

UNCOV
462
    this._collectActorStats(engine);
×
463

UNCOV
464
    this._postupdate(engine, elapsed);
×
465

UNCOV
466
    this.input.update();
×
467
  }
468

469
  /**
470
   * Draws all the actors in the Scene. Called by the {@apilink Engine}.
471
   * @param ctx    The current rendering context
472
   * @param elapsed  The number of milliseconds since the last draw
473
   */
474
  public draw(ctx: ExcaliburGraphicsContext, elapsed: number) {
UNCOV
475
    if (!this.isInitialized) {
×
UNCOV
476
      this._logger.warnOnce(`Scene draw called before initialize!`);
×
UNCOV
477
      return;
×
478
    }
UNCOV
479
    this._predraw(ctx, elapsed);
×
480

UNCOV
481
    this.world.update(SystemType.Draw, elapsed);
×
482

UNCOV
483
    if (this.engine?.isDebug) {
×
UNCOV
484
      this.debugDraw(ctx);
×
485
    }
UNCOV
486
    this._postdraw(ctx, elapsed);
×
487
  }
488

489
  /**
490
   * Draws all the actors' debug information in the Scene. Called by the {@apilink Engine}.
491
   * @param ctx  The current rendering context
492
   */
493
  /* istanbul ignore next */
494
  public debugDraw(ctx: ExcaliburGraphicsContext) {
495
    this.emit('predebugdraw', new PreDebugDrawEvent(ctx, this));
496
    // pass
497
    this.emit('postdebugdraw', new PostDebugDrawEvent(ctx, this));
498
  }
499

500
  /**
501
   * Checks whether an actor is contained in this scene or not
502
   */
503
  public contains(actor: Actor): boolean {
UNCOV
504
    return this.actors.indexOf(actor) > -1;
×
505
  }
506

507
  /**
508
   * Adds a {@apilink Timer} to the current {@apilink Scene}.
509
   * @param timer  The timer to add to the current {@apilink Scene}.
510
   */
511
  public add(timer: Timer): void;
512

513
  /**
514
   * Adds a {@apilink TileMap} to the {@apilink Scene}, once this is done the {@apilink TileMap} will be drawn and updated.
515
   */
516
  public add(tileMap: TileMap): void;
517

518
  /**
519
   * Adds a {@apilink Trigger} to the {@apilink Scene}, once this is done the {@apilink Trigger} will listen for interactions with other actors.
520
   * @param trigger
521
   */
522
  public add(trigger: Trigger): void;
523

524
  /**
525
   * Adds an actor to the scene, once this is done the {@apilink Actor} will be drawn and updated.
526
   * @param actor  The actor to add to the current scene
527
   */
528
  public add(actor: Actor): void;
529

530
  /**
531
   * Adds an {@apilink Entity} to the scene, once this is done the {@apilink Actor} will be drawn and updated.
532
   * @param entity The entity to add to the current scene
533
   */
534
  public add(entity: Entity): void;
535

536
  /**
537
   * Adds a {@apilink ScreenElement} to the scene.
538
   * @param screenElement  The ScreenElement to add to the current scene
539
   */
540
  public add(screenElement: ScreenElement): void;
541
  public add(entity: any): void {
UNCOV
542
    this.emit('entityadded', { target: entity } as any);
×
UNCOV
543
    if (entity instanceof Timer) {
×
UNCOV
544
      if (!Util.contains(this._timers, entity)) {
×
UNCOV
545
        this.addTimer(entity);
×
546
      }
UNCOV
547
      return;
×
548
    }
UNCOV
549
    this.world.add(entity);
×
UNCOV
550
    entity.scene = this;
×
551
  }
552

553
  /**
554
   * Removes a {@apilink Timer} from it's current scene
555
   * and adds it to this scene.
556
   *
557
   * Useful if you want to have an object be present in only 1 scene at a time.
558
   * @param timer The Timer to transfer to the current scene
559
   */
560
  public transfer(timer: Timer): void;
561

562
  /**
563
   * Removes a {@apilink TileMap} from it's current scene
564
   * and adds it to this scene.
565
   *
566
   * Useful if you want to have an object be present in only 1 scene at a time.
567
   * @param tileMap The TileMap to transfer to the current scene
568
   */
569
  public transfer(tileMap: TileMap): void;
570

571
  /**
572
   * Removes a {@apilink Trigger} from it's current scene
573
   * and adds it to this scene.
574
   *
575
   * Useful if you want to have an object be present in only 1 scene at a time.
576
   * @param trigger The Trigger to transfer to the current scene
577
   */
578
  public transfer(trigger: Trigger): void;
579

580
  /**
581
   * Removes an {@apilink Actor} from it's current scene
582
   * and adds it to this scene.
583
   *
584
   * Useful if you want to have an object be present in only 1 scene at a time.
585
   * @param actor The Actor to transfer to the current scene
586
   */
587
  public transfer(actor: Actor): void;
588

589
  /**
590
   * Removes an {@apilink Entity} from it's current scene
591
   * and adds it to this scene.
592
   *
593
   * Useful if you want to have an object be present in only 1 scene at a time.
594
   * @param entity The Entity to transfer to the current scene
595
   */
596
  public transfer(entity: Entity): void;
597

598
  /**
599
   * Removes a {@apilink ScreenElement} from it's current scene
600
   * and adds it to this scene.
601
   *
602
   * Useful if you want to have an object be present in only 1 scene at a time.
603
   * @param screenElement The ScreenElement to transfer to the current scene
604
   */
605
  public transfer(screenElement: ScreenElement): void;
606

607
  /**
608
   * Removes an {@apilink Entity} (Actor, TileMap, Trigger, etc) or {@apilink Timer} from it's current scene
609
   * and adds it to this scene.
610
   *
611
   * Useful if you want to have an object be present in only 1 scene at a time.
612
   * @param entity
613
   */
614
  public transfer(entity: any): void {
615
    let scene: Scene;
UNCOV
616
    if (entity instanceof Entity && entity.scene && entity.scene !== this) {
×
UNCOV
617
      scene = entity.scene;
×
UNCOV
618
      entity.scene.world.remove(entity, false);
×
619
    }
UNCOV
620
    if (entity instanceof Timer && entity.scene) {
×
UNCOV
621
      scene = entity.scene;
×
UNCOV
622
      entity.scene.removeTimer(entity);
×
623
    }
624

UNCOV
625
    scene?.emit('entityremoved', { target: entity } as any);
×
UNCOV
626
    this.add(entity);
×
627
  }
628

629
  /**
630
   * Removes a {@apilink Timer} from the current scene, it will no longer be updated.
631
   * @param timer  The timer to remove to the current scene.
632
   */
633
  public remove(timer: Timer): void;
634

635
  /**
636
   * Removes a {@apilink TileMap} from the scene, it will no longer be drawn or updated.
637
   * @param tileMap {TileMap}
638
   */
639
  public remove(tileMap: TileMap): void;
640

641
  /**
642
   * Removes an actor from the scene, it will no longer be drawn or updated.
643
   * @param actor  The actor to remove from the current scene.
644
   */
645
  public remove(actor: Actor): void;
646

647
  public remove(entity: Entity): void;
648

649
  /**
650
   * Removes a {@apilink ScreenElement} to the scene, it will no longer be drawn or updated
651
   * @param screenElement  The ScreenElement to remove from the current scene
652
   */
653
  public remove(screenElement: ScreenElement): void;
654
  public remove(entity: any): void {
UNCOV
655
    this.emit('entityremoved', { target: entity } as any);
×
UNCOV
656
    if (entity instanceof Entity) {
×
UNCOV
657
      if (entity.isActive) {
×
UNCOV
658
        entity.kill();
×
659
      }
UNCOV
660
      this.world.remove(entity);
×
661
    }
UNCOV
662
    if (entity instanceof Timer) {
×
663
      this.removeTimer(entity);
×
664
    }
665
  }
666

667
  /**
668
   * Removes all entities and timers from the scene, optionally indicate whether deferred should or shouldn't be used.
669
   *
670
   * By default entities use deferred removal
671
   * @param deferred
672
   */
673
  public clear(deferred: boolean = true): void {
×
UNCOV
674
    for (let i = this.entities.length - 1; i >= 0; i--) {
×
UNCOV
675
      this.world.remove(this.entities[i], deferred);
×
676
    }
UNCOV
677
    for (let i = this.timers.length - 1; i >= 0; i--) {
×
UNCOV
678
      this.removeTimer(this.timers[i]);
×
679
    }
680
  }
681

682
  /**
683
   * Adds a {@apilink Timer} to the scene
684
   * @param timer  The timer to add
685
   */
686
  public addTimer(timer: Timer): Timer {
UNCOV
687
    this._timers.push(timer);
×
UNCOV
688
    timer.scene = this;
×
UNCOV
689
    return timer;
×
690
  }
691

692
  /**
693
   * Removes a {@apilink Timer} from the scene.
694
   * @warning Can be dangerous, use {@apilink cancelTimer} instead
695
   * @param timer  The timer to remove
696
   */
697
  public removeTimer(timer: Timer): Timer {
UNCOV
698
    const i = this._timers.indexOf(timer);
×
UNCOV
699
    if (i !== -1) {
×
UNCOV
700
      this._timers.splice(i, 1);
×
701
    }
UNCOV
702
    return timer;
×
703
  }
704

705
  /**
706
   * Cancels a {@apilink Timer}, removing it from the scene nicely
707
   * @param timer  The timer to cancel
708
   */
709
  public cancelTimer(timer: Timer): Timer {
UNCOV
710
    this._cancelQueue.push(timer);
×
UNCOV
711
    return timer;
×
712
  }
713

714
  /**
715
   * Tests whether a {@apilink Timer} is active in the scene
716
   */
717
  public isTimerActive(timer: Timer): boolean {
UNCOV
718
    return this._timers.indexOf(timer) > -1 && !timer.complete;
×
719
  }
720

721
  public isCurrentScene(): boolean {
UNCOV
722
    if (this.engine) {
×
UNCOV
723
      return this.engine.currentScene === this;
×
724
    }
UNCOV
725
    return false;
×
726
  }
727

728
  private _collectActorStats(engine: Engine) {
UNCOV
729
    const actors = this.actors;
×
UNCOV
730
    for (let i = 0; i < actors.length; i++) {
×
UNCOV
731
      const actor = actors[i];
×
UNCOV
732
      if (actor instanceof ScreenElement) {
×
UNCOV
733
        engine.stats.currFrame.actors.ui++;
×
734
      }
UNCOV
735
      engine.stats.currFrame.actors.alive++;
×
UNCOV
736
      for (let j = 0; j < actor.children.length; j++) {
×
UNCOV
737
        const child = actor.children[j];
×
UNCOV
738
        if (isScreenElement(child as Actor)) {
×
739
          // TODO not true
740
          engine.stats.currFrame.actors.ui++;
×
741
        } else {
UNCOV
742
          engine.stats.currFrame.actors.alive++;
×
743
        }
744
      }
745
    }
746
  }
747
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc