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

excaliburjs / Excalibur / 20757998504

06 Jan 2026 06:27PM UTC coverage: 88.644% (-0.1%) from 88.739%
20757998504

Pull #3656

github

web-flow
Merge 45693b2b4 into 7940d6600
Pull Request #3656: [feat] PauseComponent

5396 of 7349 branches covered (73.42%)

33 of 48 new or added lines in 10 files covered. (68.75%)

7 existing lines in 2 files now uncovered.

14871 of 16776 relevant lines covered (88.64%)

24704.18 hits per line

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

78.06
/src/engine/actor.ts
1
import type {
2
  PostCollisionEvent,
3
  PreCollisionEvent,
4
  CollisionStartEvent,
5
  CollisionEndEvent,
6
  EnterViewPortEvent,
7
  ExitViewPortEvent,
8
  PreDrawEvent,
9
  PostDrawEvent,
10
  PreDebugDrawEvent,
11
  PostDebugDrawEvent,
12
  ActionStartEvent,
13
  ActionCompleteEvent
14
} from './events';
15
import { type KillEvent, PreUpdateEvent, PostUpdateEvent, PostKillEvent, PreKillEvent } from './events';
16
import type { Engine } from './engine';
17
import type { Color } from './color';
18
import type { CanInitialize, CanUpdate, CanBeKilled } from './interfaces/lifecycle-events';
19
import type { Scene } from './scene';
20
import { Logger } from './util/log';
21
import { Vector, vec } from './math/vector';
22
import { BodyComponent } from './collision/body-component';
23
import type { Eventable } from './interfaces/evented';
24
import type { PointerEvents } from './interfaces/pointer-event-handlers';
25
import { CollisionType } from './collision/collision-type';
26
import { PauseComponent } from './entity-component-system/components/pause-component';
27

28
import type { EntityEvents } from './entity-component-system/entity';
29
import { Entity } from './entity-component-system/entity';
30
import { TransformComponent } from './entity-component-system/components/transform-component';
31
import { MotionComponent } from './entity-component-system/components/motion-component';
32
import { GraphicsComponent } from './graphics/graphics-component';
33
import { Rectangle } from './graphics/rectangle';
34
import { ColliderComponent } from './collision/collider-component';
35
import { Shape } from './collision/colliders/shape';
36
import { watch } from './util/watch';
37
import type { Collider, CollisionContact, CollisionGroup, Side } from './collision/index';
38
import { Circle } from './graphics/circle';
39
import type { PointerEvent } from './input/pointer-event';
40
import type { WheelEvent } from './input/wheel-event';
41
import { PointerComponent } from './input/pointer-component';
42
import { ActionsComponent } from './actions/actions-component';
43
import { CoordPlane } from './math/coord-plane';
44
import type { EventKey, Handler, Subscription } from './event-emitter';
45
import { EventEmitter } from './event-emitter';
46
import type { Component } from './entity-component-system';
47
import type { Graphic, Material } from './graphics';
48

49
/**
50
 * Type guard for checking if something is an Actor
51
 * @param x
52
 */
53
export function isActor(x: any): x is Actor {
54
  return x instanceof Actor;
×
55
}
56

57
/**
58
 * Actor constructor options
59
 */
60
export type ActorArgs = ColliderArgs & {
61
  /**
62
   * Optionally set the name of the actor, default is 'anonymous'
63
   */
64
  name?: string;
65
  /**
66
   * Optionally set the x position of the actor, default is 0
67
   */
68
  x?: number;
69
  /**
70
   * Optionally set the y position of the actor, default is 0
71
   */
72
  y?: number;
73
  /**
74
   * Optionally set the (x, y) position of the actor as a vector, default is (0, 0)
75
   */
76
  pos?: Vector;
77
  /**
78
   * Optionally set the coordinate plane of the actor, default is {@apilink CoordPlane.World} meaning actor is subject to camera positioning
79
   */
80
  coordPlane?: CoordPlane;
81
  /**
82
   * Optionally set the velocity of the actor in pixels/sec
83
   */
84
  vel?: Vector;
85
  /**
86
   * Optionally set the acceleration of the actor in pixels/sec^2
87
   */
88
  acc?: Vector;
89
  /**
90
   * Optionally se the rotation in radians (180 degrees = Math.PI radians)
91
   */
92
  rotation?: number;
93
  /**
94
   * Optionally set the angular velocity of the actor in radians/sec (180 degrees = Math.PI radians)
95
   */
96
  angularVelocity?: number;
97
  /**
98
   * Optionally set the scale of the actor's transform
99
   */
100
  scale?: Vector;
101
  /**
102
   * Optionally set the z index of the actor, default is 0
103
   */
104
  z?: number;
105
  /**
106
   * Optionally set the color of an actor, only used if no graphics are present
107
   * If a width/height or a radius was set a default graphic will be added
108
   */
109
  color?: Color;
110
  /**
111
   * Optionally set the default graphic
112
   */
113
  graphic?: Graphic;
114
  /**
115
   * Optionally set the default material
116
   */
117
  material?: Material;
118
  /**
119
   * Optionally set the color of an actor, only used if no graphics are present
120
   * If a width/height or a radius was set a default graphic will be added
121
   */
122
  opacity?: number;
123
  /**
124
   * Optionally set the visibility of the actor
125
   */
126
  visible?: boolean;
127
  /**
128
   * Optionally set the anchor for graphics in the actor
129
   */
130
  anchor?: Vector;
131
  /**
132
   * Optionally set the anchor for graphics in the actor
133
   */
134
  offset?: Vector;
135
  /**
136
   * Optionally set the collision type
137
   */
138
  collisionType?: CollisionType;
139

140
  /**
141
   * Optionally supply a {@apilink CollisionGroup}
142
   */
143
  collisionGroup?: CollisionGroup;
144
  /**
145
   * Optionally set if the actor can be paused
146
   */
147
  canPause?: boolean;
148
};
149

