pixi-vn-assets

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Pixi'VN Assets: local vs. online, manifests, bundles, and loading strategy

Pixi'VN 资源:本地与在线、manifest、bundle 与加载策略

Official docs: pixi-vn.com/start/assets (storage options) and pixi-vn.com/start/assets-management (loading strategy).
官方文档:pixi-vn.com/start/assets(存储选项)以及 pixi-vn.com/start/assets-management(加载 策略)。

When to use this skill

何时使用此 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")
.
当任务涉及向项目中添加新的图像/音频/视频文件、决定某项资源应存放在本地还是在线托管、编辑 assets manifest,或选择何时加载资源 bundle(启动时阻塞加载、后台加载、按 label、按 route)时,请使用此 skill。 此 skill 不涵盖图像/音频加载后如何显示或播放——那属于
pixi-vn-canvas
/
pixi-vn-sound
——只涵盖如何注册资源并使其可通过 alias 使用。
**最重要的一条规则:在游戏代码中,始终通过 manifest 的
alias
引用资源,绝不要使用原始
src
路径或 URL。**强烈不建议按路径直接加载——这会将代码耦合到特定的文件位置或托管服务,因此移动文件或切换托管服务会悄无声息地破坏所有引用。应使用
Assets.load("bg01-hallway")
,而不是
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.
LocalOnline
Bundled into the appYes — ships in
public/assets
No — fetched at runtime from a URL
Best forFrequently used assets (backgrounds, sprites, core music)Large/optional/rarely-used assets
RequiresPixiJS AssetPack (pre-configured in official templates)A CORS-enabled host with a public URL
资源可以存储在项目的本地,也可以在线托管(CDN、GitHub raw、云存储)。频繁使用的资源(角色 sprite、背景、背景音乐)最好保存在本地;只使用一次的资源或大型可选文件则更适合在线托管,以减小项目体积并加快初始下载速度。
本地在线
随应用打包是——随
public/assets
一起发布
否——在运行时从 URL 获取
最适合频繁使用的资源(背景、sprite、核心音乐)大型、可选或低频使用的资源
要求PixiJS AssetPack(官方 template 中已预配置)支持 CORS 且提供公开 URL 的托管服务

Local assets: the
src/assets
folder + PixiJS AssetPack

本地资源:
src/assets
文件夹与 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" }]
    }
  ]
}
本地资源应放在
src/assets
下(可以使用任意文件夹结构,但所有官方 template 都遵循这一约定),并按适合项目的方式组织子文件夹。PixiJS AssetPack——已接入每个官方 template 的 Vite config——会监视此文件夹,并在每次变更时(
vite dev
),或在生产构建前执行一次(
vite build
),将文件优化(压缩、WebP/AVIF 转换、texture-atlas 打包)至
public/assets
,并自动生成
src/assets/manifest.gen.json
,其中描述每个最终生成的 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
是自动生成的——切勿手动编辑。**任何手动修改都会在 AssetPack 下次运行时被覆盖。如果某个本地资源没有以预期的 alias 出现,请检查该文件在
src/assets
下的实际 path/name,并让 AssetPack 重新生成文件,而不是直接编辑 JSON。
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

在线资源:准确的注册位置——
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 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.
对于在线托管的资源(或 AssetPack 生成内容之外的任何 manifest 条目),唯一应该添加或修改它们的位置是
src/assets/index.ts
。每个官方 template 中都包含此文件。它会导入自动生成的本地 manifest,并为远程 URL 附加手写的 bundle:
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",
        },
      ],
    },
  ],
};
常见的支持 CORS 的托管选项(价格、限制和可编辑性的完整对比见 pixi-vn.com/start/assets#assets-hosting)包括:GitHub raw URL(
https://raw.githubusercontent.com/[repo]/refs/heads/main/[path]
,免费,单文件限制 100MB)、图像托管服务(imgbb/imgur,仅支持图像),以及适用于更大文件或非图像资源的云存储(Cloudflare R2、Firebase Storage、S3、Supabase)。无论使用哪种托管服务,都必须允许 CORS 请求,否则浏览器会阻止资源加载。

Organizing assets into bundles

