arcade2d
Class

WorldObject

world/world-object.ts:90

A single addressable thing inside a World. A WorldObject is a spatial node that hosts Components — controllers, visuals, colliders, audio sources, anything else — and provides them with a canonical, shared transform (position, rotation, scale) that they all read from or write into.

The behaviour and appearance of an object is defined by its components. The transform fields, by contrast, are owned by the host: there is one position, one rotation, and one scale per object, regardless of how many components reference them. This is the engine's answer to the "ten components agreeing about where the thing is" coordination problem — authoritative state lives on the host, and components are either:

  • Projections of the host transform — a PolygonGraphics reading host.position / host.rotation / host.scale and pushing them to its underlying PIXI display object once per frame. A collider reading the same fields to transform its local shape into world space.
  • Writers of the host transform — controllers and AI setting host.rotation to face a target, dynamic physics writing back simulated results in onPostUpdate.

Pick one role per component. Mixing both — having two components fight over host.rotation in the same phase, for instance — is exactly the coordination bug the host-owned transform exists to prevent. If two systems both need to author rotation, decide who owns it and have the other read.

Lifecycle

An object has three internal states: live (in the world, ticking), marked (WorldObject.destroy has been called, awaiting the world's sweep at the end of the current/next tick), and cleaned (onDestroy has fired, the object is inert). Transitions are one-way and the API is idempotent — calling destroy() repeatedly or on an already-cleaned object is safe.

Enabling and disabling

Setting AbstractComponentHost.enabled to false on an object gates all three of its per-frame component phases (onPreUpdate, onUpdate, onPostUpdate) at a single early-return: a paused enemy, a frozen UI widget, a temporarily-disabled debug overlay. The object keeps its components and their state; flip enabled back to true and it resumes ticking from where it was. onAdded and onDestroy are not gated — a half-attached or half-destroyed object would be worse than a paused one.

Example

// A controller sets the host's rotation; the graphics component reads
// it back out in the same tick (no coupling between the two).
class ChaseAI implements WorldObjectComponent {
  constructor(public readonly host: WorldObject) {}

  onAdded() {}

  onUpdate() {
    const target = this.host.world.findOneByTag('player');
    if (target) {
      this.host.rotation = this.host.position.angleTo(target.position);
    }
  }

  onDestroy() {}
}

Constructors

#
constructor(world: World, position: PointPrimitive, metadata: WorldObjectMetadata, rotation: number, scale: Point): WorldObject

Parameters

Properties

protected readonly #
components: Map<string, Component<WorldObject, unknown>>
#
enabled: boolean

Master gate on every component update phase this host runs. When false, the host's onPreUpdate, onUpdate, and onPostUpdate iterations short-circuit at a single check — useful for freezing a single object during a cutscene, pausing a UI widget while a menu is up, or temporarily disabling a debug overlay without tearing the components down.

The flag is not propagated to onAdded or onDestroy. Those always fire so a host can never end up with half-attached components, and a disabled object is still cleanly torn down when destroyed.

Defaults to true (active). Flip back to true and the host resumes ticking from its preserved state on the next world update().

readonly #
metadata: WorldObjectMetadata

Metadata about the object and its relationship with the world it is part of.

readonly #
position: Point

The object's position in world space, in pixels. Constructed fresh from the value passed to the constructor so external mutations of that input cannot leak in; the Point exposed here is mutable and intended to be written by controllers / physics / movement code (host.position.x += dx).

#
rotation: number

The object's rotation in world space, in radians, measured clockwise from the positive x-axis (i.e. 0 faces right, matching the convention used by Point.angular and Point.angleTo). Mutable — controllers, AI and physics write into this directly; visual components read it back to orient themselves.

Defaults to 0 (facing right) for newly-constructed objects. The engine does not normalise the value, so callers may freely accumulate angles past if that simplifies their logic.

readonly #
scale: Point

The object's scale, expressed as a 2D Point so x and y can be scaled independently. Defaults to 1,1 (no scaling). The exposed Point is mutable in place — host.scale.x = 2 works — and components projecting from the host transform are expected to honour both axes.

Like WorldObject.position, scale is cloned from the value passed to the constructor so the inbound point can be safely reused or mutated by the caller without affecting this object.

readonly #
world: World

The world that the object exists within.

Accessors

readonly #
destroyed: boolean

Whether this object is no longer alive — either marked for destruction and awaiting the next sweep, or already cleaned up. Live objects return false; everything else returns true.

Methods

protected #
_createDependencyResolver(component: Component<WorldObject>, key: string): WorldObjectComponentDependencyResolver

Subclass hook that produces the concrete dependency resolver the host hands to a component's resolveDependencies. The World hosts a resolver scoped to siblings only; a WorldObject hosts one that also exposes cross-tier lookups against the parent world.

Engine-internal — never called by user code.

Parameters

Returns

WorldObjectComponentDependencyResolver
protected #
_handleComponentDestroyError(error: unknown, key: string): void

Hook for subclasses to intercept errors thrown by a component's onDestroy during AbstractComponentHost.removeAllComponents. Default behaviour is to log and swallow — a single bad component must not prevent the rest of the host's components from being torn down. Subclasses may override to route errors through their own reporting channel.

Parameters

  • error unknown
  • key string

Returns

void
#
addComponent(key: string, component: Component<WorldObject>, options: AddComponentOptions): Component<WorldObject>

Adds a new component to the host object. Throws if a component with the specified key already exists. Calls onAdded() on the component once registered with its host.

Parameters

#
addComponentFromFactory(key: string, factory: ComponentFactory<WorldObject>, options: AddComponentOptions): Component<WorldObject>

Adds a new component to the host object using a factory function. Internally produces the new component using the factory function, then calls addComponent() with the result.

The advantage of using this method over addComponent() is that the factory function is provided with the host.

Parameters

#
addComponents(components: ComponentMap<WorldObject>, options: AddComponentOptions): ComponentMap<WorldObject>

Adds a new set of components to the host object. Throws if a component with the specified key already exists. Calls onAdded() on each component after they are all registered with the host, rather than one by one. This is important for components that may want to reference each other during the addition phase via host.getComponent() or similar methods.

It is recommended to use this method rather than addComponent() in situations like initialization of a new host object.

Parameters

#
addComponentsFromFactories(map: ComponentFactoryMap<WorldObject>, options?: AddComponentOptions): ComponentMap<WorldObject>

Adds a new set of components to the host object based on an input map of component keys to factory functions. Behavious is equivalent to addComponents() using the key and output of each factory function.

#
destroy(): void

Marks the object as destroyed. This does not immediately remove it from the world or destroy its components — the world must tick at least once for that to happen.

If called during a World.update tick, the object is removed at the end of that tick. If the object has not yet had its onUpdate called during the same tick (e.g. it was destroyed by a component or by an earlier object in the iteration), its onUpdate is skipped — destroyed objects do not get one final tick.

Calling destroy on an already-marked or already-cleaned object is a no-op.

Returns

void
#
getComponent(key: string): T

Gets a component from the host object using the key it was registered with. Throws if the component does not exist. Performs an efficient lookup on a local Map instance.

Parameters

  • key string

Returns

T
#
getComponentByType(type: ComponentHostConstructor<T>): T

Gets a component from the host object using its type. Throws if no component of the type exists, or if more than one component of the type exists — in the multi-match case, getComponentByType deliberately does not pick one for you. Use ComponentHost.getComponentsByType when you genuinely expect multiple matches, or look the component up by its string key.

Performs an O(n) lookup once per type initially, then caches the resolved key for O(1) lookups on subsequent calls. The cache is invalidated whenever a component is removed.

Parameters

Returns

T
#
getComponentsByType(type: ComponentHostConstructor<T>): readonly T[]

Gets every component on the host of the given type, in the order they were originally registered. Returns an empty array if no components match.

Unlike ComponentHost.getComponentByType, this method never throws on multiplicity — it is the explicit "I expect more than one" accessor.

Parameters

Returns

readonly T[]
protected #
getHostReference(): WorldObject

Gets a reference to the host object that this component is attached to. Required for the compiler to be able to resolve the type of the host object correctly in some internal function calls (e.g. when resolving the type of the host object from a factory function).

Returns

WorldObject
#
getNullableComponent(key: string): null | T

Gets a component from the host object using the key it was registered with. Returns null if the component does not exist, rather than throwing an error. Useful for referencing transient or optional components without manually handling errors.

Parameters

  • key string

Returns

null | T
#
getNullableComponentByType(type: ComponentHostConstructor<T>): null | T

Gets a component from the host object using its type. Returns null if the component does not exist or if more than one component of the type is registered (i.e. the lookup is ambiguous) — the nullable variant collapses both "not found" and "ambiguous" into a single null. Use ComponentHost.getComponentsByType when you need to distinguish them.

Parameters

Returns

null | T
#
hasComponent(key: string): boolean

Checks if the host object has a component with the specified key.

Parameters

  • key string

Returns

boolean
#
hasComponentByType(type: ComponentHostConstructor<T>): boolean

Checks if the host object has a component with the specified type.

Parameters

Returns

boolean
#
localToWorld(point: PointPrimitive): Point

Maps a point expressed in this object's local space into the world's coordinate system. The forward transform applied — in order — is scale, rotate, then translate by WorldObject.position, so (0, 0) always maps to the host's world position, and a local point "10 units along +x" lands wherever the host is currently facing, scaled to whatever the host's scale.x is.

Pairs with WorldObject.worldToLocal — round-tripping a point through both methods is the identity (modulo floating-point error).

Allocates a fresh Point per call so callers can mutate the result without affecting host state.

Parameters

Returns

Point

A new Point expressing the same location in world space.

#
onDestroy(): void

Lifecycle hook called when this object is actually removed from the world. Idempotent — repeat invocations are no-ops, so callers can fire it defensively without worrying about double-cleanup of components.

Returns

void
#
onPostUpdate(update: WorldUpdate): void

Drives the onPostUpdate phase across this object's components. Called by the World during the post-update pass of each tick. Skips components whose enabled is explicitly false, and components that do not implement the optional hook.

Parameters

Returns

void
#
onPreUpdate(update: WorldUpdate): void

Drives the onPreUpdate phase across this object's components. Called by the World during the pre-update pass of each tick. Skips components whose enabled is explicitly false, and components that do not implement the optional hook.

Parameters

Returns

void
#
onUpdate(update: WorldUpdate): void

Drives the onUpdate phase across this object's components. Called by the World during the main update pass of each tick. Skips components whose enabled is explicitly false.

Parameters

Returns

void
#
removeAllComponents(): void

Removes all components from the host object. Typically called internally when the lifecycle of the host object is terminated. Differs from individually removing components in that it first calls onDestroy() on each component, then removes references from the host object in a separate step. This allows cleaner teardown of components that may reference each other.

Returns

void
#
removeComponent(key: string): void

Removes a component from the host object. Care should be taken when manually removing components, as methods like getComponent() will throw if components do not exist. Removal is idempotent and will do nothing if the component does not exist i.e. was already removed, or never existed.

Parameters

  • key string

Returns

void
#
worldToLocal(point: PointPrimitive): Point

Maps a point expressed in world space into this object's local coordinate system — the inverse of WorldObject.localToWorld. This is the primitive that hit-tests and other shape-vs-world queries are built on: convert the world point to local, then ask the local- space shape (a Polygon, a Circle, ...) whether it contains it.

Axes with zero WorldObject.scale are left untouched on that axis (rather than dividing by zero); a fully zero-scaled object collapses to a point and containment against it is undefined either way, so this is just the cheaper of two equally-degenerate behaviours.

Allocates a fresh Point per call.

Parameters

Returns

Point

A new Point expressing the same location in the host's local space.

ESC