arcade2d
Class

AssetLibrary

assets/asset-library.ts:87

Game-tier registry that loads, caches, and hands out Asset resources — textures today, audio and other kinds later.

AssetLibrary is auto-attached to every Game and reached through Game.assets. It lives at the game tier, not the world tier, because assets outlive worlds: you do not want to reload the player spritesheet every time a world is created or destroyed. Group assets with namespaces (see below) when you want world- or level-scoped grouping without world-scoped lifetime.

Under the hood it wraps PIXI's global Assets loader. That dependency is an implementation detail reachable only through AssetLibrary.raw; the surface here speaks in arcade2d Asset handles.

Preloading model

Loading is explicit and developer-driven. There is no lazy load-on-first-use: you preload the assets a phase needs, then run the phase that uses them. The intended granularity is coarse and eager — load a whole bundle/namespace at a boundary (entering a level), never one texture per object as it spawns:

await game.assets.loadMany(
  ['tiles/floor.png', 'tiles/wall.png', 'sprites/goblin.png'],
  { namespace: 'level-1' },
);
const world = game.createWorld({ ... }); // safe: every asset resolved

Loading eagerly at a coarse boundary is what makes a missing asset a load-time failure (ErrorCode.ASSET_LOAD_FAILED the moment the batch is awaited) rather than a gameplay-time surprise that only fires if a tester happens to encounter a particular object. AssetLibrary.get throwing on a missing key is the backstop for the residual "forgot to load the bundle entirely" mistake — not the primary safety net.

Idempotent loading

AssetLibrary.load is idempotent per (namespace, key). A path already loaded under that identity resolves immediately to the existing handle; a load still in flight returns the same in-flight promise rather than starting a second fetch. Reusing a key for a different path within a namespace is a programming error and throws ErrorCode.ASSET_KEY_CONFLICT.

Namespaces

A namespace is a grouping whose keys are unique only within it. The DEFAULT_ASSET_NAMESPACE covers the simple case; reach for explicit namespaces when you want AssetLibrary.unloadNamespace to free a whole group at once (one namespace per level is the canonical use).

Unloading

Unloading is explicit; there is no automatic eviction. Free a single asset with AssetLibrary.unload or a whole group with AssetLibrary.unloadNamespace. Both release the underlying GPU resource via the renderer, not just the JS reference — the realistic win is dropping a finished level's textures, or (later) decoded audio buffers.

Constructors

#
constructor(host: Game): AssetLibrary

Parameters

Properties

#
enabled: boolean

Per-component gate on the three update hooks (onPreUpdate, onUpdate, onPostUpdate). When explicitly false, the engine skips all three for this component during the host's tick — useful for temporarily pausing behaviour (e.g. a freeze powerup) without removing the component and losing its internal state.

Does not gate onAdded or onDestroy; those always fire so a host can never end up with a half-attached component.

readonly #
host: Game

The host this component is attached to. Stored read-only; subclasses access it as this.host directly, or through the tier-appropriate aliases (this.world, this.game) on the per-tier abstract bases.

Accessors

readonly #
game: Game

The Game this component is attached to — identical to AbstractComponent.host at this tier, exposed under the game name so subclass code reads the same on every tier.

readonly #
raw: AssetsClass

Direct access to the underlying PIXI Assets loader.

Use with care. raw is an intentional escape hatch for cases the arcade2d API doesn't cover — registering custom loader parsers, tuning resolver preferences, background-loading, anything we haven't decided how to model yet. Code that touches raw is coupled to PIXI's public API and may break when:

  • arcade2d upgrades PIXI (including minor versions).
  • PIXI itself ships a breaking change.
  • arcade2d swaps PIXI for a different loader.

None of those will be treated as breaking changes to arcade2d's own surface. Prefer the typed methods on this component; reach for raw only when no equivalent exists, and isolate the access behind your own helper so the coupling is in one place.

Methods

#
get(key: string, namespace: string): Asset

Retrieves a previously loaded or stored asset by key.

Throws on a miss rather than returning null — a missing asset at this point means the expected preload never ran, which is a programming error the engine surfaces loudly. Use AssetLibrary.getNullable when absence is a legitimate, handled state.

Parameters

  • key string
  • namespace string

Returns

Asset

The stored Asset handle.

Throws

EngineError with code ErrorCode.ASSET_NOT_FOUND when no asset is stored under (namespace, key).

Example

const player = game.assets.get('player') as ImageAsset;
#
getAs(key: string, type: AssetConstructor<T>, namespace: string): T

Retrieves an asset by key and asserts its concrete type, returning it typed — so call sites avoid an unchecked as ImageAsset cast.

Pass the expected Asset subclass (e.g. ImageAsset) as a runtime witness; the lookup verifies the stored asset is an instance of it. This mirrors ComponentHost.getComponentByType and, unlike a bare get<T>() generic, is type-safe at runtime: a wrong type is a loud ErrorCode.ASSET_TYPE_MISMATCH, not a latent mis-cast.

Parameters

Returns

T

The stored asset, typed as T.

Throws

EngineError with code ErrorCode.ASSET_NOT_FOUND when no asset is stored under (namespace, key).

EngineError with code ErrorCode.ASSET_TYPE_MISMATCH when the stored asset is not an instance of type.

Example

