arcade2d
Class

Game

game.ts:89

Root container for an arcade2d application. A Game owns the renderer (a PIXI Application under the hood), the per-frame ticker, and everything else that is conceptually outside a World — input samplers, audio mixers, asset registries, and similar page-scoped services that don't belong to any individual simulation.

A Game is the entry point. Real applications start with:

const game = await Game.bootstrap({
  backgroundColour: 0x101820,
  canvas: { fill: 'window' },
});

const world = game.createWorld({
  components: () => ({ ... }),
});

Lifecycle and the tick

Game is itself a AbstractComponentHost, so any Component attached to it goes through the standard pre-update / update / post-update phases each frame. The order is:

  1. game.onPreUpdate — every game component's pre-phase, in insertion order. Canonical use: input samplers snapshotting pending DOM event state so world-scoped code reads a consistent value all tick.
  2. game.onUpdate — every game component's main phase.
  3. game.onPostUpdate — every game component's post-phase.
  4. world.update() — if an active World exists, the engine drives its full three-phase tick. World-scoped reads of game state (e.g. World.getMouseState) therefore see values that were snapshotted during step 1.

The PIXI ticker drives Game.update; user code never calls it directly.

Worlds

Only one world is active at a time. Game.createWorld constructs it (auto-attaching a Scene and import('./world').Camera), and Game.destroyWorld tears it down. Switching between "menu" and "gameplay" worlds is the canonical use of this pair — destroy, then create the next one. Attempting to create a second world while one is already active throws ErrorCode.GAME_WORLD_ALREADY_EXISTS; attempting to destroy when no world is active throws ErrorCode.GAME_WORLD_NOT_FOUND.

Cross-references

Constructors

#
constructor(_application: Application, options: GameOptions): Game

Parameters

Properties

protected readonly #
components: Map<string, Component<Game, 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().

Accessors

readonly #
activeWorld: null | World

The currently active World, or null if no world has been created (or the last one was destroyed). Most game code that needs the world will close over the value returned by Game.createWorld directly; this accessor is intended for higher-level orchestration (scene-switching managers, dev tools).

readonly #
assets: AssetLibrary

The game's AssetLibrary — the page-scoped registry for loading, caching, and retrieving textures (and, later, other resources). Assets live at the game tier because they outlive any individual World; preload them here and reference them by key from world-scoped code.

Requires the auto-attached AssetLibrary registered under ASSET_LIBRARY_COMPONENT_KEY. If you removed it deliberately, reading this accessor throws ErrorCode.COMPONENT_NOT_FOUND.

readonly #
audio: AudioEngine

The game's AudioEngine — the page-scoped owner of the Web Audio context, the master and per-category gain buses, and the seam used by AssetLibrary to decode AudioAssets and by audio components (AudioSource, Music) to construct voices.

Lives at the game tier because audio outlives any individual World: a music track survives a world transition, and the pause-menu volume sliders mutate a single set of buses.

Requires the auto-attached AudioEngine registered under AUDIO_ENGINE_COMPONENT_KEY. If you removed it deliberately, reading this accessor throws ErrorCode.COMPONENT_NOT_FOUND.

readonly #
canvas: HTMLCanvasElement

The HTML canvas element that arcade2d renders into, owned by the underlying PIXI application. Exposed for application-level concerns that legitimately need the canvas — e.g. attaching a contextmenu handler to suppress the browser's right-click menu over the game.

Methods

protected #
_createDependencyResolver(): unknown

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.

Returns

unknown
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<Game>, options: AddComponentOptions): Component<Game>

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

Returns

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

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

Returns

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

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

Returns

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

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.

Parameters

Returns

ComponentMap<Game>
#
createWorld(options: Omit<WorldOptions, 'components'> & { components?: (world: World) => Record<string, () => Component<World>> }): World

