Skip to content

Asset Loading

Estella provides the Assets resource (an AssetServer instance) for loading textures, Spine animations, materials, and generic files. Access it in any system via Res(Assets).

Accessing Assets

import { defineSystem, addStartupSystem, Res } from 'esengine';
import { Assets } from 'esengine';
addStartupSystem(defineSystem(
[Res(Assets)],
async (assets) => {
const tex = await assets.loadTexture('assets/player.png');
console.log(`Loaded ${tex.width}x${tex.height}, handle: ${tex.handle}`);
}
));

Texture Loading

loadTexture(path)

Loads an image and returns a TextureInfo object. The image is automatically flipped vertically for OpenGL UV conventions.

const tex = await assets.loadTexture('assets/player.png');
sprite.texture = tex.handle;

Returns: TextureInfo { handle: TextureHandle, width: number, height: number }

Other Texture Methods

MethodReturnsDescription
loadTextureRaw(source)Promise<TextureInfo>Load without vertical flip (used by Spine runtime)
getTexture(path)TextureInfo | undefinedReturns cached TextureInfo if already loaded
hasTexture(path)booleanReturns true if the texture is cached
releaseTexture(path)voidDecrements ref count; releases from GPU when count reaches zero
releaseAll()voidReleases all cached assets (textures, fonts, materials, shaders, prefabs, spine)

9-Slice Metadata

For 9-slice sprites, set border metadata after loading:

const tex = await assets.loadTexture('assets/panel.png');
assets.setTextureMetadata(tex.handle, {
left: 10, right: 10, top: 10, bottom: 10
});

You can also set metadata by path if the texture is already loaded:

assets.setTextureMetadataByPath('assets/panel.png', {
left: 10, right: 10, top: 10, bottom: 10
});

Spine Loading

const result = await assets.loadSpine('assets/hero.json', 'assets/hero.atlas');
if (!result.success) {
console.error(result.error);
}

loadSpine automatically:

  1. Fetches the atlas file and writes it to the virtual filesystem
  2. Parses the atlas for texture filenames and loads each texture
  3. Fetches the skeleton file (.json or .skel binary) and writes it to the virtual filesystem
MethodReturnsDescription
loadSpine(skeleton, atlas, baseUrl?)Promise<SpineLoadResult>Load Spine skeleton and atlas
isSpineLoaded(skeleton, atlas)booleanCheck if a Spine asset pair is already loaded
releaseSpine(key)voidRelease a loaded Spine asset (key = "skeleton:atlas")

BitmapFont Loading

const fontHandle = await assets.loadBitmapFont('assets/my-font.fnt');

Supports .fnt (BMFont text format) and .bmfont (JSON metadata) files. The loader automatically resolves and loads referenced texture atlases.

MethodReturnsDescription
loadBitmapFont(path, baseUrl?)Promise<FontHandle>Load a bitmap font file and its textures. Returns a font handle
getFont(path)FontHandle | undefinedGet cached font handle, undefined if not loaded
releaseFont(path)voidRelease the font and its textures from memory

Prefab Loading

const prefab = await assets.loadPrefab('prefabs/Enemy.esprefab');

Loads and parses a .esprefab file. The result is a PrefabData object that can be passed to instantiatePrefab().

MethodReturnsDescription
loadPrefab(path, baseUrl?)Promise<PrefabData>Load and cache a prefab file

See Prefabs for the full prefab API.

Generic File Loading

MethodReturnsDescription
loadJson<T>(path, options?)Promise<T>Load and parse a JSON file
loadText(path, options?)Promise<string>Load a text file
loadBinary(path, options?)Promise<ArrayBuffer>Load a binary file
const config = await assets.loadJson<GameConfig>('assets/config.json');
const csv = await assets.loadText('assets/levels.csv');
const data = await assets.loadBinary('assets/tilemap.bin');

FileLoadOptions

OptionTypeDescription
baseUrlstringOverride the base URL for this request
noCachebooleanSkip cache — always fetch from network

Batch Loading

Load all assets from an AddressableManifest in parallel with loadAll:

const manifest = await assets.loadJson('assets/addressable-manifest.json');
assets.setAddressableManifest(manifest);
const bundle = await assets.loadAll(manifest);
// Access loaded assets from the bundle
const bgTex = bundle.textures.get('bg');
const config = bundle.json.get('game-config');

