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

excaliburjs / Excalibur / 19644361088

24 Nov 2025 06:06PM UTC coverage: 88.57% (-0.02%) from 88.585%
19644361088

push

github

web-flow
feat: Add convenience `graphic` and `material` to Actor ctor (#3581)

- Added a convenience parameter to set the initial graphics or material in an Actor
  ```typescript
  const cloudSprite = cloud.toSprite();
  const swirlMaterial = game.graphicsContext.createMaterial({
    name: 'swirl',
    fragmentSource
  });
  const actor = new ex.Actor({
      graphic: cloudSprite,
      material: swirlMaterial
  });
  ```

5274 of 7188 branches covered (73.37%)

2 of 4 new or added lines in 1 file covered. (50.0%)

1 existing line in 1 file now uncovered.

14459 of 16325 relevant lines covered (88.57%)

24339.88 hits per line

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

78.65
/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 { 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/LifecycleEvents';
19
import type { Scene } from './Scene';
20
import { Logger } from './Util/Log';
21
import { Vector, vec } from './Math/vector';
22
import { BodyComponent } from './Collision/BodyComponent';
23
import type { Eventable } from './Interfaces/Evented';
24
import type { PointerEvents } from './Interfaces/PointerEventHandlers';
25
import { CollisionType } from './Collision/CollisionType';
26

27
import type { EntityEvents } from './EntityComponentSystem/Entity';
28
import { Entity } from './EntityComponentSystem/Entity';
29
import { TransformComponent } from './EntityComponentSystem/Components/TransformComponent';
30
import { MotionComponent } from './EntityComponentSystem/Components/MotionComponent';
31
import { GraphicsComponent } from './Graphics/GraphicsComponent';
32
import { Rectangle } from './Graphics/Rectangle';
33
import { ColliderComponent } from './Collision/ColliderComponent';
34
import { Shape } from './Collision/Colliders/Shape';
35
import { watch } from './Util/Watch';
36
import type { Collider, CollisionContact, CollisionGroup, Side } from './Collision/Index';
37
import { Circle } from './Graphics/Circle';
38
import type { PointerEvent } from './Input/PointerEvent';
39
import type { WheelEvent } from './Input/WheelEvent';
40
import { PointerComponent } from './Input/PointerComponent';
41
import { ActionsComponent } from './Actions/ActionsComponent';
42
import { CoordPlane } from './Math/coord-plane';
43
import type { EventKey, Handler, Subscription } from './EventEmitter';
44
import { EventEmitter } from './EventEmitter';
45
import type { Component } from './EntityComponentSystem';
46
import type { Graphic, Material } from './Graphics';
47

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

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

145
type ColliderArgs =
146
  | // custom collider
147
  {
148
      /**
149
       * Optionally supply a collider for an actor, if supplied ignores any supplied width/height
150
       *
151
       * No default graphigc is created in this case
152
       */
153
      collider?: Collider;
154

155
      width?: undefined;
156
      height?: undefined;
157
      radius?: undefined;
158
      color?: undefined;
159
    }
160
  // box collider
161
  | {
162
      /**
163
       * Optionally set the width of a box collider for the actor
164
       */
165
      width?: number;
166
      /**
167
       * Optionally set the height of a box collider for the actor
168
       */
169
      height?: number;
170

171
      /**
172
       * Optionally set the color of a rectangle graphic for the actor
173
       */
174
      color?: Color;
175

176
      collider?: undefined;
177
      radius?: undefined;
178
    }
179
  // circle collider
180
  | {
181
      /**
182
       * Optionally set the radius of the circle collider for the actor
183
       */
184
      radius?: number;
185

186
      /**
187
       * Optionally set the color on a circle graphic for the actor
188
       */
189
      color?: Color;
190

191
      collider?: undefined;
192
      width?: undefined;
193
      height?: undefined;
194
    };
195

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

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

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

270
  /**
271
   * Set defaults for all Actors
272
   */
273
  public static defaults = {
274
    anchor: Vector.Half
275
  };
276

277
  /**
278
   * The physics body the is associated with this actor. The body is the container for all physical properties, like position, velocity,
279
   * acceleration, mass, inertia, etc.
280
   */
281
  public body: BodyComponent;
282

283
  /**
284
   * Access the Actor's built in {@apilink TransformComponent}
285
   */
286
  public transform: TransformComponent;
287

288
  /**
289
   * Access the Actor's built in {@apilink MotionComponent}
290
   */
291
  public motion: MotionComponent;
292

293
  /**
294
   * Access to the Actor's built in {@apilink GraphicsComponent}
295
   */
296
  public graphics: GraphicsComponent;
297

298
  /**
299
   * Access to the Actor's built in {@apilink ColliderComponent}
300
   */
301
  public collider: ColliderComponent;
302

303
  /**
304
   * Access to the Actor's built in {@apilink PointerComponent} config
305
   */
306
  public pointer: PointerComponent;
307

308
  /**
309
   * Useful for quickly scripting actor behavior, like moving to a place, patrolling back and forth, blinking, etc.
310
   *
311
   *  Access to the Actor's built in {@apilink ActionsComponent} which forwards to the
312
   * {@apilink ActionContext | `Action context`} of the actor.
313
   */
314
  public actions: ActionsComponent;
315

316
  /**
317
   * Gets the position vector of the actor in pixels
318
   */
319
  public get pos(): Vector {
320
    return this.transform.pos;
607✔
321
  }
322

323
  /**
324
   * Sets the position vector of the actor in pixels
325
   */
326
  public set pos(thePos: Vector) {
327
    this.transform.pos = thePos.clone();
935✔
328
  }
329

330
  /**
331
   * Gets the position vector of the actor from the last frame
332
   */
333
  public get oldPos(): Vector {
334
    return this.body.oldPos;
2✔
335
  }
336

337
  /**
338
   * Gets the global position vector of the actor from the last frame
339
   */
340
  public get oldGlobalPos(): Vector {
341
    return this.body.oldGlobalPos;
2✔
342
  }
343

344
  /**
345
   * Sets the position vector of the actor in the last frame
346
   */
347
  public set oldPos(thePos: Vector) {
348
    this.body.oldPos.setTo(thePos.x, thePos.y);
×
349
  }
350

351
  /**
352
   * Gets the velocity vector of the actor in pixels/sec
353
   */
354
  public get vel(): Vector {
355
    return this.motion.vel;
79✔
356
  }
357

358
  /**
359
   * Sets the velocity vector of the actor in pixels/sec
360
   */
361
  public set vel(theVel: Vector) {
362
    this.motion.vel = theVel.clone();
905✔
363
  }
364

365
  /**
366
   * Gets the velocity vector of the actor from the last frame
367
   */
368
  public get oldVel(): Vector {
369
    return this.body.oldVel;
2✔
370
  }
371

372
  /**
373
   * Sets the velocity vector of the actor from the last frame
374
   */
375
  public set oldVel(theVel: Vector) {
376
    this.body.oldVel.setTo(theVel.x, theVel.y);
×
377
  }
378

379
  /**
380
   * Gets the acceleration vector of the actor in pixels/second/second. An acceleration pointing down such as (0, 100) may be
381
   * useful to simulate a gravitational effect.
382
   */
383
  public get acc(): Vector {
384
    return this.motion.acc;
6✔
385
  }
386

387
  /**
388
   * Sets the acceleration vector of teh actor in pixels/second/second
389
   */
390
  public set acc(theAcc: Vector) {
391
    this.motion.acc = theAcc.clone();
896✔
392
  }
393

394
  /**
395
   * Sets the acceleration of the actor from the last frame. This does not include the global acc {@apilink Physics.acc}.
396
   */
397
  public set oldAcc(theAcc: Vector) {
398
    this.body.oldAcc.setTo(theAcc.x, theAcc.y);
×
399
  }
400

401
  /**
402
   * Gets the acceleration of the actor from the last frame. This does not include the global acc {@apilink Physics.acc}.
403
   */
404
  public get oldAcc(): Vector {
405
    return this.body.oldAcc;
×
406
  }
407

408
  /**
409
   * Gets the rotation of the actor in radians. 1 radian = 180/PI Degrees.
410
   */
411
  public get rotation(): number {
412
    return this.transform.rotation;
84✔
413
  }
414

415
  /**
416
   * Sets the rotation of the actor in radians. 1 radian = 180/PI Degrees.
417
   */
418
  public set rotation(theAngle: number) {
419
    this.transform.rotation = theAngle;
902✔
420
  }
421

422
  /**
423
   * Gets the rotational velocity of the actor in radians/second
424
   */
425
  public get angularVelocity(): number {
426
    return this.motion.angularVelocity;
23✔
427
  }
428

429
  /**
430
   * Sets the rotational velocity of the actor in radians/sec
431
   */
432
  public set angularVelocity(angularVelocity: number) {
433
    this.motion.angularVelocity = angularVelocity;
893✔
434
  }
435

436
  public get scale(): Vector {
437
    return this.get(TransformComponent).scale;
79✔
438
  }
439

440
  public set scale(scale: Vector) {
441
    this.get(TransformComponent).scale = scale;
905✔
442
  }
443

444
  private _anchor: Vector = watch(Vector.Half, (v) => this._handleAnchorChange(v));
893✔
445
  /**
446
   * The anchor to apply all actor related transformations like rotation,
447
   * translation, and scaling. By default the anchor is in the center of
448
   * the actor. By default it is set to the center of the actor (.5, .5)
449
   *
450
   * An anchor of (.5, .5) will ensure that drawings are centered.
451
   *
452
   * Use `anchor.setTo` to set the anchor to a different point using
453
   * values between 0 and 1. For example, anchoring to the top-left would be
454
   * `Actor.anchor.setTo(0, 0)` and top-right would be `Actor.anchor.setTo(0, 1)`.
455
   */
456
  public get anchor(): Vector {
457
    return this._anchor;
1,544✔
458
  }
459

460
  public set anchor(vec: Vector) {
461
    this._anchor = watch(vec, (v) => this._handleAnchorChange(v));
915✔
462
    this._handleAnchorChange(vec);
915✔
463
  }
464

465
  private _handleAnchorChange(v: Vector) {
466
    if (this.graphics) {
915✔
467
      this.graphics.anchor = v;
22✔
468
    }
469
  }
470

471
  private _offset: Vector = watch(Vector.Zero, (v) => this._handleOffsetChange(v));
893✔
472
  /**
473
   * The offset in pixels to apply to all actor graphics
474
   *
475
   * Default offset of (0, 0)
476
   */
477
  public get offset(): Vector {
478
    return this._offset;
895✔
479
  }
480

481
  public set offset(vec: Vector) {
482
    this._offset = watch(vec, (v) => this._handleOffsetChange(v));
893✔
483
    this._handleOffsetChange(vec);
893✔
484
  }
485

486
  private _handleOffsetChange(v: Vector) {
487
    if (this.graphics) {
893!
488
      this.graphics.offset = v;
×
489
    }
490
  }
491

492
  /**
493
   * Indicates whether the actor is physically in the viewport
494
   */
495
  public get isOffScreen(): boolean {
496
    return this.hasTag('ex.offscreen');
13✔
497
  }
498

499
  /**
500
   * Convenience reference to the global logger
501
   */
502
  public logger: Logger = Logger.getInstance();
893✔
503

504
  /**
505
   * Draggable helper
506
   */
507
  private _draggable: boolean = false;
893✔
508
  private _dragging: boolean = false;
893✔
509

510
  private _pointerDragStartHandler = () => {
893✔
511
    this._dragging = true;
×
512
  };
513

514
  private _pointerDragEndHandler = () => {
893✔
515
    this._dragging = false;
×
516
  };
517

518
  private _pointerDragMoveHandler = (pe: PointerEvent) => {
893✔
519
    if (this._dragging) {
×
520
      this.pos = pe.worldPos;
×
521
    }
522
  };
523

524
  private _pointerDragLeaveHandler = (pe: PointerEvent) => {
893✔
525
    if (this._dragging) {
×
526
      this.pos = pe.worldPos;
×
527
    }
528
  };
529

530
  public get draggable(): boolean {
531
    return this._draggable;
×
532
  }
533

534
  public set draggable(isDraggable: boolean) {
535
    if (isDraggable) {
×
536
      if (isDraggable && !this._draggable) {
×
537
        this.events.on('pointerdragstart', this._pointerDragStartHandler);
×
538
        this.events.on('pointerdragend', this._pointerDragEndHandler);
×
539
        this.events.on('pointerdragmove', this._pointerDragMoveHandler);
×
540
        this.events.on('pointerdragleave', this._pointerDragLeaveHandler);
×
541
      } else if (!isDraggable && this._draggable) {
×
542
        this.events.off('pointerdragstart', this._pointerDragStartHandler);
×
543
        this.events.off('pointerdragend', this._pointerDragEndHandler);
×
544
        this.events.off('pointerdragmove', this._pointerDragMoveHandler);
×
545
        this.events.off('pointerdragleave', this._pointerDragLeaveHandler);
×
546
      }
547

548
      this._draggable = isDraggable;
×
549
    }
550
  }
551

552
  /**
553
   * Sets the color of the actor's current graphic
554
   */
555
  public get color(): Color {
556
    return this.graphics.color;
21✔
557
  }
558
  public set color(v: Color) {
559
    this.graphics.color = v;
171✔
560
  }
561

562
  // #endregion
563

564
  /**
565
   *
566
   * @param config
567
   */
568
  constructor(config?: ActorArgs) {
569
    super();
893✔
570

571
    const {
572
      name,
573
      x,
574
      y,
575
      pos,
576
      coordPlane,
577
      scale,
578
      width,
579
      height,
580
      radius,
581
      collider,
582
      vel,
583
      acc,
584
      rotation,
585
      angularVelocity,
586
      z,
587
      color,
588
      visible,
589
      opacity,
590
      anchor,
591
      offset,
592
      collisionType,
593
      collisionGroup,
594
      graphic,
595
      material
596
    } = {
893✔
597
      ...config
598
    };
599

600
    this.name = name ?? this.name;
893✔
601
    this.anchor = anchor ?? Actor.defaults.anchor.clone();
893✔
602
    this.offset = offset ?? Vector.Zero;
893✔
603
    this.transform = new TransformComponent();
893✔
604
    this.addComponent(this.transform);
893✔
605
    this.pos = pos ?? vec(x ?? 0, y ?? 0);
893✔
606
    this.rotation = rotation ?? 0;
893✔
607
    this.scale = scale ?? vec(1, 1);
893✔
608
    this.z = z ?? 0;
893✔
609
    this.transform.coordPlane = coordPlane ?? CoordPlane.World;
893✔
610

611
    this.pointer = new PointerComponent();
893✔
612
    this.addComponent(this.pointer);
893✔
613

614
    this.graphics = new GraphicsComponent({
893✔
615
      anchor: this.anchor,
616
      offset: this.offset,
617
      opacity: opacity
618
    });
619
    this.addComponent(this.graphics);
893✔
620

621
    this.motion = new MotionComponent();
893✔
622
    this.addComponent(this.motion);
893✔
623
    this.vel = vel ?? Vector.Zero;
893✔
624
    this.acc = acc ?? Vector.Zero;
893✔
625
    this.angularVelocity = angularVelocity ?? 0;
893✔
626

627
    this.actions = new ActionsComponent();
893✔
628
    this.addComponent(this.actions);
893✔
629

630
    this.body = new BodyComponent();
893✔
631
    this.addComponent(this.body);
893✔
632
    this.body.collisionType = collisionType ?? CollisionType.Passive;
893✔
633
    if (collisionGroup) {
893✔
634
      this.body.group = collisionGroup;
21✔
635
    }
636

637
    if (color) {
893✔
638
      this.color = color;
140✔
639
    }
640

641
    if (collider) {
893✔
642
      this.collider = new ColliderComponent(collider);
8✔
643
      this.addComponent(this.collider);
8✔
644
    } else if (radius) {
885✔
645
      this.collider = new ColliderComponent(Shape.Circle(radius));
6✔
646
      this.addComponent(this.collider);
6✔
647

648
      if (color) {
6✔
649
        this.graphics.add(
2✔
650
          new Circle({
651
            color: color,
652
            radius
653
          })
654
        );
655
      }
656
    } else {
657
      if (width > 0 && height > 0) {
879✔
658
        this.collider = new ColliderComponent(Shape.Box(width, height, this.anchor));
562✔
659
        this.addComponent(this.collider);
562✔
660

661
        if (color && width && height) {
562✔
662
          this.graphics.add(
134✔
663
            new Rectangle({
664
              color: color,
665
              width,
666
              height
667
            })
668
          );
669
        }
670
      } else {
671
        this.collider = new ColliderComponent();
317✔
672
        this.addComponent(this.collider); // no collider
317✔
673
      }
674
    }
675

676
    this.graphics.isVisible = visible ?? true;
893✔
677
    if (graphic) {
893!
NEW
678
      this.graphics.use(graphic);
×
679
    }
680
    if (material) {
893!
NEW
681
      this.graphics.material = material;
×
682
    }
683
  }
684

685
  public clone(): Actor {
686
    const clone = new Actor({
2✔
687
      color: this.color.clone(),
688
      anchor: this.anchor.clone(),
689
      offset: this.offset.clone()
690
    });
691
    clone.clearComponents();
2✔
692
    clone.processComponentRemoval();
2✔
693

694
    // Clone builtins, order is important, same as ctor
695
    clone.addComponent((clone.transform = this.transform.clone() as TransformComponent), true);
2✔
696
    clone.addComponent((clone.pointer = this.pointer.clone() as PointerComponent), true);
2✔
697
    clone.addComponent((clone.graphics = this.graphics.clone() as GraphicsComponent), true);
2✔
698
    clone.addComponent((clone.motion = this.motion.clone() as MotionComponent), true);
2✔
699
    clone.addComponent((clone.actions = this.actions.clone() as ActionsComponent), true);
2✔
700
    clone.addComponent((clone.body = this.body.clone() as BodyComponent), true);
2✔
701
    if (this.collider.get()) {
2✔
702
      clone.addComponent((clone.collider = this.collider.clone() as ColliderComponent), true);
1✔
703
    }
704

705
    const builtInComponents: Component[] = [
2✔
706
      this.transform,
707
      this.pointer,
708
      this.graphics,
709
      this.motion,
710
      this.actions,
711
      this.body,
712
      this.collider
713
    ];
714

715
    // Clone non-builtin the current actors components
716
    const components = this.getComponents();
2✔
717
    for (const c of components) {
2✔
718
      if (!builtInComponents.includes(c)) {
14!
719
        clone.addComponent(c.clone(), true);
×
720
      }
721
    }
722
    return clone;
2✔
723
  }
724

725
  /**
726
   * `onInitialize` is called before the first update of the actor. This method is meant to be
727
   * overridden. This is where initialization of child actors should take place.
728
   *
729
   * Synonymous with the event handler `.on('initialize', (evt) => {...})`
730
   */
731
  public onInitialize(engine: Engine): void {
732
    // Override me
733
  }
734

735
  /**
736
   * Initializes this actor and all it's child actors, meant to be called by the Scene before first update not by users of Excalibur.
737
   *
738
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
739
   * @internal
740
   */
741
  public _initialize(engine: Engine) {
742
    super._initialize(engine);
2,394✔
743
    for (const child of this.children) {
2,394✔
744
      child._initialize(engine);
15✔
745
    }
746
  }
747

748
  // #region Events
749
  public emit<TEventName extends EventKey<ActorEvents>>(eventName: TEventName, event: ActorEvents[TEventName]): void;
750
  public emit(eventName: string, event?: any): void;
751
  public emit<TEventName extends EventKey<ActorEvents> | string>(eventName: TEventName, event?: any): void {
752
    this.events.emit(eventName, event);
532✔
753
  }
754

755
  public on<TEventName extends EventKey<ActorEvents>>(eventName: TEventName, handler: Handler<ActorEvents[TEventName]>): Subscription;
756
  public on(eventName: string, handler: Handler<unknown>): Subscription;
757
  public on<TEventName extends EventKey<ActorEvents> | string>(eventName: TEventName, handler: Handler<any>): Subscription {
758
    return this.events.on(eventName, handler);
72✔
759
  }
760

761
  public once<TEventName extends EventKey<ActorEvents>>(eventName: TEventName, handler: Handler<ActorEvents[TEventName]>): Subscription;
762
  public once(eventName: string, handler: Handler<unknown>): Subscription;
763
  public once<TEventName extends EventKey<ActorEvents> | string>(eventName: TEventName, handler: Handler<any>): Subscription {
764
    return this.events.once(eventName, handler);
1✔
765
  }
766

767
  public off<TEventName extends EventKey<ActorEvents>>(eventName: TEventName, handler: Handler<ActorEvents[TEventName]>): void;
768
  public off(eventName: string, handler: Handler<unknown>): void;
769
  public off(eventName: string): void;
770
  public off<TEventName extends EventKey<ActorEvents> | string>(eventName: TEventName, handler?: Handler<any>): void {
771
    this.events.off(eventName, handler);
×
772
  }
773

774
  // #endregion
775

776
  /**
777
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
778
   *
779
   * Internal _prekill handler for {@apilink onPreKill} lifecycle event
780
   * @internal
781
   */
782
  public _prekill(scene: Scene) {
783
    this.events.emit('prekill', new PreKillEvent(this));
22✔
784
    this.onPreKill(scene);
22✔
785
  }
786

787
  /**
788
   * Safe to override onPreKill lifecycle event handler. Synonymous with `.on('prekill', (evt) =>{...})`
789
   *
790
   * `onPreKill` is called directly before an actor is killed and removed from its current {@apilink Scene}.
791
   */
792
  public onPreKill(scene: Scene) {
793
    // Override me
794
  }
795

796
  /**
797
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
798
   *
799
   * Internal _prekill handler for {@apilink onPostKill} lifecycle event
800
   * @internal
801
   */
802
  public _postkill(scene: Scene) {
803
    this.events.emit('postkill', new PostKillEvent(this));
22✔
804
    this.onPostKill(scene);
22✔
805
  }
806

807
  /**
808
   * Safe to override onPostKill lifecycle event handler. Synonymous with `.on('postkill', (evt) => {...})`
809
   *
810
   * `onPostKill` is called directly after an actor is killed and remove from its current {@apilink Scene}.
811
   */
812
  public onPostKill(scene: Scene) {
813
    // Override me
814
  }
815

816
  /**
817
   * If the current actor is a member of the scene, this will remove
818
   * it from the scene graph. It will no longer be drawn or updated.
819
   */
820
  public kill() {
821
    if (this.scene) {
23✔
822
      this._prekill(this.scene);
22✔
823
      this.events.emit('kill', new KillEvent(this));
22✔
824
      super.kill();
22✔
825
      this._postkill(this.scene);
22✔
826
    } else {
827
      if (process.env.NODE_ENV === 'development') {
1!
828
        this.logger.warn(`Cannot kill actor named "${this.name}", it was never added to the Scene`);
1✔
829
      }
830
    }
831
  }
832

833
  /**
834
   * If the current actor is killed, it will now not be killed.
835
   */
836
  public unkill() {
837
    this.isActive = true;
×
838
  }
839

840
  /**
841
   * Indicates wether the actor has been killed.
842
   */
843
  public isKilled(): boolean {
844
    return !this.isActive;
5✔
845
  }
846

847
  /**
848
   * Gets the z-index of an actor. The z-index determines the relative order an actor is drawn in.
849
   * Actors with a higher z-index are drawn on top of actors with a lower z-index
850
   */
851
  public get z(): number {
852
    return this.get(TransformComponent).z;
85✔
853
  }
854

855
  /**
856
   * Sets the z-index of an actor and updates it in the drawing list for the scene.
857
   * The z-index determines the relative order an actor is drawn in.
858
   * Actors with a higher z-index are drawn on top of actors with a lower z-index
859
   * @param newZ new z-index to assign
860
   */
861
  public set z(newZ: number) {
862
    this.get(TransformComponent).z = newZ;
906✔
863
  }
864

865
  /**
866
   * Get the center point of an actor (global position)
867
   */
868
  public get center(): Vector {
869
    const globalPos = this.getGlobalPos();
27✔
870
    return new Vector(
27✔
871
      globalPos.x + this.width / 2 - this.anchor.x * this.width,
872
      globalPos.y + this.height / 2 - this.anchor.y * this.height
873
    );
874
  }
875

876
  /**
877
   * Get the local center point of an actor
878
   */
879
  public get localCenter(): Vector {
880
    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✔
881
  }
882

883
  public get width() {
884
    return this.collider.localBounds.width * this.getGlobalScale().x;
85✔
885
  }
886

887
  public get height() {
888
    return this.collider.localBounds.height * this.getGlobalScale().y;
85✔
889
  }
890

891
  /**
892
   * Gets this actor's rotation taking into account any parent relationships
893
   * @returns Rotation angle in radians
894
   * @deprecated Use {@apilink globalRotation} instead
895
   */
896
  public getGlobalRotation(): number {
897
    return this.get(TransformComponent).globalRotation;
1✔
898
  }
899

900
  /**
901
   * The actor's rotation (in radians) taking into account any parent relationships
902
   */
903
  public get globalRotation(): number {
904
    return this.get(TransformComponent).globalRotation;
×
905
  }
906

907
  /**
908
   * Gets an actor's world position taking into account parent relationships, scaling, rotation, and translation
909
   * @returns Position in world coordinates
910
   * @deprecated Use {@apilink globalPos} instead
911
   */
912
  public getGlobalPos(): Vector {
913
    return this.get(TransformComponent).globalPos;
45✔
914
  }
915

916
  /**
917
   * The actor's world position taking into account parent relationships, scaling, rotation, and translation
918
   */
919
  public get globalPos(): Vector {
920
    return this.get(TransformComponent).globalPos;
4✔
921
  }
922

923
  /**
924
   * Gets the global scale of the Actor
925
   * @deprecated Use {@apilink globalScale} instead
926
   */
927
  public getGlobalScale(): Vector {
928
    return this.get(TransformComponent).globalScale;
170✔
929
  }
930

931
  /**
932
   * The global scale of the Actor
933
   */
934
  public get globalScale(): Vector {
935
    return this.get(TransformComponent).globalScale;
×
936
  }
937

938
  /**
939
   * The global z-index of the actor
940
   */
941
  public get globalZ(): number {
942
    return this.get(TransformComponent).globalZ;
×
943
  }
944

945
  // #region Collision
946

947
  /**
948
   * Tests whether the x/y specified are contained in the actor
949
   * @param x  X coordinate to test (in world coordinates)
950
   * @param y  Y coordinate to test (in world coordinates)
951
   * @param recurse checks whether the x/y are contained in any child actors (if they exist).
952
   */
953
  public contains(x: number, y: number, recurse: boolean = false): boolean {
11✔
954
    const point = vec(x, y);
18✔
955
    const collider = this.get(ColliderComponent);
18✔
956
    collider.update();
18✔
957
    const geom = collider.get();
18✔
958
    if (!geom) {
18!
959
      return false;
×
960
    }
961
    const containment = geom.contains(point);
18✔
962

963
    if (recurse) {
18✔
964
      return (
7✔
965
        containment ||
12✔
966
        this.children.some((child: Actor) => {
967
          return child.contains(x, y, true);
4✔
968
        })
969
      );
970
    }
971

972
    return containment;
11✔
973
  }
974

975
  /**
976
   * Returns true if the two actor.collider's surfaces are less than or equal to the distance specified from each other
977
   * @param actor     Actor to test
978
   * @param distance  Distance in pixels to test
979
   */
980
  public within(actor: Actor, distance: number): boolean {
981
    const collider = this.get(ColliderComponent);
×
982
    const otherCollider = actor.get(ColliderComponent);
×
983
    const me = collider.get();
×
984
    const other = otherCollider.get();
×
985
    if (me && other) {
×
986
      return me.getClosestLineBetween(other).getLength() <= distance;
×
987
    }
988
    return false;
×
989
  }
990

991
  // #endregion
992

993
  // #region Update
994

995
  /**
996
   * Called by the Engine, updates the state of the actor
997
   * @internal
998
   * @param engine The reference to the current game engine
999
   * @param elapsed  The time elapsed since the last update in milliseconds
1000
   */
1001
  public update(engine: Engine, elapsed: number) {
1002
    this._initialize(engine);
2,175✔
1003
    this._add(engine);
2,175✔
1004
    this._preupdate(engine, elapsed);
2,175✔
1005
    this._postupdate(engine, elapsed);
2,175✔
1006
    this._remove(engine);
2,175✔
1007
  }
1008

1009
  /**
1010
   * Safe to override onPreUpdate lifecycle event handler. Synonymous with `.on('preupdate', (evt) =>{...})`
1011
   *
1012
   * `onPreUpdate` is called directly before an actor is updated.
1013
   * @param engine The reference to the current game engine
1014
   * @param elapsed  The time elapsed since the last update in milliseconds
1015
   */
1016
  public onPreUpdate(engine: Engine, elapsed: number): void {
1017
    // Override me
1018
  }
1019

1020
  /**
1021
   * Safe to override onPostUpdate lifecycle event handler. Synonymous with `.on('postupdate', (evt) =>{...})`
1022
   *
1023
   * `onPostUpdate` is called directly after an actor is updated.
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 onPostUpdate(engine: Engine, elapsed: number): void {
1028
    // Override me
1029
  }
1030

1031
  /**
1032
   * Fires before every collision resolution for a confirmed contact
1033
   * @param self
1034
   * @param other
1035
   * @param side
1036
   * @param contact
1037
   */
1038
  public onPreCollisionResolve(self: Collider, other: Collider, side: Side, contact: CollisionContact) {
1039
    // Override me
1040
  }
1041

1042
  /**
1043
   * Fires after every resolution for a confirmed contact.
1044
   * @param self
1045
   * @param other
1046
   * @param side
1047
   * @param contact
1048
   */
1049
  public onPostCollisionResolve(self: Collider, other: Collider, side: Side, contact: CollisionContact) {
1050
    // Override me
1051
  }
1052

1053
  /**
1054
   * Fires once when 2 entities with a ColliderComponent first start colliding or touching, if the Colliders stay in contact this
1055
   * does not continue firing until they separate and re-collide.
1056
   * @param self
1057
   * @param other
1058
   * @param side
1059
   * @param contact
1060
   */
1061
  public onCollisionStart(self: Collider, other: Collider, side: Side, contact: CollisionContact) {
1062
    // Override me
1063
  }
1064

1065
  /**
1066
   * Fires once when 2 entities with a ColliderComponent separate after having been in contact.
1067
   * @param self
1068
   * @param other
1069
   * @param side
1070
   * @param lastContact
1071
   */
1072
  public onCollisionEnd(self: Collider, other: Collider, side: Side, lastContact: CollisionContact) {
1073
    // Override me
1074
  }
1075

1076
  /**
1077
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
1078
   *
1079
   * Internal _preupdate handler for {@apilink onPreUpdate} lifecycle event
1080
   * @param engine The reference to the current game engine
1081
   * @param elapsed  The time elapsed since the last update in milliseconds
1082
   * @internal
1083
   */
1084
  public _preupdate(engine: Engine, elapsed: number): void {
1085
    this.events.emit('preupdate', new PreUpdateEvent(engine, elapsed, this));
2,175✔
1086
    this.onPreUpdate(engine, elapsed);
2,175✔
1087
  }
1088

1089
  /**
1090
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
1091
   *
1092
   * Internal _preupdate handler for {@apilink onPostUpdate} lifecycle event
1093
   * @param engine The reference to the current game engine
1094
   * @param elapsed  The time elapsed since the last update in milliseconds
1095
   * @internal
1096
   */
1097
  public _postupdate(engine: Engine, elapsed: number): void {
1098
    this.events.emit('postupdate', new PostUpdateEvent(engine, elapsed, this));
2,175✔
1099
    this.onPostUpdate(engine, elapsed);
2,175✔
1100
  }
1101

1102
  // endregion
1103
}
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