Creates a new World and marks it as the active world for this Game. The engine auto-attaches a Scene (parented to the PIXI application's stage) and a Camera to every world it creates, so the user's component factory sees both as already-resolvable siblings.

Only one world may be active at a time. Calling this method while a world already exists throws ErrorCode.GAME_WORLD_ALREADY_EXISTS; destroy the existing one first via Game.destroyWorld.

Parameters

Returns

World

Throws

EngineError with code ErrorCode.GAME_WORLD_ALREADY_EXISTS when an active world already exists on this game.

Example

const world = game.createWorld({
  components: (world) => ({
    physics: () => new PhysicsSystem(world),
  }),
  prefabs: prefabRegistry,
});
#
destroy(): void

Tears the game down completely: destroys the active world (if any), runs onDestroy on every game component, unhooks the PIXI ticker, and destroys the underlying PIXI application. The Game instance is not reusable after this call.

Idempotent in the same sense as World.destroy — calling it twice is safe and the second call is a no-op.

Returns

void
#
destroyWorld(): void

Destroys the active world, releasing this Game's reference to it. Equivalent to calling world.destroy() and then clearing Game.activeWorld, in that order — the world's components and objects all see their onDestroy hooks fire as part of the call.

Subsequent Game.createWorld calls succeed because the active world slot is now empty.

Returns

void

Throws

EngineError with code ErrorCode.GAME_WORLD_NOT_FOUND when no world is currently active.

#
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(): Game

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

Game
#
getKeyboardState(): KeyboardState

Returns the current keyboard state — the set of physical keys held at tick-snapshot time, plus a isDown(code) predicate for membership tests. Page-global; the same snapshot is visible to menu UI and to gameplay code alike.

Keys are identified by KeyboardEvent.code (the physical key, not the logical character) so movement bindings like KeyW/KeyA/ KeyS/KeyD keep working across keyboard layouts. See KeyboardState for the convention and a pointer to MDN's full code-value list.

Allocates a fresh KeyboardState (with a freshly-cloned downKeys set) per call so callers can safely stash the result.

Requires the auto-attached Keyboard component registered under KEYBOARD_COMPONENT_KEY. If you removed it deliberately, calling this method throws ErrorCode.COMPONENT_NOT_FOUND.

Returns

KeyboardState
#
getMouseState(): MouseSnapshot

Returns the current screen-space mouse snapshot — canvas-local cursor position and the held/released state of the three standard buttons. Independent of any world or camera; the same snapshot is visible to menu UI and to gameplay code alike.

For the world-space projection of the cursor (camera-transformed), reach for World.getMouseState instead. This method exists so code working entirely in screen space (overlays, HUDs, intro menus with no world yet) doesn't have to construct a world to read the pointer.

Allocates a fresh MouseSnapshot (and a fresh Point for screenPosition) per call so callers can safely stash the result.

Requires the auto-attached Mouse component registered under MOUSE_COMPONENT_KEY. If you removed it deliberately, calling this method throws ErrorCode.COMPONENT_NOT_FOUND.

Returns

MouseSnapshot
#
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
#
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
#
update(): void

Runs the game's per-frame tick: every game component's pre/update/post phase in order, then delegates to the active world's update() if one exists. Wired to the PIXI ticker during construction, so user code almost never calls this method directly; it's public only so tests (and the rare callers that disable the ticker for deterministic stepping) can drive a single tick on demand.

Returns

void
static #
bootstrap(options: GameOptions): Promise<Game>

Creates and initialises a new Game. The async step is the PIXI application's own init() — which constructs the WebGL/WebGPU context and prepares the renderer. After it resolves, the canvas is appended to document.body (when running in a browser), the engine's auto-attached components are registered, the ticker is wired, and the returned Game is ready to receive a Game.createWorld call.

Parameters

Returns

Promise<Game>
static #
createHeadless(options: GameOptions): Game

Synchronous test-only factory that constructs a Game backed by a stub PIXI Application. No WebGL context, no canvas mounted to the DOM, no ticker registered with a real renderer.

When to use this

Unit tests and headless tooling only. The engine's production surface — every accessor that needs page-scoped services like the mouse or keyboard — assumes a Game is present, so unit tests that exercise a World in isolation need something to satisfy the new mandatory game argument on the World constructor. This factory exists to give those tests a cheap, synchronous game without the cost of bootstrapping a real PIXI renderer.

When not to use this

  • Never in production game code. The stub application does not render anything, does not run a ticker, does not own a canvas attached to the page. Anything that depends on actual draw output — Scene's transform sync to the screen, AbstractGraphics's parenting to the stage during real rendering, any code expecting Game.update to be driven by the PIXI ticker — will not behave correctly when the game is headless. Use Game.bootstrap instead, which performs the asynchronous WebGL/WebGPU init and mounts the canvas under document.body.
  • Not for renderer-level unit tests that need to observe the real PIXI pipeline. Those should construct their own Application and pass it to the public Game constructor.

The auto-attached Mouse and Keyboard components are still registered on the returned game. In non-browser environments (Node, jest's default node environment) their typeof window guards short-circuit listener attachment, so they're effectively inert — callable, but reporting empty input state.

Parameters

Returns

Game
ESC