You can also load a subset of assets by label or group:

const bundle = await assets.loadByLabel('ui-sprites');
const levelBundle = await assets.loadGroup('level-1');

AssetBundle

The returned AssetBundle contains Maps keyed by address (or path if no address is set):

FieldType
texturesMap<string, TextureInfo>
materialsMap<string, LoadedMaterial>
spineMap<string, SpineLoadResult>
fontsMap<string, FontHandle>
prefabsMap<string, PrefabData>
jsonMap<string, unknown>
textMap<string, string>
binaryMap<string, ArrayBuffer>

Material Loading

const loaded = await assets.loadMaterial('assets/effects/glow.esmaterial');
sprite.material = loaded.handle;
MethodReturnsDescription
loadMaterial(path, baseUrl?)Promise<LoadedMaterial>Load and compile a .esmaterial file
getMaterial(path, baseUrl?)LoadedMaterial | undefinedGet cached material if already loaded
hasMaterial(path, baseUrl?)booleanCheck if a material is cached

See Materials & Shaders for the full material API and .esmaterial file format.

Caching

All load methods cache by path. Loading the same path twice returns the cached result without a network request. Use the noCache option on generic file loaders to bypass caching:

const fresh = await assets.loadJson('assets/config.json', { noCache: true });

Base URL

Set assets.baseUrl to prefix all relative paths:

assets.baseUrl = 'https://cdn.example.com/game';
const tex = await assets.loadTexture('sprites/player.png');
// fetches https://cdn.example.com/game/sprites/player.png

Absolute paths and full URLs are not affected by baseUrl.

Scene Loading

Load a scene file with all its referenced assets in a single call:

const sceneData = await assets.loadJson<SceneData>('assets/scenes/main.esscene');
const entityMap = await assets.loadScene(world, sceneData);

loadScene pre-collects all asset references in the scene (textures, materials, Spine, fonts, prefabs), loads them in parallel via Promise.all, then instantiates the entity hierarchy. Returns a Map<number, Entity> mapping scene IDs to runtime entities.

Embedded Assets

For Playable Ad builds, the editor’s build pipeline automatically embeds all referenced assets as data URIs into the output bundle. At runtime, assets.loadTexture() / assets.loadJson() and other load calls resolve from the embedded data instead of fetching over the network — no code changes are needed.

For custom builds, you can register embedded assets manually:

assets.registerEmbeddedAssets({
'assets/player.png': 'data:image/png;base64,...',
'assets/config.json': 'data:application/json;base64,...',
});

Addressable Assets

Addressable assets let you load resources by logical address, label, or group instead of raw file paths. The editor builds an AddressableManifest during the build process, which maps addresses and labels to asset paths.

Loading by Address

const texture = await assets.load('player-idle');

To resolve an address to its manifest entry without loading:

const entry = assets.resolveAddress('player-idle');
if (entry) {
console.log(`Type: ${entry.type}, Path: ${entry.path}`);
}

Loading by Label

Load all assets tagged with a label. Returns an AssetBundle:

const bundle = await assets.loadByLabel('ui-sprites');
for (const [path, tex] of bundle.textures) {
console.log(`Loaded ${path}: ${tex.width}x${tex.height}`);
}

Loading by Group

Load all assets in a named group:

const bundle = await assets.loadGroup('level-1');

Setting the Manifest

The manifest is generated by the editor during build. At runtime, set it before loading:

import { Assets } from 'esengine';
const manifest = await assets.loadJson('assets/addressable-manifest.json');
assets.setAddressableManifest(manifest);

Manifest Format

The AddressableManifest has the following structure:

interface AddressableManifest {
version: '2.0';
groups: Record<string, AddressableManifestGroup>;
}
interface AddressableManifestGroup {
bundleMode: string;
labels: string[];
assets: Record<string, AddressableManifestAsset>;
}
interface AddressableManifestAsset {
path: string;
address?: string;
type: AddressableAssetType;
size: number;
labels: string[];
metadata?: {
atlas?: string;
atlasPage?: number;
atlasFrame?: { x: number; y: number; width: number; height: number };
};
}

Each group contains assets with their paths, optional addresses, labels, and type-specific metadata. Asset types include texture, material, spine, bitmap-font, prefab, json, text, binary, and audio.

Next Steps