const player = game.assets.getAs('player', ImageAsset); // typed, no cast
new Texture(player);
#
getNullable(key: string, namespace: string): null | Asset

Retrieves a previously loaded or stored asset by key, or null if none is stored under (namespace, key).

The non-throwing counterpart to AssetLibrary.get; mirrors the getNullableComponent convention on the component host. Use it when absence is an expected, handled outcome rather than a programming error.

Parameters

  • key string
  • namespace string

Returns

null | Asset

The stored Asset handle, or null.

#
has(key: string, namespace: string): boolean

Reports whether an asset is stored under (namespace, key).

Parameters

  • key string
  • namespace string

Returns

boolean
#
load(path: string, options: AssetLoadOptions): Promise<Asset>

Loads a single resource and stores it for later retrieval by key.

Idempotent per (namespace, key): a matching asset that is already loaded resolves immediately, and one still loading returns the shared in-flight promise. See the class docs for the full preloading model.

Parameters

Returns

Promise<Asset>

A promise resolving to the stored Asset handle.

Throws

EngineError with code ErrorCode.ASSET_KEY_CONFLICT when (namespace, key) already holds an asset loaded from a different path.

EngineError with code ErrorCode.ASSET_TYPE_MISMATCH when no type is given and the path's extension yields no recognised type.

EngineError with code ErrorCode.ASSET_LOAD_FAILED when the underlying loader rejects (network error, decode failure, unsupported format).

Example

const player = await game.assets.load('sprites/player.png', {
  key: 'player',
  namespace: 'level-1',
});
#
loadMany(paths: readonly string[], options: AssetLoadManyOptions): Promise<readonly Asset[]>

Loads many resources in parallel, each stored under its own path as key.

The promise resolves once every load settles, with the resulting Asset handles in the same order as paths. If any load rejects, the returned promise rejects with the first error — assets that loaded successfully remain stored.

Use this for the eager, coarse-grained preload described in the class docs — load a level's whole asset set in one call before creating the world that uses it.

Parameters

Returns

Promise<readonly Asset[]>

A promise resolving to the stored Asset handles, in input order.

Throws

The same EngineError codes as AssetLibrary.load, surfaced from whichever path failed.

Example

await game.assets.loadMany(
  ['tiles/floor.png', 'tiles/wall.png'],
  { namespace: 'level-1' },
);
#
onAdded(_deps: Record): void

Lifecycle hook that is called when the component is added to the host object. Should not be called directly.

Parameters

  • _deps Record

Returns

void
#
onDestroy(): void

Drops every tracked asset reference when the game tears down. The underlying GPU resources are released by the PIXI application's own destroy during Game.destroy, so this only needs to clear the library's bookkeeping.

Returns

void
#
onUpdate(_update: WorldUpdate, _deps: Record): void

Lifecycle hook for the main update phase. Called once per world tick, after every component's onPreUpdate and before any onPostUpdate.

Parameters

Returns

void
#
store(key: string, asset: Asset, namespace: string): Asset

Registers an externally-produced Asset under a key, without going through the loader.

This is the entry point for assets the engine did not fetch — a texture generated at runtime, a render-texture snapshot, a procedurally-built resource. AssetLibrary.load also routes through store once its fetch resolves, so a stored asset is indistinguishable from a loaded one at lookup time.

Parameters

  • key string
  • asset Asset
  • namespace string

Returns

Asset

The same asset, for call-site convenience.

Throws

EngineError with code ErrorCode.ASSET_KEY_CONFLICT when (namespace, key) already holds a different asset. Re-storing the identical instance is a no-op and does not throw.

#
unload(key: string, namespace: string): Promise<void>

Unloads a single asset, releasing its underlying GPU/loader resource and dropping it from the library.

Idempotent: unloading a key that isn't stored is a no-op, like removeComponent on the component host. After this resolves, AssetLibrary.get for the same key throws again until it is reloaded.

Parameters

  • key string
  • namespace string

Returns

Promise<void>

A promise that resolves once the underlying resource is freed.

#
unloadNamespace(namespace: string): Promise<void>

Unloads every asset in a namespace in one batch, releasing all of their underlying resources and dropping the namespace.

This is the coarse-grained eviction the namespace model is built for: give a level its own namespace, then drop the whole set on exit. Idempotent — unloading an unknown or already-empty namespace is a no-op.

Parameters

  • namespace string

Returns

Promise<void>

A promise that resolves once every underlying resource is freed.

Example

await game.assets.unloadNamespace('level-1'); // entering level 2
#
use(bundle: AssetBundle<E>): BoundAssetBundle<E>

Binds a declarative AssetBundle to this library, returning a BoundAssetBundle whose load/get/unload are scoped to the bundle's namespace and — crucially — typed to the bundle's declared keys.

This is the recommended entry point for any asset whose key is known at authoring time. Where the untyped AssetLibrary.get accepts any string and throws ErrorCode.ASSET_NOT_FOUND at runtime on a typo, a bound bundle rejects an unknown key at compile time — turning "a rare object's graphics component throws mid-session because its asset key was wrong" into a tsc error. Keep the untyped methods for genuinely dynamic keys.

Parameters

Returns

BoundAssetBundle<E>

A BoundAssetBundle bound to this library.

Example

const lvl = game.assets.use(level1);
await lvl.load();
const zombie = lvl.get('zombie'); // typed to the bundle's keys
ESC