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

excaliburjs / Excalibur / 29113721976

10 Jul 2026 06:12PM UTC coverage: 88.902% (-0.004%) from 88.906%
29113721976

Pull #3793

github

web-flow
Merge e34b9fb0f into 3ee8a530a
Pull Request #3793: chore: TS6 Upgrades + Typedoc update

6933 of 9094 branches covered (76.24%)

279 of 378 new or added lines in 58 files covered. (73.81%)

3 existing lines in 2 files now uncovered.

15517 of 17454 relevant lines covered (88.9%)

24916.19 hits per line

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

78.28
/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 opacity of an actor, only used if no graphics are present
120
   */
121
  opacity?: number;
122
  /**
123
   * Optionally set the visibility of the actor
124
   */
125
  visible?: boolean;
126
  /**
127
   * Optionally set the anchor for graphics in the actor
128
   */
129
  anchor?: Vector;
130
  /**
131
   * Optionally set the pixel offset for graphics in the actor
132
   */
133
  offset?: Vector;
134
  /**
135
   * Optionally set the collision type
136
   */
137
  collisionType?: CollisionType;
138

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

460
  public set canPause(canPause: boolean) {
461
    this.paused.canPause = canPause;
18✔
462
  }
463

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

586
  // #endregion
587

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

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

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

636
    this.pointer = new PointerComponent();
908✔
637
    this.addComponent(this.pointer);
908✔
638

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

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

652
    this.actions = new ActionsComponent();
908✔
653
    this.addComponent(this.actions);
908✔
654

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

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

665
    if (color) {
908✔
666
      this.color = color;
140✔
667
    }
668

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

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

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

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

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

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

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

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

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

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

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

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

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

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

802
  // #endregion
803

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

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

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

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

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

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

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

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

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

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

903
  /**
904
   * Get the local center point of an actor
905
   */
906
  public get localCenter(): Vector {
907
    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✔
908
  }
909

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

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

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

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

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

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

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

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

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

972
  // #region Collision
973

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

990
    if (recurse) {
18✔
991
      return (
7✔
992
        containment ||
12✔
993
        this.children.some((child) => {
994
          if (child instanceof Actor) {
4!
995
            return child.contains(x, y, true);
4✔
996
          }
NEW
997
          return false;
×
998
        })
999
      );
1000
    }
1001

1002
    return containment;
11✔
1003
  }
1004

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

1021
  // #endregion
1022

1023
  // #region Update
1024

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

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

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

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

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

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

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

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

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

1132
  // endregion
1133
}
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