150
type ColliderArgs =
151
  | // custom collider
152
  {
153
      /**
154
       * Optionally supply a collider for an actor, if supplied ignores any supplied width/height
155
       *
156
       * No default graphigc is created in this case
157
       */
158
      collider?: Collider;
159

160
      width?: undefined;
161
      height?: undefined;
162
      radius?: undefined;
163
      color?: undefined;
164
    }
165
  // box collider
166
  | {
167
      /**
168
       * Optionally set the width of a box collider for the actor
169
       */
170
      width?: number;
171
      /**
172
       * Optionally set the height of a box collider for the actor
173
       */
174
      height?: number;
175

176
      /**
177
       * Optionally set the color of a rectangle graphic for the actor
178
       */
179
      color?: Color;
180

181
      collider?: undefined;
182
      radius?: undefined;
183
    }
184
  // circle collider
185
  | {
186
      /**
187
       * Optionally set the radius of the circle collider for the actor
188
       */
189
      radius?: number;
190

191
      /**
192
       * Optionally set the color on a circle graphic for the actor
193
       */
194
      color?: Color;
195

196
      collider?: undefined;
197
      width?: undefined;
198
      height?: undefined;
199
    };
200

201
export interface ActorEvents extends EntityEvents {
202
  collisionstart: CollisionStartEvent;
203
  collisionend: CollisionEndEvent;
204
  precollision: PreCollisionEvent;
205
  postcollision: PostCollisionEvent;
206
  kill: KillEvent;
207
  prekill: PreKillEvent;
208
  postkill: PostKillEvent;
209
  predraw: PreDrawEvent;
210
  postdraw: PostDrawEvent;
211
  pretransformdraw: PreDrawEvent;
212
  posttransformdraw: PostDrawEvent;
213
  predebugdraw: PreDebugDrawEvent;
214
  postdebugdraw: PostDebugDrawEvent;
215
  pointerup: PointerEvent;
216
  pointerdown: PointerEvent;
217
  pointerenter: PointerEvent;
218
  pointerleave: PointerEvent;
219
  pointermove: PointerEvent;
220
  pointercancel: PointerEvent;
221
  pointerwheel: WheelEvent;
222
  pointerdragstart: PointerEvent;
223
  pointerdragend: PointerEvent;
224
  pointerdragenter: PointerEvent;
225
  pointerdragleave: PointerEvent;
226
  pointerdragmove: PointerEvent;
227
  enterviewport: EnterViewPortEvent;
228
  exitviewport: ExitViewPortEvent;
229
  actionstart: ActionStartEvent;
230
  actioncomplete: ActionCompleteEvent;
231
}
232

233
export const ActorEvents = {
250✔
234
  CollisionStart: 'collisionstart',
235
  CollisionEnd: 'collisionend',
236
  PreCollision: 'precollision',
237
  PostCollision: 'postcollision',
238
  Kill: 'kill',
239
  PreKill: 'prekill',
240
  PostKill: 'postkill',
241
  PreDraw: 'predraw',
242
  PostDraw: 'postdraw',
243
  PreTransformDraw: 'pretransformdraw',
244
  PostTransformDraw: 'posttransformdraw',
245
  PreDebugDraw: 'predebugdraw',
246
  PostDebugDraw: 'postdebugdraw',
247
  PointerUp: 'pointerup',
248
  PointerDown: 'pointerdown',
249
  PointerEnter: 'pointerenter',
250
  PointerLeave: 'pointerleave',
251
  PointerMove: 'pointermove',
252
  PointerCancel: 'pointercancel',
253
  Wheel: 'pointerwheel',
254
  PointerDrag: 'pointerdragstart',
255
  PointerDragEnd: 'pointerdragend',
256
  PointerDragEnter: 'pointerdragenter',
257
  PointerDragLeave: 'pointerdragleave',
258
  PointerDragMove: 'pointerdragmove',
259
  EnterViewPort: 'enterviewport',
260
  ExitViewPort: 'exitviewport',
261
  ActionStart: 'actionstart',
262
  ActionComplete: 'actioncomplete'
263
} as const;
264