将资源组织成 bundle

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
  ],
};
应将相关资源分组到 bundle 中,而不是逐个松散地注册——一个 bundle 会作为单一单元加载或延迟加载。每个 bundle 的名称都应基于其使用位置:
  • 仅某个 label 需要的资源,使用该 label 的 id(例如
    startLabel.id
    )
  • 某个特定屏幕需要的资源,使用该 route 的 path(例如
    "/"
    、
    "/game/narration"
    )
  • 某个角色的 sprite/expression 资源,使用该角色的 id
这一约定对于在线资源尤其重要,因为它会直接决定玩家需要等待什么;对于本地资源(通常几乎瞬间加载),精确的 bundle 边界则没那么重要。
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.
在应用启动阶段注册一次 manifest,并确保在依赖任何 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
}
随后,在任何位置都只通过 alias 引用资源(图像/视频使用
pixi-vn-canvas
,音频使用
pixi-vn-sound
):
ts
const texture = await Assets.load("mc-neutral");
默认情况下,资源会按需加载;但等到确切需要某项资源时才加载,可能会造成可见的卡顿。常见的推荐加载时机如下,从最少预加载到最积极预加载排列:
  • 在 label 开始时后台加载——在 label 的
    onLoadingLabel
    hook 中调用
    Assets.backgroundLoadBundle(label.id)
    (fire-and-forget):label 会立即开始,其 bundle 在不阻塞的情况下流式加载。除非某个步骤确实无法在缺少该资源的情况下继续,否则应优先使用此方式,而不是下方的阻塞方式。
  • 在 label 开始时阻塞加载——使用同一个 hook,但执行
    await Assets.loadBundle(label.id)
    :label 会等待其 bundle 加载完成后才运行第一个步骤。仅当 label 确实无法在没有这些资源的情况下启动时才使用。
  • 在项目启动时加载——对第一个屏幕会使用的资源执行
    await Assets.loadBundle(...)
    (建议限制在 100MB 以下,以缩短初始等待时间)。
  • 在项目启动时后台加载——对较大且无需阻塞的预加载资源执行
    Assets.backgroundLoadBundle(...)
    (建议限制在 2GB 以下)。
  • 在 route/屏幕渲染前加载——在 router 的
    loader
    中(例如 TanStack Router)调用
    await Assets.loadBundle(routePath)
    ,并配合使用
    pendingComponent
    ,让玩家看到加载状态而不是空白屏幕。

Real-world project convention (official React template)

真实项目约定(官方 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'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.
官方 React template 中,
src/lib/utils/assets-utility.ts
的
defineAssets()
combines 了上述多种策略:使用
assetsInitialized
标志防止
Assets.init
重复执行(可安全调用多次),阻塞加载当前 route 的 bundle(
await Assets.loadBundle("/")
),确保第一个屏幕不会显示缺失资源,随后在后台启动 audio bundle(
sound.backgroundLoadBundle(...)
——这是 sound 模块自己的 wrapper,而不是
Assets.backgroundLoadBundle
,因为它还需要将已加载的媒体接入 channel)。
此外,
main.tsx
只在全局连接一次
Game.onLoadingLabel((_stepId, { id }) => Assets.backgroundLoadBundle(id))
——这样每个 label 的 bundle(按照上述约定,以其 id 命名)都会在 label 开始时自动后台加载,无需每个 label 自行实现
onLoadingLabel
hook。这是 template 的约定,而不是库的要求——项目可以根据需要选择更积极或更少的预加载策略。
如果需要缓存在线资源,以免回访玩家重复下载:官方 template 会预配置 VitePWA 的 service worker,默认缓存来自指定 hostname 的响应(
vite.config.ts
中的
CACHED_EXTERNAL_HOSTNAMES
列表),缓存时间为 7 天——请将实际使用的所有 CDN/host 添加到该列表中。参见 pixi-vn.com/start/assets#caching-assets。

Related skills

相关 skill

  • 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
    ).
  • pixi-vn-getting-started——
    @drincs/pixi-vn/vite
    插件的
    assetsManifest
    选项可将 manifest(通常是此 skill 中的
    src/assets/index.ts
    )接入插件生成的类型文件和开发服务器 endpoint;
    Game.onLoadingLabel
    是连接 label 范围 bundle 加载的位置。
  • pixi-vn-canvas——在 alias 加载完成后显示图像和视频。
  • pixi-vn-sound——在 alias 加载完成后播放音频,以及 sound 模块自己的
    backgroundLoadBundle
    /
    loadBundle
    wrapper。
  • pixi-vn-narration——label 是天然的 bundle 命名单元(
    label.id
    )。