Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/sanchedev/fraxel/llms.txt

Use this file to discover all available pages before exploring further.

Fraxel’s asset pipeline is built around a symbol-based ID system: every loaded asset is identified by a unique symbol returned at load time. This means you never pass raw URLs into nodes — you pass the stable symbol your loader gave you. All loaders are fully async and integrate naturally with top-level await in ES modules or inside any async function.

Loading Textures

loadTexture(url) loads an image from a URL, stores it in the internal texture registry, and returns a Promise<symbol> ID. Pass that symbol to any <sprite> via its textureId prop.
import { loadTexture } from 'fraxel/assets'

const PLAYER_TEX = await loadTexture('/assets/sprites/player.png')
const BG_TEX     = await loadTexture('/assets/background.png')

function Player() {
  return <sprite textureId={PLAYER_TEX} />
}
Internally, loadTexture creates an HTMLImageElement, waits for its load event, then wraps it in a Texture instance stored in a Map<symbol, Texture>. On the next call with the same URL, the map is scanned and the existing symbol is returned immediately — no network round-trip.
Texture URLs are deduplicated automatically. If two modules both call loadTexture('/assets/player.png'), they receive the same symbol and share the same underlying HTMLImageElement. Note that loadSound does not deduplicate — each call returns a new symbol and issues a fresh network request.

Loading Sounds

loadSound(url) fetches an audio file, decodes it through the Web Audio AudioContext, and returns a Promise<symbol> ID. The decoded AudioBuffer is stored in a module-level Map<symbol, AudioBuffer> and can be played back through the <audio-player> node.
import { loadSound } from 'fraxel/assets'

const SHOOT_SFX = await loadSound('/assets/sounds/shoot.mp3')
const HIT_SFX   = await loadSound('/assets/sounds/hit.ogg')
Because decoding is done up front via AudioContext.decodeAudioData, playback is instant with no stutter. See Audio for how to trigger sounds with <audio-player>.

Batch Loading

When you need to load many assets before entering a scene, loadBatch fires all loaders in parallel via Promise.all and calls an optional onProgress callback after each individual asset resolves — perfect for driving a loading bar.
import { loadBatch, loadTexture, loadSound } from 'fraxel/assets'

const [bg, player, enemy, shootSfx] = await loadBatch(
  [
    () => loadTexture('/assets/bg.png'),
    () => loadTexture('/assets/player.png'),
    () => loadTexture('/assets/enemy.png'),
    () => loadSound('/assets/shoot.ogg'),
  ],
  {
    onProgress: (loaded, total) => {
      console.log(`Loading… ${loaded}/${total}`)
    },
  },
)
Results are returned in the same order as the input loaders, so you can destructure them directly.

Function Reference

FunctionSignatureDescription
loadTexture(url: string) => Promise<symbol>Loads an image; returns a deduplicated symbol ID
loadSound(url: string) => Promise<symbol>Loads and decodes an audio buffer; returns a new symbol ID each call
loadBatch(loaders: (() => Promise<T>)[], options?: LoaderOptions) => Promise<T[]>Loads multiple assets in parallel with progress tracking
loadBatchAsset(type: AssetType, urls: string[], options?: LoaderOptions) => Promise<symbol[]>Typed variant for loading multiple same-type assets
unloadTexture(id: symbol) => voidRemoves a texture from memory
unloadSound(id: symbol) => voidRemoves a sound buffer from memory

LoaderOptions

PropertyTypeDescription
onProgress(loaded: number, total: number) => voidInvoked after each asset resolves; loaded counts completed assets, total is the batch size

loadBatchAsset

loadBatchAsset is a convenience wrapper over loadBatch for when all assets in a batch share the same type. It accepts a type string ('texture' or 'sound'), an array of URLs, and optional LoaderOptions. The return type is always Promise<symbol[]>.
import { loadBatchAsset } from 'fraxel/assets'

// Load all textures for level 1
const [bgTex, enemiesTex, itemsTex] = await loadBatchAsset('texture', [
  '/assets/level1-bg.png',
  '/assets/enemies.png',
  '/assets/items.png',
])

// Load a pack of sound effects
const [jumpSfx, landSfx, coinSfx] = await loadBatchAsset('sound', [
  '/assets/sounds/jump.ogg',
  '/assets/sounds/land.ogg',
  '/assets/sounds/coin.ogg',
])
You can also pass onProgress as a third argument:
const textures = await loadBatchAsset(
  'texture',
  urls,
  { onProgress: (loaded, total) => updateBar(loaded / total) },
)

Unloading Assets

When assets are no longer needed — after leaving a scene, for example — free their memory with unloadTexture or unloadSound. The symbol ID becomes invalid after unloading; do not pass it to nodes afterwards.
import { unloadTexture, unloadSound } from 'fraxel/assets'

// Tear down level 1 assets
unloadTexture(BG_TEX)
unloadTexture(ENEMY_TEX)
unloadSound(SHOOT_SFX)
Internally, both functions call Map.delete(id) on the respective registry, allowing the garbage collector to reclaim the underlying HTMLImageElement or AudioBuffer.

Usage Patterns

Pattern 1 — Individual Loading at Module Top Level

For small games or prototypes, load assets at the top of each module with await. Because ES modules support top-level await, the imported symbol is ready before any component that imports the module runs.
// player.tsx
import { loadTexture } from 'fraxel/assets'

const IDLE_TEX  = await loadTexture('/assets/player-idle.png')
const RUN_TEX   = await loadTexture('/assets/player-run.png')
const JUMP_TEX  = await loadTexture('/assets/player-jump.png')

export function Player() {
  return <sprite textureId={IDLE_TEX} />
}
Top-level await is ideal for a handful of assets. Switch to loadBatch once you have many assets and want a proper progress indicator.

Pattern 2 — Batch Loading with Progress Bar

Centralise all asset loading in a preload function and call it before mounting your scene. This lets you show a loading screen while assets stream in.
import { loadBatch, loadTexture, loadSound } from 'fraxel/assets'

async function preload(onProgress: (pct: number) => void) {
  await loadBatch(
    [
      () => loadTexture('/assets/bg.png'),
      () => loadTexture('/assets/player.png'),
      () => loadTexture('/assets/enemy.png'),
      () => loadSound('/assets/shoot.ogg'),
      () => loadSound('/assets/music.mp3'),
    ],
    {
      onProgress: (loaded, total) => {
        onProgress(loaded / total)
      },
    },
  )
}

// In your entry point:
await preload((pct) => {
  const bar = document.querySelector<HTMLElement>('#loading-bar')
  if (bar) bar.style.width = `${pct * 100}%`
})

// Safe to start the game now
mountGame(<App />)

Pattern 3 — Per-Scene Loading with loadBatchAsset

For larger games with multiple levels, load only the assets that belong to the current scene and unload them when leaving. loadBatchAsset keeps this concise when all assets share a type.
import { loadBatchAsset, unloadTexture } from 'fraxel/assets'

async function loadLevel1() {
  const [bgTex, enemyTex, tileTex] = await loadBatchAsset('texture', [
    '/assets/level1/bg.png',
    '/assets/level1/enemy.png',
    '/assets/level1/tiles.png',
  ])

  return {
    scene: (
      <transform>
        <sprite textureId={bgTex} />
        {/* level nodes… */}
      </transform>
    ),
    unload: () => {
      unloadTexture(bgTex)
      unloadTexture(enemyTex)
      unloadTexture(tileTex)
    },
  }
}

Related pages: Nodes 2D · Hooks · Audio · Filters · API: Assets

Build docs developers (and LLMs) love