265
/**
266
 * The most important primitive in Excalibur is an `Actor`. Anything that
267
 * can move on the screen, collide with another `Actor`, respond to events,
268
 * or interact with the current scene, must be an actor. An `Actor` **must**
269
 * be part of a {@apilink Scene} for it to be drawn to the screen.
270
 */
271
export class Actor extends Entity implements Eventable, PointerEvents, CanInitialize, CanUpdate, CanBeKilled {
250✔
272
  public events = new EventEmitter<ActorEvents>();
894✔
273
  // #region Properties
274

275
  /**
276
   * Set defaults for all Actors
277
   */
278
  public static defaults = {
279
    anchor: Vector.Half
280
  };
281

282
  /**
283
   * The physics body the is associated with this actor. The body is the container for all physical properties, like position, velocity,
284
   * acceleration, mass, inertia, etc.
285
   */
286
  public body: BodyComponent;
287

288
  /**
289
   * The physics body the is associated with this actor. The body is the container for all physical properties, like position, velocity,
290
   * acceleration, mass, inertia, etc.
291
   */
292
  public paused: PauseComponent;
293

294
  /**
295
   * Access the Actor's built in {@apilink TransformComponent}
296
   */
297
  public transform: TransformComponent;
298

299
  /**
300
   * Access the Actor's built in {@apilink MotionComponent}
301
   */
302
  public motion: MotionComponent;
303

304
  /**
305
   * Access to the Actor's built in {@apilink GraphicsComponent}
306
   */
307
  public graphics: GraphicsComponent;
308

309
  /**
310
   * Access to the Actor's built in {@apilink ColliderComponent}
311
   */
312
  public collider: ColliderComponent;
313

314
  /**
315
   * Access to the Actor's built in {@apilink PointerComponent} config
316
   */
317
  public pointer: PointerComponent;
318

319
  /**
320
   * Useful for quickly scripting actor behavior, like moving to a place, patrolling back and forth, blinking, etc.
321
   *
322
   *  Access to the Actor's built in {@apilink ActionsComponent} which forwards to the
323
   * {@apilink ActionContext | `Action context`} of the actor.
324
   */
325
  public actions: ActionsComponent;
326

327
  /**
328
   * Gets the position vector of the actor in pixels
329
   */
330
  public get pos(): Vector {
331
    return this.transform.pos;
607✔
332
  }
333

334
  /**
335
   * Sets the position vector of the actor in pixels
336
   */
337
  public set pos(thePos: Vector) {
338
    this.transform.pos = thePos.clone();
936✔
339
  }
340

341
  /**
342
   * Gets the position vector of the actor from the last frame
343
   */
344
  public get oldPos(): Vector {
345
    return this.body.oldPos;
2✔
346
  }
347

348
  /**
349
   * Gets the global position vector of the actor from the last frame
350
   */
351
  public get oldGlobalPos(): Vector {
352
    return this.body.oldGlobalPos;
2✔
353
  }
354

355
  /**
356
   * Sets the position vector of the actor in the last frame
357
   */
358
  public set oldPos(thePos: Vector) {
359
    this.body.oldPos.setTo(thePos.x, thePos.y);
×
360
  }
361

362
  /**
363
   * Gets the velocity vector of the actor in pixels/sec
364
   */
365
  public get vel(): Vector {
366
    return this.motion.vel;
79✔
367
  }
368

369
  /**
370
   * Sets the velocity vector of the actor in pixels/sec
371
   */
372
  public set vel(theVel: Vector) {
373
    this.motion.vel = theVel.clone();
906✔
374
  }
375

376
  /**
377
   * Gets the velocity vector of the actor from the last frame
378
   */
379
  public get oldVel(): Vector {
380
    return this.body.oldVel;
2✔
381
  }
382

383
  /**
384
   * Sets the velocity vector of the actor from the last frame
385
   */
386
  public set oldVel(theVel: Vector) {
387
    this.body.oldVel.setTo(theVel.x, theVel.y);
×
388
  }
389

390
  /**
391
   * Gets the acceleration vector of the actor in pixels/second/second. An acceleration pointing down such as (0, 100) may be
392
   * useful to simulate a gravitational effect.
393
   */
394
  public get acc(): Vector {
395
    return this.motion.acc;
6✔
396
  }
397

398
  /**
399
   * Sets the acceleration vector of teh actor in pixels/second/second
400
   */
401
  public set acc(theAcc: Vector) {
402
    this.motion.acc = theAcc.clone();
897✔
403
  }
404

405
  /**
406
   * Sets the acceleration of the actor from the last frame. This does not include the global acc {@apilink Physics.acc}.
407
   */
408
  public set oldAcc(theAcc: Vector) {
409
    this.body.oldAcc.setTo(theAcc.x, theAcc.y);
×
410
  }
411

412
  /**
413
   * Gets the acceleration of the actor from the last frame. This does not include the global acc {@apilink Physics.acc}.
414
   */
415
  public get oldAcc(): Vector {
416
    return this.body.oldAcc;
×
417
  }
418

419
  /**
420
   * Gets the rotation of the actor in radians. 1 radian = 180/PI Degrees.
421
   */
422
  public get rotation(): number {
423
    return this.transform.rotation;
84✔
424
  }
425

426
  /**
427
   * Sets the rotation of the actor in radians. 1 radian = 180/PI Degrees.
428
   */
429
  public set rotation(theAngle: number) {
430
    this.transform.rotation = theAngle;
903✔
431
  }
432

433
  /**
434
   * Gets the rotational velocity of the actor in radians/second
435
   */
436
  public get angularVelocity(): number {
437
    return this.motion.angularVelocity;
23✔
438
  }
439

440
  /**
441
   * Sets the rotational velocity of the actor in radians/sec
442
   */
443
  public set angularVelocity(angularVelocity: number) {
444
    this.motion.angularVelocity = angularVelocity;
894✔
445
  }
446

447
  public get scale(): Vector {
448
    return this.get(TransformComponent).scale;
79✔
449
  }
450

451
  public set scale(scale: Vector) {
452
    this.get(TransformComponent).scale = scale;
906✔
453
  }
454

455
  public get canPause(): boolean {
NEW
456
    return this.paused.canPause;
×
457
  }
458

459
  public set canPause(canPause: boolean) {
NEW
460
    this.paused.canPause = canPause;
×
461
  }
462

463
  public get isPaused(): boolean {
NEW
464
    return this.paused.paused;
×
465
  }
466

467
  private _anchor: Vector = watch(Vector.Half, (v) => this._handleAnchorChange(v));
894✔
468
  /**
469
   * The anchor to apply all actor related transformations like rotation,
470
   * translation, and scaling. By default the anchor is in the center of
471
   * the actor. By default it is set to the center of the actor (.5, .5)
472
   *
473
   * An anchor of (.5, .5) will ensure that drawings are centered.
474
   *
475
   * Use `anchor.setTo` to set the anchor to a different point using
476
   * values between 0 and 1. For example, anchoring to the top-left would be
477
   * `Actor.anchor.setTo(0, 0)` and top-right would be `Actor.anchor.setTo(0, 1)`.
478
   */
479
  public get anchor(): Vector {
480
    return this._anchor;
1,545✔
481
  }
482

483
  public set anchor(vec: Vector) {
484
    this._anchor = watch(vec, (v) => this._handleAnchorChange(v));
916✔
485
    this._handleAnchorChange(vec);
916✔
486
  }
487

488
  private _handleAnchorChange(v: Vector) {
489
    if (this.graphics) {
916✔
490
      this.graphics.anchor = v;
22✔
491
    }
492
  }
493

494
  private _offset: Vector = watch(Vector.Zero, (v) => this._handleOffsetChange(v));
894✔
495
  /**
496
   * The offset in pixels to apply to all actor graphics
497
   *
498
   * Default offset of (0, 0)
499
   */
500
  public get offset(): Vector {
501
    return this._offset;
896✔
502
  }
503

504
  public set offset(vec: Vector) {
505
    this._offset = watch(vec, (v) => this._handleOffsetChange(v));
894✔
506
    this._handleOffsetChange(vec);
894✔
507
  }
508

509
  private _handleOffsetChange(v: Vector) {
510
    if (this.graphics) {
894!
511
      this.graphics.offset = v;
×
512
    }
513
  }
514

515
  /**
516
   * Indicates whether the actor is physically in the viewport
517
   */
518
  public get isOffScreen(): boolean {
519
    return this.hasTag('ex.offscreen');
13✔
520
  }
521

522
  /**
523
   * Convenience reference to the global logger
524
   */
525
  public logger: Logger = Logger.getInstance();
894✔
526

527
  /**
528
   * Draggable helper
529
   */
530
  private _draggable: boolean = false;
894✔
531
  private _dragging: boolean = false;
894✔
532

533
  private _pointerDragStartHandler = () => {
894✔
534
    this._dragging = true;
×
535
  };
536

537
  private _pointerDragEndHandler = () => {
894✔
538
    this._dragging = false;
×
539
  };
540

541
  private _pointerDragMoveHandler = (pe: PointerEvent) => {
894✔
542
    if (this._dragging) {
×
543
      this.pos = pe.worldPos;
×
544
    }
545
  };
546

547
  private _pointerDragLeaveHandler = (pe: PointerEvent) => {
894✔
548
    if (this._dragging) {
×
549
      this.pos = pe.worldPos;
×
550
    }
551
  };
552

553
  public get draggable(): boolean {
554
    return this._draggable;
×
555
  }
556

557
  public set draggable(isDraggable: boolean) {
558
    if (isDraggable) {
×
559
      if (isDraggable && !this._draggable) {
×
560
        this.events.on('pointerdragstart', this._pointerDragStartHandler);
×
561
        this.events.on('pointerdragend', this._pointerDragEndHandler);
×
562
        this.events.on('pointerdragmove', this._pointerDragMoveHandler);
×
563
        this.events.on('pointerdragleave', this._pointerDragLeaveHandler);
×
564
      } else if (!isDraggable && this._draggable) {
×
565
        this.events.off('pointerdragstart', this._pointerDragStartHandler);
×
566
        this.events.off('pointerdragend', this._pointerDragEndHandler);
×
567
        this.events.off('pointerdragmove', this._pointerDragMoveHandler);
×
568
        this.events.off('pointerdragleave', this._pointerDragLeaveHandler);
×
569
      }
570

571
      this._draggable = isDraggable;
×
572
    }
573
  }
574

575
  /**
576
   * Sets the color of the actor's current graphic
577
   */
578
  public get color(): Color {
579
    return this.graphics.color;
21✔
580
  }
581
  public set color(v: Color) {
582
    this.graphics.color = v;
171✔
583
  }
584

585
  // #endregion
586

587
  /**
588
   *
589
   * @param config
590
   */
591
  constructor(config?: ActorArgs) {
592
    super();
894✔
593

594
    const {
595
      name,
596
      x,
597
      y,
598
      pos,
599
      coordPlane,
600
      scale,
601
      width,
602
      height,
603
      radius,
604
      collider,
605
      vel,
606
      acc,
607
      rotation,
608
      angularVelocity,
609
      z,
610
      color,
611
      visible,
612
      opacity,
613
      anchor,
614
      offset,
615
      collisionType,
616
      collisionGroup,
617
      graphic,
618
      material,
619
      canPause
620
    } = {
894✔
621
      ...config
622
    };
623

624
    this.name = name ?? this.name;
894✔
625
    this.anchor = anchor ?? Actor.defaults.anchor.clone();
894✔
626
    this.offset = offset ?? Vector.Zero;
894✔
627
    this.transform = new TransformComponent();
894✔
628
    this.addComponent(this.transform);
894✔
629
    this.pos = pos ?? vec(x ?? 0, y ?? 0);
894✔
630
    this.rotation = rotation ?? 0;
894✔
631
    this.scale = scale ?? vec(1, 1);
894✔
632
    this.z = z ?? 0;
894✔
633
    this.transform.coordPlane = coordPlane ?? CoordPlane.World;
894✔
634

635
    this.pointer = new PointerComponent();
894✔
636
    this.addComponent(this.pointer);
894✔
637

638
    this.graphics = new GraphicsComponent({
894✔
639
      anchor: this.anchor,
640
      offset: this.offset,
641
      opacity: opacity
642
    });
643
    this.addComponent(this.graphics);
894✔
644

645
    this.motion = new MotionComponent();
894✔
646
    this.addComponent(this.motion);
894✔
647
    this.vel = vel ?? Vector.Zero;
894✔
648
    this.acc = acc ?? Vector.Zero;
894✔
649
    this.angularVelocity = angularVelocity ?? 0;
894✔
650

651
    this.actions = new ActionsComponent();
894✔
652
    this.addComponent(this.actions);
894✔
653

654
    this.body = new BodyComponent();
894✔
655
    this.addComponent(this.body);
894✔
656
    this.body.collisionType = collisionType ?? CollisionType.Passive;
894✔
657
    if (collisionGroup) {
894✔
658
      this.body.group = collisionGroup;
21✔
659
    }
660

661
    this.paused = new PauseComponent({ canPause });
894✔
662
    this.addComponent(this.paused);
894✔
663

664
    if (color) {
894✔
665
      this.color = color;
140✔
666
    }
667

668
    if (collider) {
894✔
669
      this.collider = new ColliderComponent(collider);
8✔
670
      this.addComponent(this.collider);
8✔
671
    } else if (radius) {
886✔
672
      this.collider = new ColliderComponent(Shape.Circle(radius));
7✔
673
      this.addComponent(this.collider);
7✔
674

675
      if (color) {
7✔
676
        this.graphics.add(
2✔
677
          new Circle({
678
            color: color,
679
            radius
680
          })
681
        );
682
      }
683
    } else {
684
      if (width > 0 && height > 0) {
879✔
685
        this.collider = new ColliderComponent(Shape.Box(width, height, this.anchor));
562✔
686
        this.addComponent(this.collider);
562✔
687

688
        if (color && width && height) {
562✔
689
          this.graphics.add(
134✔
690
            new Rectangle({
691
              color: color,
692
              width,
693
              height
694
            })
695
          );
696
        }
697
      } else {
698
        this.collider = new ColliderComponent();
317✔
699
        this.addComponent(this.collider); // no collider
317✔
700
      }
701
    }
702

703
    this.graphics.isVisible = visible ?? true;
894✔
704
    if (graphic) {
894!
705
      this.graphics.use(graphic);
×
706
    }
707
    if (material) {
894!
708
      this.graphics.material = material;
×
709
    }
710
  }
711

712
  public clone(): Actor {
713
    const clone = new Actor({
2✔
714
      color: this.color.clone(),
715
      anchor: this.anchor.clone(),
716
      offset: this.offset.clone()
717
    });
718
    clone.clearComponents();
2✔
719
    clone.processComponentRemoval();
2✔
720

721
    // Clone builtins, order is important, same as ctor
722
    clone.addComponent((clone.transform = this.transform.clone() as TransformComponent), true);
2✔
723
    clone.addComponent((clone.pointer = this.pointer.clone() as PointerComponent), true);
2✔
724
    clone.addComponent((clone.graphics = this.graphics.clone() as GraphicsComponent), true);
2✔
725
    clone.addComponent((clone.motion = this.motion.clone() as MotionComponent), true);
2✔
726
    clone.addComponent((clone.actions = this.actions.clone() as ActionsComponent), true);
2✔
727
    clone.addComponent((clone.body = this.body.clone() as BodyComponent), true);
2✔
728
    if (this.collider.get()) {
2✔
729
      clone.addComponent((clone.collider = this.collider.clone() as ColliderComponent), true);
1✔
730
    }
731

732
    const builtInComponents: Component[] = [
2✔
733
      this.transform,
734
      this.pointer,
735
      this.graphics,
736
      this.motion,
737
      this.actions,
738
      this.body,
739
      this.collider
740
    ];
741

742
    // Clone non-builtin the current actors components
743
    const components = this.getComponents();
2✔
744
    for (const c of components) {
2✔
745
      if (!builtInComponents.includes(c)) {
16✔
746
        clone.addComponent(c.clone(), true);
2✔
747
      }
748
    }
749
    return clone;
2✔
750
  }
751

752
  /**
753
   * `onInitialize` is called before the first update of the actor. This method is meant to be
754
   * overridden. This is where initialization of child actors should take place.
755
   *
756
   * Synonymous with the event handler `.on('initialize', (evt) => {...})`
757
   */
758
  public onInitialize(engine: Engine): void {
759
    // Override me
760
  }
761

762
  /**
763
   * Initializes this actor and all it's child actors, meant to be called by the Scene before first update not by users of Excalibur.
764
   *
765
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
766
   * @internal
767
   */
768
  public _initialize(engine: Engine) {
769
    super._initialize(engine);
2,399✔
770
    for (const child of this.children) {
2,399✔
771
      child._initialize(engine);
15✔
772
    }
773
  }
774

775
  // #region Events
776
  public emit<TEventName extends EventKey<ActorEvents>>(eventName: TEventName, event: ActorEvents[TEventName]): void;
777
  public emit(eventName: string, event?: any): void;
778
  public emit<TEventName extends EventKey<ActorEvents> | string>(eventName: TEventName, event?: any): void {
779
    this.events.emit(eventName, event);
532✔
780
  }
781

782
  public on<TEventName extends EventKey<ActorEvents>>(eventName: TEventName, handler: Handler<ActorEvents[TEventName]>): Subscription;
783
  public on(eventName: string, handler: Handler<unknown>): Subscription;
784
  public on<TEventName extends EventKey<ActorEvents> | string>(eventName: TEventName, handler: Handler<any>): Subscription {
785
    return this.events.on(eventName, handler);
73✔
786
  }
787

788
  public once<TEventName extends EventKey<ActorEvents>>(eventName: TEventName, handler: Handler<ActorEvents[TEventName]>): Subscription;
789
  public once(eventName: string, handler: Handler<unknown>): Subscription;
790
  public once<TEventName extends EventKey<ActorEvents> | string>(eventName: TEventName, handler: Handler<any>): Subscription {
791
    return this.events.once(eventName, handler);
1✔
792
  }
793

794
  public off<TEventName extends EventKey<ActorEvents>>(eventName: TEventName, handler: Handler<ActorEvents[TEventName]>): void;
795
  public off(eventName: string, handler: Handler<unknown>): void;
796
  public off(eventName: string): void;
797
  public off<TEventName extends EventKey<ActorEvents> | string>(eventName: TEventName, handler?: Handler<any>): void {
798
    this.events.off(eventName, handler);
×
799
  }
800

801
  // #endregion
802

803
  /**
804
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
805
   *
806
   * Internal _prekill handler for {@apilink onPreKill} lifecycle event
807
   * @internal
808
   */
809
  public _prekill(scene: Scene) {
810
    this.events.emit('prekill', new PreKillEvent(this));
22✔
811
    this.onPreKill(scene);
22✔
812
  }
813

814
  /**
815
   * Safe to override onPreKill lifecycle event handler. Synonymous with `.on('prekill', (evt) =>{...})`
816
   *
817
   * `onPreKill` is called directly before an actor is killed and removed from its current {@apilink Scene}.
818
   */
819
  public onPreKill(scene: Scene) {
820
    // Override me
821
  }
822

823
  /**
824
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
825
   *
826
   * Internal _prekill handler for {@apilink onPostKill} lifecycle event
827
   * @internal
828
   */
829
  public _postkill(scene: Scene) {
830
    this.events.emit('postkill', new PostKillEvent(this));
22✔
831
    this.onPostKill(scene);
22✔
832
  }
833

834
  /**
835
   * Safe to override onPostKill lifecycle event handler. Synonymous with `.on('postkill', (evt) => {...})`
836
   *
837
   * `onPostKill` is called directly after an actor is killed and remove from its current {@apilink Scene}.
838
   */
839
  public onPostKill(scene: Scene) {
840
    // Override me
841
  }
842

843
  /**
844
   * If the current actor is a member of the scene, this will remove
845
   * it from the scene graph. It will no longer be drawn or updated.
846
   */
847
  public kill() {
848
    if (this.scene) {
23✔
849
      this._prekill(this.scene);
22✔
850
      super.kill();
22✔
851
      this._postkill(this.scene);
22✔
852
    } else {
853
      if (process.env.NODE_ENV === 'development') {
1!
854
        this.logger.warn(`Cannot kill actor named "${this.name}", it was never added to the Scene`);
1✔
855
      }
856
    }
857
  }
858

859
  /**
860
   * If the current actor is killed, it will now not be killed.
861
   */
862
  public unkill() {
863
    this.isActive = true;
×
864
  }
865

866
  /**
867
   * Indicates wether the actor has been killed.
868
   */
869
  public isKilled(): boolean {
870
    return !this.isActive;
5✔
871
  }
872

873
  /**
874
   * Gets the z-index of an actor. The z-index determines the relative order an actor is drawn in.
875
   * Actors with a higher z-index are drawn on top of actors with a lower z-index
876
   */
877
  public get z(): number {
878
    return this.get(TransformComponent).z;
85✔
879
  }
880

881
  /**
882
   * Sets the z-index of an actor and updates it in the drawing list for the scene.
883
   * The z-index determines the relative order an actor is drawn in.
884
   * Actors with a higher z-index are drawn on top of actors with a lower z-index
885
   * @param newZ new z-index to assign
886
   */
887
  public set z(newZ: number) {
888
    this.get(TransformComponent).z = newZ;
908✔
889
  }
890

891
  /**
892
   * Get the center point of an actor (global position)
893
   */
894
  public get center(): Vector {
895
    const globalPos = this.getGlobalPos();
27✔
896
    return new Vector(
27✔
897
      globalPos.x + this.width / 2 - this.anchor.x * this.width,
898
      globalPos.y + this.height / 2 - this.anchor.y * this.height
899
    );
900
  }
901

902
  /**
903
   * Get the local center point of an actor
904
   */
905
  public get localCenter(): Vector {
906
    return new Vector(this.pos.x + this.width / 2 - this.anchor.x * this.width, this.pos.y + this.height / 2 - this.anchor.y * this.height);
3✔
907
  }
908

909
  public get width() {
910
    return this.collider.localBounds.width * this.getGlobalScale().x;
72✔
911
  }
912

913
  public get height() {
914
    return this.collider.localBounds.height * this.getGlobalScale().y;
72✔
915
  }
916

917
  /**
918
   * Gets this actor's rotation taking into account any parent relationships
919
   * @returns Rotation angle in radians
920
   * @deprecated Use {@apilink globalRotation} instead
921
   */
922
  public getGlobalRotation(): number {
923
    return this.get(TransformComponent).globalRotation;
1✔
924
  }
925

926
  /**
927
   * The actor's rotation (in radians) taking into account any parent relationships
928
   */
929
  public get globalRotation(): number {
930
    return this.get(TransformComponent).globalRotation;
×
931
  }
932

933
  /**
934
   * Gets an actor's world position taking into account parent relationships, scaling, rotation, and translation
935
   * @returns Position in world coordinates
936
   * @deprecated Use {@apilink globalPos} instead
937
   */
938
  public getGlobalPos(): Vector {
939
    return this.get(TransformComponent).globalPos;
45✔
940
  }
941

942
  /**
943
   * The actor's world position taking into account parent relationships, scaling, rotation, and translation
944
   */
945
  public get globalPos(): Vector {
946
    return this.get(TransformComponent).globalPos;
4✔
947
  }
948

949
  /**
950
   * Gets the global scale of the Actor
951
   * @deprecated Use {@apilink globalScale} instead
952
   */
953
  public getGlobalScale(): Vector {
954
    return this.get(TransformComponent).globalScale;
144✔
955
  }
956

957
  /**
958
   * The global scale of the Actor
959
   */
960
  public get globalScale(): Vector {
961
    return this.get(TransformComponent).globalScale;
×
962
  }
963

964
  /**
965
   * The global z-index of the actor
966
   */
967
  public get globalZ(): number {
968
    return this.get(TransformComponent).globalZ;
×
969
  }
970

971
  // #region Collision
972

973
  /**
974
   * Tests whether the x/y specified are contained in the actor
975
   * @param x  X coordinate to test (in world coordinates)
976
   * @param y  Y coordinate to test (in world coordinates)
977
   * @param recurse checks whether the x/y are contained in any child actors (if they exist).
978
   */
979
  public contains(x: number, y: number, recurse: boolean = false): boolean {
11✔
980
    const point = vec(x, y);
18✔
981
    const collider = this.get(ColliderComponent);
18✔
982
    collider.update();
18✔
983
    const geom = collider.get();
18✔
984
    if (!geom) {
18!
985
      return false;
×
986
    }
987
    const containment = geom.contains(point);
18✔
988

989
    if (recurse) {
18✔
990
      return (
7✔
991
        containment ||
12✔
992
        this.children.some((child: Actor) => {
993
          return child.contains(x, y, true);
4✔
994
        })
995
      );
996
    }
997

998
    return containment;
11✔
999
  }
1000

1001
  /**
1002
   * Returns true if the two actor.collider's surfaces are less than or equal to the distance specified from each other
1003
   * @param actor     Actor to test
1004
   * @param distance  Distance in pixels to test
1005
   */
1006
  public within(actor: Actor, distance: number): boolean {
1007
    const collider = this.get(ColliderComponent);
×
1008
    const otherCollider = actor.get(ColliderComponent);
×
1009
    const me = collider.get();
×
1010
    const other = otherCollider.get();
×
1011
    if (me && other) {
×
1012
      return me.getClosestLineBetween(other).getLength() <= distance;
×
1013
    }
1014
    return false;
×
1015
  }
1016

1017
  // #endregion
1018

1019
  // #region Update
1020

1021
  /**
1022
   * Called by the Engine, updates the state of the actor
1023
   * @internal
1024
   * @param engine The reference to the current game engine
1025
   * @param elapsed  The time elapsed since the last update in milliseconds
1026
   */
1027
  public update(engine: Engine, elapsed: number) {
1028
    this._initialize(engine);
2,180✔
1029
    this._add(engine);
2,180✔
1030
    this._preupdate(engine, elapsed);
2,180✔
1031
    this._postupdate(engine, elapsed);
2,180✔
1032
    this._remove(engine);
2,180✔
1033
  }
1034

1035
  /**
1036
   * Safe to override onPreUpdate lifecycle event handler. Synonymous with `.on('preupdate', (evt) =>{...})`
1037
   *
1038
   * `onPreUpdate` is called directly before an actor is updated.
1039
   * @param engine The reference to the current game engine
1040
   * @param elapsed  The time elapsed since the last update in milliseconds
1041
   */
1042
  public onPreUpdate(engine: Engine, elapsed: number): void {
1043
    // Override me
1044
  }
1045

1046
  /**
1047
   * Safe to override onPostUpdate lifecycle event handler. Synonymous with `.on('postupdate', (evt) =>{...})`
1048
   *
1049
   * `onPostUpdate` is called directly after an actor is updated.
1050
   * @param engine The reference to the current game engine
1051
   * @param elapsed  The time elapsed since the last update in milliseconds
1052
   */
1053
  public onPostUpdate(engine: Engine, elapsed: number): void {
1054
    // Override me
1055
  }
1056

1057
  /**
1058
   * Fires before every collision resolution for a confirmed contact
1059
   * @param self
1060
   * @param other
1061
   * @param side
1062
   * @param contact
1063
   */
1064
  public onPreCollisionResolve(self: Collider, other: Collider, side: Side, contact: CollisionContact) {
1065
    // Override me
1066
  }
1067

1068
  /**
1069
   * Fires after every resolution for a confirmed contact.
1070
   * @param self
1071
   * @param other
1072
   * @param side
1073
   * @param contact
1074
   */
1075
  public onPostCollisionResolve(self: Collider, other: Collider, side: Side, contact: CollisionContact) {
1076
    // Override me
1077
  }
1078

1079
  /**
1080
   * Fires once when 2 entities with a ColliderComponent first start colliding or touching, if the Colliders stay in contact this
1081
   * does not continue firing until they separate and re-collide.
1082
   * @param self
1083
   * @param other
1084
   * @param side
1085
   * @param contact
1086
   */
1087
  public onCollisionStart(self: Collider, other: Collider, side: Side, contact: CollisionContact) {
1088
    // Override me
1089
  }
1090

1091
  /**
1092
   * Fires once when 2 entities with a ColliderComponent separate after having been in contact.
1093
   * @param self
1094
   * @param other
1095
   * @param side
1096
   * @param lastContact
1097
   */
1098
  public onCollisionEnd(self: Collider, other: Collider, side: Side, lastContact: CollisionContact) {
1099
    // Override me
1100
  }
1101

1102
  /**
1103
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
1104
   *
1105
   * Internal _preupdate handler for {@apilink onPreUpdate} lifecycle event
1106
   * @param engine The reference to the current game engine
1107
   * @param elapsed  The time elapsed since the last update in milliseconds
1108
   * @internal
1109
   */
1110
  public _preupdate(engine: Engine, elapsed: number): void {
1111
    this.events.emit('preupdate', new PreUpdateEvent(engine, elapsed, this));
2,180✔
1112
    this.onPreUpdate(engine, elapsed);
2,180✔
1113
  }
1114

1115
  /**
1116
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
1117
   *
1118
   * Internal _preupdate handler for {@apilink onPostUpdate} lifecycle event
1119
   * @param engine The reference to the current game engine
1120
   * @param elapsed  The time elapsed since the last update in milliseconds
1121
   * @internal
1122
   */
1123
  public _postupdate(engine: Engine, elapsed: number): void {
1124
    this.events.emit('postupdate', new PostUpdateEvent(engine, elapsed, this));
2,180✔
1125
    this.onPostUpdate(engine, elapsed);
2,180✔
1126
  }
1127

1128
  // endregion
1129
}
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