Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/excaliburjs/Excalibur. 🎮 Your friendly TypeScript 2D game engine for the web 🗡️ excaliburjs.com
excalibur excaliburjs game-development game-engine game-framework gamedev games html5-canvas typescript
Something went wrong. Try again.
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122import type { PostCollisionEvent, PreCollisionEvent, CollisionStartEvent, CollisionEndEvent, EnterViewportEvent, ExitViewportEvent, PreDrawEvent, PostDrawEvent, PreDebugDrawEvent, PostDebugDrawEvent, ActionStartEvent, ActionCompleteEvent, PostTransformDrawEvent, PreTransformDrawEvent} from './events';import { type KillEvent, PreUpdateEvent, PostUpdateEvent, PostKillEvent, PreKillEvent } from './events';import type { Engine } from './engine';import type { Color } from './color';import type { CanInitialize, CanUpdate, CanBeKilled } from './interfaces/lifecycle-events';import type { Scene } from './scene';import { Logger } from './util/log';import { Vector, vec } from './math/vector';import { BodyComponent } from './collision/body-component';import type { Eventable } from './interfaces/evented';import type { PointerEvents } from './interfaces/pointer-event-handlers';import { CollisionType } from './collision/collision-type';import { PauseComponent } from './entity-component-system/components/pause-component';
import type { EntityEvents } from './entity-component-system/entity';import { Entity } from './entity-component-system/entity';import { TransformComponent } from './entity-component-system/components/transform-component';import { MotionComponent } from './entity-component-system/components/motion-component';import { GraphicsComponent } from './graphics/graphics-component';import { Rectangle } from './graphics/rectangle';import { ColliderComponent } from './collision/collider-component';import { Colliders } from './collision/colliders/colliders';import { watch } from './util/watch';import type { Collider, CollisionContact, CollisionGroup, Side } from './collision/index';import { Circle } from './graphics/circle';import type { PointerEvent } from './input/pointer-event';import type { WheelEvent } from './input/wheel-event';import { PointerComponent } from './input/pointer-component';import { ActionsComponent } from './actions/actions-component';import { CoordPlane } from './math/coord-plane';import type { EventKey, Handler, Subscription } from './event-emitter';import { EventEmitter } from './event-emitter';import type { Component } from './entity-component-system';import type { Graphic, Material } from './graphics';
/** * Type guard for checking if something is an Actor * @param x */export function isActor(x: any): x is Actor { return x instanceof Actor;}
/** * Actor constructor options */export type ActorOptions = ColliderArgs & { /** * Optionally set the name of the actor, default is 'anonymous' */ name?: string; /** * Optionally set the x position of the actor, default is 0 */ x?: number; /** * Optionally set the y position of the actor, default is 0 */ y?: number; /** * Optionally set the (x, y) position of the actor as a vector, default is (0, 0) */ pos?: Vector; /** * Optionally set the coordinate plane of the actor, default is {@apilink CoordPlane.World} meaning actor is subject to camera positioning */ coordPlane?: CoordPlane; /** * Optionally set the velocity of the actor in pixels/sec */ vel?: Vector; /** * Optionally set the acceleration of the actor in pixels/sec^2 */ acc?: Vector; /** * Optionally se the rotation in radians (180 degrees = Math.PI radians) */ rotation?: number; /** * Optionally set the angular velocity of the actor in radians/sec (180 degrees = Math.PI radians) */ angularVelocity?: number; /** * Optionally set the scale of the actor's transform */ scale?: Vector; /** * Optionally set the z index of the actor, default is 0 */ z?: number; /** * Optionally set the color of an actor, only used if no graphics are present * If a width/height or a radius was set a default graphic will be added */ color?: Color; /** * Optionally set the default graphic */ graphic?: Graphic; /** * Optionally set the default material */ material?: Material; /** * Optionally set the opacity of an actor, only used if no graphics are present */ opacity?: number; /** * Optionally set the visibility of the actor */ visible?: boolean; /** * Optionally set the anchor for graphics in the actor */ anchor?: Vector; /** * Optionally set the pixel offset for graphics in the actor */ offset?: Vector; /** * Optionally set the collision type */ collisionType?: CollisionType;
/** * Optionally supply a {@apilink CollisionGroup} */ collisionGroup?: CollisionGroup; /** * Optionally set if the actor can be paused * * (default is true) */ canPause?: boolean;};
type ColliderArgs = | // custom collider { /** * Optionally supply a collider for an actor, if supplied ignores any supplied width/height * * No default graphigc is created in this case */ collider?: Collider;
width?: undefined; height?: undefined; radius?: undefined; color?: undefined; } // box collider | { /** * Optionally set the width of a box collider for the actor */ width?: number; /** * Optionally set the height of a box collider for the actor */ height?: number;
/** * Optionally set the color of a rectangle graphic for the actor */ color?: Color;
collider?: undefined; radius?: undefined; } // circle collider | { /** * Optionally set the radius of the circle collider for the actor */ radius?: number;
/** * Optionally set the color on a circle graphic for the actor */ color?: Color;
collider?: undefined; width?: undefined; height?: undefined; };
/** * Actor constructor options * @deprecated use ActorOptions */export type ActorArgs = ActorOptions;
export interface ActorEvents extends EntityEvents { collisionstart: CollisionStartEvent; collisionend: CollisionEndEvent; precollision: PreCollisionEvent; postcollision: PostCollisionEvent; kill: KillEvent; prekill: PreKillEvent; postkill: PostKillEvent; predraw: PreDrawEvent; postdraw: PostDrawEvent; pretransformdraw: PreTransformDrawEvent; posttransformdraw: PostTransformDrawEvent; predebugdraw: PreDebugDrawEvent; postdebugdraw: PostDebugDrawEvent; pointerup: PointerEvent; pointerdown: PointerEvent; pointerenter: PointerEvent; pointerleave: PointerEvent; pointermove: PointerEvent; pointercancel: PointerEvent; pointerwheel: WheelEvent; pointerdragstart: PointerEvent; pointerdragend: PointerEvent; pointerdragenter: PointerEvent; pointerdragleave: PointerEvent; pointerdragmove: PointerEvent; enterviewport: EnterViewportEvent; exitviewport: ExitViewportEvent; actionstart: ActionStartEvent; actioncomplete: ActionCompleteEvent;}
export const ActorEvents = { CollisionStart: 'collisionstart', CollisionEnd: 'collisionend', PreCollision: 'precollision', PostCollision: 'postcollision', Kill: 'kill', PreKill: 'prekill', PostKill: 'postkill', PreDraw: 'predraw', PostDraw: 'postdraw', PreTransformDraw: 'pretransformdraw', PostTransformDraw: 'posttransformdraw', PreDebugDraw: 'predebugdraw', PostDebugDraw: 'postdebugdraw', PointerUp: 'pointerup', PointerDown: 'pointerdown', PointerEnter: 'pointerenter', PointerLeave: 'pointerleave', PointerMove: 'pointermove', PointerCancel: 'pointercancel', Wheel: 'pointerwheel', PointerDrag: 'pointerdragstart', PointerDragEnd: 'pointerdragend', PointerDragEnter: 'pointerdragenter', PointerDragLeave: 'pointerdragleave', PointerDragMove: 'pointerdragmove', EnterViewport: 'enterviewport', ExitViewport: 'exitviewport', ActionStart: 'actionstart', ActionComplete: 'actioncomplete'} as const;
/** * The most important primitive in Excalibur is an `Actor`. Anything that * can move on the screen, collide with another `Actor`, respond to events, * or interact with the current scene, must be an actor. An `Actor` **must** * be part of a {@apilink Scene} for it to be drawn to the screen. */export class Actor extends Entity implements Eventable, PointerEvents, CanInitialize, CanUpdate, CanBeKilled { public override events = new EventEmitter<ActorEvents>(); // #region Properties
/** * Set defaults for all Actors */ public static defaults = { anchor: Vector.Half };
/** * The physics body the is associated with this actor. The body is the container for all physical properties, like position, velocity, * acceleration, mass, inertia, etc. */ public body: BodyComponent;
/** * The physics body the is associated with this actor. The body is the container for all physical properties, like position, velocity, * acceleration, mass, inertia, etc. */ public paused: PauseComponent;
/** * Access the Actor's built in {@apilink TransformComponent} */ public transform: TransformComponent;
/** * Access the Actor's built in {@apilink MotionComponent} */ public motion: MotionComponent;
/** * Access to the Actor's built in {@apilink GraphicsComponent} */ public graphics: GraphicsComponent;
/** * Access to the Actor's built in {@apilink ColliderComponent} */ public collider: ColliderComponent;
/** * Access to the Actor's built in {@apilink PointerComponent} config */ public pointer: PointerComponent;
/** * Useful for quickly scripting actor behavior, like moving to a place, patrolling back and forth, blinking, etc. * * Access to the Actor's built in {@apilink ActionsComponent} which forwards to the * {@apilink ActionContext | `Action context`} of the actor. */ public actions: ActionsComponent;
/** * Gets the position vector of the actor in pixels */ public get pos(): Vector { return this.transform.pos; }
/** * Sets the position vector of the actor in pixels */ public set pos(thePos: Vector) { this.transform.pos = thePos.clone(); }
/** * Gets the position vector of the actor from the last frame */ public get oldPos(): Vector { return this.body.oldPos; }
/** * Gets the global position vector of the actor from the last frame */ public get oldGlobalPos(): Vector { return this.body.oldGlobalPos; }
/** * Sets the position vector of the actor in the last frame */ public set oldPos(thePos: Vector) { this.body.oldPos.setTo(thePos.x, thePos.y); }
/** * Gets the velocity vector of the actor in pixels/sec */ public get vel(): Vector { return this.motion.vel; }
/** * Sets the velocity vector of the actor in pixels/sec */ public set vel(theVel: Vector) { this.motion.vel = theVel.clone(); }
/** * Gets the velocity vector of the actor from the last frame */ public get oldVel(): Vector { return this.body.oldVel; }
/** * Sets the velocity vector of the actor from the last frame */ public set oldVel(theVel: Vector) { this.body.oldVel.setTo(theVel.x, theVel.y); }
/** * Gets the acceleration vector of the actor in pixels/second/second. An acceleration pointing down such as (0, 100) may be * useful to simulate a gravitational effect. */ public get acc(): Vector { return this.motion.acc; }
/** * Sets the acceleration vector of teh actor in pixels/second/second */ public set acc(theAcc: Vector) { this.motion.acc = theAcc.clone(); }
/** * Sets the acceleration of the actor from the last frame. This does not include the global acc {@apilink Physics.acc}. */ public set oldAcc(theAcc: Vector) { this.body.oldAcc.setTo(theAcc.x, theAcc.y); }
/** * Gets the acceleration of the actor from the last frame. This does not include the global acc {@apilink Physics.acc}. */ public get oldAcc(): Vector { return this.body.oldAcc; }
/** * Gets the rotation of the actor in radians. 1 radian = 180/PI Degrees. */ public get rotation(): number { return this.transform.rotation; }
/** * Sets the rotation of the actor in radians. 1 radian = 180/PI Degrees. */ public set rotation(theAngle: number) { this.transform.rotation = theAngle; }
/** * Gets the rotational velocity of the actor in radians/second */ public get angularVelocity(): number { return this.motion.angularVelocity; }
/** * Sets the rotational velocity of the actor in radians/sec */ public set angularVelocity(angularVelocity: number) { this.motion.angularVelocity = angularVelocity; }
public get scale(): Vector { return this.get(TransformComponent).scale; }
public set scale(scale: Vector) { this.get(TransformComponent).scale = scale; }
public get canPause(): boolean { return this.paused.canPause; }
public set canPause(canPause: boolean) { this.paused.canPause = canPause; }
public get isPaused(): boolean { return this.paused.paused; }
private _anchor: Vector = watch(Vector.Half, (v) => this._handleAnchorChange(v)); /** * The anchor to apply all actor related transformations like rotation, * translation, and scaling. By default the anchor is in the center of * the actor. By default it is set to the center of the actor (.5, .5) * * An anchor of (.5, .5) will ensure that drawings are centered. * * Use `anchor.setTo` to set the anchor to a different point using * values between 0 and 1. For example, anchoring to the top-left would be * `Actor.anchor.setTo(0, 0)` and top-right would be `Actor.anchor.setTo(0, 1)`. */ public get anchor(): Vector { return this._anchor; }
public set anchor(vec: Vector) { this._anchor = watch(vec, (v) => this._handleAnchorChange(v)); this._handleAnchorChange(vec); }
private _handleAnchorChange(v: Vector) { if (this.graphics) { this.graphics.anchor = v; } }
private _offset: Vector = watch(Vector.Zero, (v) => this._handleOffsetChange(v)); /** * The offset in pixels to apply to all actor graphics * * Default offset of (0, 0) */ public get offset(): Vector { return this._offset; }
public set offset(vec: Vector) { this._offset = watch(vec, (v) => this._handleOffsetChange(v)); this._handleOffsetChange(vec); }
private _handleOffsetChange(v: Vector) { if (this.graphics) { this.graphics.offset = v; } }
/** * Indicates whether the actor is physically in the viewport */ public get isOffScreen(): boolean { return this.hasTag('ex.offscreen'); }
/** * Convenience reference to the global logger */ public logger: Logger = Logger.getInstance();
/** * Draggable helper */ private _draggable: boolean = false; private _dragging: boolean = false;
private _pointerDragStartHandler = () => { this._dragging = true; };
private _pointerDragEndHandler = () => { this._dragging = false; };
private _pointerDragMoveHandler = (pe: PointerEvent) => { if (this._dragging) { this.pos = pe.worldPos; } };
private _pointerDragLeaveHandler = (pe: PointerEvent) => { if (this._dragging) { this.pos = pe.worldPos; } };
public get draggable(): boolean { return this._draggable; }
public set draggable(isDraggable: boolean) { if (isDraggable) { if (isDraggable && !this._draggable) { this.events.on('pointerdragstart', this._pointerDragStartHandler); this.events.on('pointerdragend', this._pointerDragEndHandler); this.events.on('pointerdragmove', this._pointerDragMoveHandler); this.events.on('pointerdragleave', this._pointerDragLeaveHandler); } else if (!isDraggable && this._draggable) { this.events.off('pointerdragstart', this._pointerDragStartHandler); this.events.off('pointerdragend', this._pointerDragEndHandler); this.events.off('pointerdragmove', this._pointerDragMoveHandler); this.events.off('pointerdragleave', this._pointerDragLeaveHandler); }
this._draggable = isDraggable; } }
/** * Sets the color of the actor's current graphic */ public get color(): Color { return this.graphics.color!; } public set color(v: Color) { this.graphics.color = v; }
// #endregion
/** * * @param config */ constructor(config?: ActorOptions) { super();
const { name, x, y, pos, coordPlane, scale, width, height, radius, collider, vel, acc, rotation, angularVelocity, z, color, visible, opacity, anchor, offset, collisionType, collisionGroup, graphic, material, canPause } = { ...config! };
this.name = name ?? this.name; this.anchor = anchor ?? Actor.defaults.anchor.clone(); this.offset = offset ?? Vector.Zero; this.transform = new TransformComponent(); this.addComponent(this.transform); this.pos = pos ?? vec(x ?? 0, y ?? 0); this.rotation = rotation ?? 0; this.scale = scale ?? vec(1, 1); this.z = z ?? 0; this.transform.coordPlane = coordPlane ?? CoordPlane.World;
this.pointer = new PointerComponent(); this.addComponent(this.pointer);
this.graphics = new GraphicsComponent({ anchor: this.anchor, offset: this.offset, opacity: opacity }); this.addComponent(this.graphics);
this.motion = new MotionComponent(); this.addComponent(this.motion); this.vel = vel ?? Vector.Zero; this.acc = acc ?? Vector.Zero; this.angularVelocity = angularVelocity ?? 0;
this.actions = new ActionsComponent(); this.addComponent(this.actions);
this.body = new BodyComponent(); this.addComponent(this.body); this.body.collisionType = collisionType ?? CollisionType.Passive; if (collisionGroup) { this.body.group = collisionGroup; }
this.paused = new PauseComponent({ canPause }); this.addComponent(this.paused);
if (color) { this.color = color; }
if (collider) { this.collider = new ColliderComponent(collider); this.addComponent(this.collider); } else if (radius) { this.collider = new ColliderComponent(Colliders.Circle(radius)); this.addComponent(this.collider);
if (color) { this.graphics.add( new Circle({ color: color, radius }) ); } } else { if (width! > 0 && height! > 0) { this.collider = new ColliderComponent(Colliders.Box(width!, height!, this.anchor)); this.addComponent(this.collider);
if (color && width && height) { this.graphics.add( new Rectangle({ color: color, width, height }) ); } } else { this.collider = new ColliderComponent(); this.addComponent(this.collider); // no collider } }
this.graphics.isVisible = visible ?? true; if (graphic) { this.graphics.use(graphic); } if (material) { this.graphics.material = material; } }
public override clone(): Actor { const clone = new Actor({ color: this.color.clone(), anchor: this.anchor.clone(), offset: this.offset.clone() }); clone.clearComponents(); clone.processComponentRemoval();
// Clone builtins, order is important, same as ctor clone.addComponent((clone.transform = this.transform.clone() as TransformComponent), true); clone.addComponent((clone.pointer = this.pointer.clone() as PointerComponent), true); clone.addComponent((clone.graphics = this.graphics.clone() as GraphicsComponent), true); clone.addComponent((clone.motion = this.motion.clone() as MotionComponent), true); clone.addComponent((clone.actions = this.actions.clone() as ActionsComponent), true); clone.addComponent((clone.body = this.body.clone() as BodyComponent), true); if (this.collider.get()) { clone.addComponent((clone.collider = this.collider.clone() as ColliderComponent), true); }
const builtInComponents: Component[] = [ this.transform, this.pointer, this.graphics, this.motion, this.actions, this.body, this.collider ];
// Clone non-builtin the current actors components const components = this.getComponents(); for (const c of components) { if (!builtInComponents.includes(c)) { clone.addComponent(c.clone(), true); } } return clone; }
/** * `onInitialize` is called before the first update of the actor. This method is meant to be * overridden. This is where initialization of child actors should take place. * * Synonymous with the event handler `.on('initialize', (evt) => {...})` */ public override onInitialize(engine: Engine): void { // Override me }
/** * Initializes this actor and all it's child actors, meant to be called by the Scene before first update not by users of Excalibur. * * It is not recommended that internal excalibur methods be overridden, do so at your own risk. * @internal */ public override _initialize(engine: Engine) { super._initialize(engine); for (const child of this.children) { child._initialize(engine); } }
// #region Events public override emit<TEventName extends EventKey<ActorEvents>>(eventName: TEventName, event: ActorEvents[TEventName]): void; public override emit(eventName: string, event?: any): void; public override emit<TEventName extends EventKey<ActorEvents> | string>(eventName: TEventName, event?: any): void { this.events.emit(eventName, event); }
public override on<TEventName extends EventKey<ActorEvents>>( eventName: TEventName, handler: Handler<ActorEvents[TEventName]> ): Subscription; public override on(eventName: string, handler: Handler<unknown>): Subscription; public override on<TEventName extends EventKey<ActorEvents> | string>(eventName: TEventName, handler: Handler<any>): Subscription { return this.events.on(eventName, handler); }
public override once<TEventName extends EventKey<ActorEvents>>( eventName: TEventName, handler: Handler<ActorEvents[TEventName]> ): Subscription; public override once(eventName: string, handler: Handler<unknown>): Subscription; public override once<TEventName extends EventKey<ActorEvents> | string>(eventName: TEventName, handler: Handler<any>): Subscription { return this.events.once(eventName, handler); }
public override off<TEventName extends EventKey<ActorEvents>>(eventName: TEventName, handler: Handler<ActorEvents[TEventName]>): void; public override off(eventName: string, handler: Handler<unknown>): void; public override off(eventName: string): void; public override off<TEventName extends EventKey<ActorEvents> | string>(eventName: TEventName, handler?: Handler<any>): void { this.events.off(eventName, handler as any); }
// #endregion
/** * It is not recommended that internal excalibur methods be overridden, do so at your own risk. * * Internal _prekill handler for {@apilink onPreKill} lifecycle event * @internal */ public _prekill(scene: Scene) { this.events.emit('prekill', new PreKillEvent(this)); this.onPreKill(scene); }
/** * Safe to override onPreKill lifecycle event handler. Synonymous with `.on('prekill', (evt) =>{...})` * * `onPreKill` is called directly before an actor is killed and removed from its current {@apilink Scene}. */ public onPreKill(scene: Scene) { // Override me }
/** * It is not recommended that internal excalibur methods be overridden, do so at your own risk. * * Internal _prekill handler for {@apilink onPostKill} lifecycle event * @internal */ public _postkill(scene: Scene) { this.events.emit('postkill', new PostKillEvent(this)); this.onPostKill(scene); }
/** * Safe to override onPostKill lifecycle event handler. Synonymous with `.on('postkill', (evt) => {...})` * * `onPostKill` is called directly after an actor is killed and remove from its current {@apilink Scene}. */ public onPostKill(scene: Scene) { // Override me }
/** * If the current actor is a member of the scene, this will remove * it from the scene graph. It will no longer be drawn or updated. */ public override kill() { if (this.scene) { this._prekill(this.scene); super.kill(); this._postkill(this.scene); } else { if (process.env.NODE_ENV === 'development') { this.logger.warn(`Cannot kill actor named "${this.name}", it was never added to the Scene`); } } }
/** * If the current actor is killed, it will now not be killed. */ public unkill() { this.isActive = true; }
/** * Indicates whether the actor has been killed. */ public override isKilled(): boolean { return !this.isActive; }
/** * Gets the z-index of an actor. The z-index determines the relative order an actor is drawn in. * Actors with a higher z-index are drawn on top of actors with a lower z-index */ public get z(): number { return this.get(TransformComponent).z; }
/** * Sets the z-index of an actor and updates it in the drawing list for the scene. * The z-index determines the relative order an actor is drawn in. * Actors with a higher z-index are drawn on top of actors with a lower z-index * @param newZ new z-index to assign */ public set z(newZ: number) { this.get(TransformComponent).z = newZ; }
/** * Get the center point of an actor (global position) */ public get center(): Vector { const globalPos = this.globalPos; return new Vector( globalPos.x + this.width / 2 - this.anchor.x * this.width, globalPos.y + this.height / 2 - this.anchor.y * this.height ); }
/** * Get the local center point of an actor */ public get localCenter(): Vector { 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); }
public get width() { return this.collider.localBounds.width * this.globalScale.x; }
public get height() { return this.collider.localBounds.height * this.globalScale.y; }
/** * The actor's rotation (in radians) taking into account any parent relationships */ public get globalRotation(): number { return this.get(TransformComponent).globalRotation; }
/** * The actor's world position taking into account parent relationships, scaling, rotation, and translation */ public get globalPos(): Vector { return this.get(TransformComponent).globalPos; }
/** * The global scale of the Actor */ public get globalScale(): Vector { return this.get(TransformComponent).globalScale; }
/** * The global z-index of the actor */ public get globalZ(): number { return this.get(TransformComponent).globalZ; }
// #region Collision
/** * Tests whether the x/y specified are contained in the actor * @param x X coordinate to test (in world coordinates) * @param y Y coordinate to test (in world coordinates) * @param recurse checks whether the x/y are contained in any child actors (if they exist). */ public contains(x: number, y: number, recurse: boolean = false): boolean { const point = vec(x, y); const collider = this.get(ColliderComponent); collider.update(); const geom = collider.get(); if (!geom) { return false; } const containment = geom.contains(point);
if (recurse) { return ( containment || this.children.some((child) => { if (child instanceof Actor) { return child.contains(x, y, true); } return false; }) ); }
return containment; }
/** * Returns true if the two actor.collider's surfaces are less than or equal to the distance specified from each other * @param actor Actor to test * @param distance Distance in pixels to test */ public within(actor: Actor, distance: number): boolean { const collider = this.get(ColliderComponent); const otherCollider = actor.get(ColliderComponent); const me = collider.get(); const other = otherCollider.get(); if (me && other) { return me.getClosestLineBetween(other).getLength() <= distance; } return false; }
// #endregion
// #region Update
/** * Called by the Engine, updates the state of the actor * @internal * @param engine The reference to the current game engine * @param elapsed The time elapsed since the last update in milliseconds */ public override update(engine: Engine, elapsed: number) { this._initialize(engine); this._add(engine); this._preupdate(engine, elapsed); this._postupdate(engine, elapsed); this._remove(engine); }
/** * Safe to override onPreUpdate lifecycle event handler. Synonymous with `.on('preupdate', (evt) =>{...})` * * `onPreUpdate` is called directly before an actor is updated. * @param engine The reference to the current game engine * @param elapsed The time elapsed since the last update in milliseconds */ public override onPreUpdate(engine: Engine, elapsed: number): void { // Override me }
/** * Safe to override onPostUpdate lifecycle event handler. Synonymous with `.on('postupdate', (evt) =>{...})` * * `onPostUpdate` is called directly after an actor is updated. * @param engine The reference to the current game engine * @param elapsed The time elapsed since the last update in milliseconds */ public override onPostUpdate(engine: Engine, elapsed: number): void { // Override me }
/** * Fires before every collision resolution for a confirmed contact * @param self * @param other * @param side * @param contact */ public onPreCollisionResolve(self: Collider, other: Collider, side: Side, contact: CollisionContact) { // Override me }
/** * Fires after every resolution for a confirmed contact. * @param self * @param other * @param side * @param contact */ public onPostCollisionResolve(self: Collider, other: Collider, side: Side, contact: CollisionContact) { // Override me }
/** * Fires once when 2 entities with a ColliderComponent first start colliding or touching, if the Colliders stay in contact this * does not continue firing until they separate and re-collide. * @param self * @param other * @param side * @param contact */ public onCollisionStart(self: Collider, other: Collider, side: Side, contact: CollisionContact) { // Override me }
/** * Fires once when 2 entities with a ColliderComponent separate after having been in contact. * @param self * @param other * @param side * @param lastContact */ public onCollisionEnd(self: Collider, other: Collider, side: Side, lastContact: CollisionContact) { // Override me }
/** * It is not recommended that internal excalibur methods be overridden, do so at your own risk. * * Internal _preupdate handler for {@apilink onPreUpdate} lifecycle event * @param engine The reference to the current game engine * @param elapsed The time elapsed since the last update in milliseconds * @internal */ public override _preupdate(engine: Engine, elapsed: number): void { this.events.emit('preupdate', new PreUpdateEvent(engine, elapsed, this)); this.onPreUpdate(engine, elapsed); }
/** * It is not recommended that internal excalibur methods be overridden, do so at your own risk. * * Internal _preupdate handler for {@apilink onPostUpdate} lifecycle event * @param engine The reference to the current game engine * @param elapsed The time elapsed since the last update in milliseconds * @internal */ public override _postupdate(engine: Engine, elapsed: number): void { this.events.emit('postupdate', new PostUpdateEvent(engine, elapsed, this)); this.onPostUpdate(engine, elapsed); }
// endregion}