Use when adding, organizing, registering, or loading non-code files (images, audio, video) in a Pixi'VN project — local assets processed by PixiJS AssetPack vs. online/remote-hosted assets, the AssetsManifest/bundle/alias system, where to edit the manifest for each case, and when to load bundles (at startup, in the background, per-label, per-route). Load this before writing any code that references an image/sound/video path directly, or before adding entries to a manifest.
Installs into .claude/skills of the current project.
Are you the author of Assets?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/drincs-productions-assets)
---
name: pixi-vn-assets
description: Use when adding, organizing, registering, or loading non-code files (images, audio, video) in a Pixi'VN project — local assets processed by PixiJS AssetPack vs. online/remote-hosted assets, the AssetsManifest/bundle/alias system, where to edit the manifest for each case, and when to load bundles (at startup, in the background, per-label, per-route). Load this before writing any code that references an image/sound/video path directly, or before adding entries to a manifest.
---
# Pixi'VN Assets: local vs. online, manifests, bundles, and loading strategy
Official docs: [pixi-vn.com/start/assets](https://pixi-vn.com/start/assets) (storage options) and
[pixi-vn.com/start/assets-management](https://pixi-vn.com/start/assets-management) (loading
strategy).
## When to use this skill
Use this skill whenever a task involves adding a new image/audio/video file to a project, deciding
whether an asset should live locally or be hosted online, editing the assets manifest, or choosing
when a bundle of assets should load (blocking at startup, in the background, per-label, per-route).
This skill does not cover _how_ an image/sound is displayed/played once loaded — that's
`pixi-vn-canvas` / `pixi-vn-sound` — only how it gets registered and made available by alias.
**The one rule that matters most: always reference assets by their manifest `alias`, never by raw
`src` path/URL, in game code.** Loading by path directly is strongly discouraged — it couples code
to a specific file location/host, so moving a file or switching hosting providers silently breaks
every reference. `Assets.load("bg01-hallway")`, not `Assets.load("https://.../bg01-hallway.webp")`.
## Local vs. online assets
Assets can be stored **locally** in the project or hosted **online** (a CDN, GitHub raw, cloud
storage). Frequently used assets (character sprites, backgrounds, background music) are best kept
local; assets used once, or large optional files, are better hosted online to keep the project
small and the initial download fast.
| | Local | Online |
| -------------------- | --------------------------------------------------------- | ------------------------------------- |
| Bundled into the app | Yes — ships in `public/assets` | No — fetched at runtime from a URL |
| Best for | Frequently used assets (backgrounds, sprites, core music) | Large/optional/rarely-used assets |
| Requires | PixiJS AssetPack (pre-configured in official templates) | A CORS-enabled host with a public URL |
## Local assets: the `src/assets` folder + PixiJS AssetPack
Local assets go under `src/assets` (any folder works, but this is the convention every official
template uses), organized into subfolders however makes sense for the project. **PixiJS AssetPack**
— pre-wired into the Vite config of every official template — watches this folder and, on every
change (`vite dev`) or once before a production build (`vite build`), optimizes the files
(compression, WebP/AVIF conversion, texture-atlas packing) into `public/assets`, and **auto-generates
`src/assets/manifest.gen.json`** describing every resulting bundle/alias.
```ts
// vite.config.ts (already present in official templates — no changes usually needed)
import { AssetPack } from "@assetpack/core";
import assetPackConfig from "./.assetpack.ts";
// a Vite plugin runs `new AssetPack(assetPackConfig).watch()` in dev,
// `.run()` once during `vite build`
```
```ts
// .assetpack.ts (already present in official templates)
import { pixiPipes } from "@assetpack/core/pixi";
export default {
entry: "./src/assets",
output: "./public/assets",
ignore: ["**/*.ts", "**/*.js", "**/*.gen.*"],
pipes: [
pixiPipes({ manifest: { output: "src/assets/manifest.gen.json" } }),
// official templates add two extra pipes here: one bundle per top-level
// folder (instead of everything in a single "default" bundle), and
// stripping file extensions from generated aliases
],
};
```
**`src/assets/manifest.gen.json` is generated — never hand-edit it.** Any manual change is
overwritten the next time AssetPack runs. If a local asset isn't showing up with the alias you
expect, check the actual file's path/name under `src/assets` and let AssetPack regenerate the file,
rather than editing the JSON directly.
```json
// src/assets/manifest.gen.json (excerpt — auto-generated, read-only)
{
"bundles": [
{
"name": "mc",
"assets": [{ "alias": "mc-neutral", "src": "./assets/mc/mc-neutral.png" }]
}
]
}
```
## Online assets: the exact place to register them — `src/assets/index.ts`
For assets you host online (or any manifest entries beyond what AssetPack generates), **the one
place to add/edit them is `src/assets/index.ts`**, present in every official template. It imports
the auto-generated local manifest and appends hand-written bundles for remote URLs:
```ts
// src/assets/index.ts — the exact, single point to add/modify online assets
import generatedManifestJson from "@/assets/manifest.gen.json";
import type { AssetsManifest } from "@drincs/pixi-vn";
export const manifest: AssetsManifest = {
bundles: [
...generatedManifestJson.bundles, // everything AssetPack generated locally
{
name: "audio", // pick a bundle name (see "Organizing into bundles" below)
assets: [
{
alias: "bgm_cheerful",
src: "https://your-cdn.example.com/audio/bgm_cheerful.wav",
},
{
alias: "sfx_whoosh",
src: "https://your-cdn.example.com/audio/sfx_whoosh.wav",
},
],
},
],
};
```
Popular CORS-friendly hosting options (see [pixi-vn.com/start/assets#assets-hosting](https://pixi-vn.com/start/assets#assets-hosting) for the full comparison of price/limits/editability):
GitHub raw URLs (`https://raw.githubusercontent.com/[repo]/refs/heads/main/[path]`, free, 100MB/file
limit), image-hosting services (imgbb/imgur, images only), or cloud storage (Cloudflare R2, Firebase
Storage, S3, Supabase) for anything larger or non-image. Whichever host is used, it must allow
**CORS requests** or the browser will block loading the asset.
## Organizing assets into bundles
Group related assets into **bundles** instead of registering each one loosely — a bundle loads (or
defers loading) as a single unit. Name each bundle after **where it's used**:
- a label's id (e.g. `startLabel.id`) for assets only that label needs
- a route's path (e.g. `"/"`, `"/game/narration"`) for assets a specific screen needs
- a character's id for that character's sprite/expression assets
This convention matters most for **online** assets, where it directly controls what the player
waits for; for local assets (which load near-instantly) precise bundle boundaries matter far less.
```ts
// src/assets/index.ts
import { startLabel } from "@/content/labels/start.label";
export const manifest: AssetsManifest = {
bundles: [
{ name: "/", assets: [{ alias: "background_main_menu", src: "..." }] }, // main menu route
{ name: startLabel.id, assets: [{ alias: "bg01-hallway", src: "..." }] }, // one label
{ name: "mc", assets: [{ alias: "mc-neutral", src: "..." }] }, // one character
],
};
```
## Initializing and loading
Register the manifest once, near app startup, before relying on any alias:
```ts
import { manifest } from "@/assets";
import { Assets } from "@drincs/pixi-vn";
let assetsInitialized = false;
export async function defineAssets() {
if (!assetsInitialized) {
const origin = `${location.protocol}//${location.host}/`;
Assets.resolver.rootPath = origin;
await Assets.init({ manifest, basePath: `${origin}assets/` });
assetsInitialized = true;
}
await Assets.loadBundle("/"); // block on what the first screen needs
Assets.backgroundLoadBundle("audio"); // load the rest quietly in the background
}
```
Then reference assets purely by alias, anywhere (`pixi-vn-canvas` for images/video,
`pixi-vn-sound` for audio):
```ts
const texture = await Assets.load("mc-neutral");
```
By default assets load on demand, but waiting until the exact moment an asset is needed can cause a
visible stall. Common, recommended loading moments, least-to-most eager:
- **At label start (background)** — in a label's `onLoadingLabel` hook, call
`Assets.backgroundLoadBundle(label.id)` (fire-and-forget): the label starts immediately, its
bundle streams in without blocking. Prefer this over the blocking variant below unless a step
genuinely cannot proceed without the asset.
- **At label start (blocking)** — same hook, but `await Assets.loadBundle(label.id)`: the label
waits for its bundle before its first step runs. Only use this when the label truly can't start
without those assets.
- **At project start** — `await Assets.loadBundle(...)` for anything used on the very first screen
(recommended limit: under 100MB, so the initial wait stays short).
- **In the background at project start** — `Assets.backgroundLoadBundle(...)` for larger,
non-blocking preloading (recommended limit: under 2GB).
- **Before a route/screen renders** — inside a router's `loader` (e.g. TanStack Router), call
`await Assets.loadBundle(routePath)`, paired with a `pendingComponent` so the player sees a
loading state instead of a blank screen.
## Real-world project convention (official React template)
`src/lib/utils/assets-utility.ts`'s `defineAssets()` combines several of the strategies above: guards
`Assets.init` with an `assetsInitialized` flag (safe to call more than once), **blocks** on the
current route's bundle (`await Assets.loadBundle("/")`) so the first screen never shows missing
assets, then kicks off the audio bundle in the **background** (`sound.backgroundLoadBundle(...)` —
the sound module's own wrapper, not `Assets.backgroundLoadBundle`, since it also has to wire the
loaded media into a channel). Separately, `main.tsx` wires `Game.onLoadingLabel((_stepId, { id }) =>
Assets.backgroundLoadBundle(id))` **once, globally** — so every label's own bundle (named after its
id, per the convention above) is background-loaded automatically the moment that label starts,
without every label needing its own `onLoadingLabel` hook. This is the template's convention, not a
library requirement — a project is free to load more/less eagerly.
For caching **online** assets so returning players don't re-download them: official templates
pre-configure [VitePWA](https://vite-pwa-org.netlify.app/)'s service worker to cache responses from
specific hostnames (a `CACHED_EXTERNAL_HOSTNAMES` list in `vite.config.ts`) for 7 days by default —
add any CDN/host actually used to that list. See
[pixi-vn.com/start/assets#caching-assets](https://pixi-vn.com/start/assets#caching-assets).
## Related skills
- **pixi-vn-getting-started** — the `@drincs/pixi-vn/vite` plugin's `assetsManifest` option bridges
a manifest (often this skill's `src/assets/index.ts`) into the plugin's generated type file and
dev-server endpoint; `Game.onLoadingLabel` is where label-scoped bundle loading is wired.
- **pixi-vn-canvas** — displaying images/video once their alias is loaded.
- **pixi-vn-sound** — playing audio once its alias is loaded, and the sound module's own
`backgroundLoadBundle`/`loadBundle` wrappers.
- **pixi-vn-narration** — labels are a natural bundle-naming unit (`label.id`).