pixi-vn-assets
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePixi'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
/ — only how it gets registered and made available by alias.
pixi-vn-canvaspixi-vn-soundThe one rule that matters most: always reference assets by their manifest , never by raw
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. , not .
aliassrcAssets.load("bg01-hallway")Assets.load("https://.../bg01-hallway.webp")当任务涉及向项目中添加新的图像/音频/视频文件、决定某项资源应存放在本地还是在线托管、编辑 assets manifest,或选择何时加载资源 bundle(启动时阻塞加载、后台加载、按 label、按 route)时,请使用此 skill。
此 skill 不涵盖图像/音频加载后如何显示或播放——那属于
/ ——只涵盖如何注册资源并使其可通过 alias 使用。
pixi-vn-canvaspixi-vn-sound**最重要的一条规则:在游戏代码中,始终通过 manifest 的 引用资源,绝不要使用原始 路径或 URL。**强烈不建议按路径直接加载——这会将代码耦合到特定的文件位置或托管服务,因此移动文件或切换托管服务会悄无声息地破坏所有引用。应使用 ,而不是 。
aliassrcAssets.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.
| Local | Online | |
|---|---|---|
| Bundled into the app | Yes — ships in | 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 |
资源可以存储在项目的本地,也可以在线托管(CDN、GitHub raw、云存储)。频繁使用的资源(角色 sprite、背景、背景音乐)最好保存在本地;只使用一次的资源或大型可选文件则更适合在线托管,以减小项目体积并加快初始下载速度。
| 本地 | 在线 | |
|---|---|---|
| 随应用打包 | 是——随 | 否——在运行时从 URL 获取 |
| 最适合 | 频繁使用的资源(背景、sprite、核心音乐) | 大型、可选或低频使用的资源 |
| 要求 | PixiJS AssetPack(官方 template 中已预配置) | 支持 CORS 且提供公开 URL 的托管服务 |
Local assets: the src/assets
folder + PixiJS AssetPack
src/assets本地资源:src/assets
文件夹与 PixiJS AssetPack
src/assetsLocal assets go under (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 () or once before a production build (), optimizes the files
(compression, WebP/AVIF conversion, texture-atlas packing) into , and auto-generates
describing every resulting bundle/alias.
src/assetsvite devvite buildpublic/assetssrc/assets/manifest.gen.jsonts
// 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.jsonsrc/assetsjson
// src/assets/manifest.gen.json (excerpt — auto-generated, read-only)
{
"bundles": [
{
"name": "mc",
"assets": [{ "alias": "mc-neutral", "src": "./assets/mc/mc-neutral.png" }]
}
]
}本地资源应放在 下(可以使用任意文件夹结构,但所有官方 template 都遵循这一约定),并按适合项目的方式组织子文件夹。PixiJS AssetPack——已接入每个官方 template 的 Vite config——会监视此文件夹,并在每次变更时(),或在生产构建前执行一次(),将文件优化(压缩、WebP/AVIF 转换、texture-atlas 打包)至 ,并自动生成 ,其中描述每个最终生成的 bundle/alias。
src/assetsvite devvite buildpublic/assetssrc/assets/manifest.gen.jsonts
// 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
],
};** 是自动生成的——切勿手动编辑。**任何手动修改都会在 AssetPack 下次运行时被覆盖。如果某个本地资源没有以预期的 alias 出现,请检查该文件在 下的实际 path/name,并让 AssetPack 重新生成文件,而不是直接编辑 JSON。
src/assets/manifest.gen.jsonsrc/assetsjson
// 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在线资源:准确的注册位置——src/assets/index.ts
src/assets/index.tsFor assets you host online (or any manifest entries beyond what AssetPack generates), the one
place to add/edit them is , present in every official template. It imports
the auto-generated local manifest and appends hand-written bundles for remote URLs:
src/assets/index.tsts
// 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 (, 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.
https://raw.githubusercontent.com/[repo]/refs/heads/main/[path]对于在线托管的资源(或 AssetPack 生成内容之外的任何 manifest 条目),唯一应该添加或修改它们的位置是 。每个官方 template 中都包含此文件。它会导入自动生成的本地 manifest,并为远程 URL 附加手写的 bundle:
src/assets/index.tsts
// 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(,免费,单文件限制 100MB)、图像托管服务(imgbb/imgur,仅支持图像),以及适用于更大文件或非图像资源的云存储(Cloudflare R2、Firebase Storage、S3、Supabase)。无论使用哪种托管服务,都必须允许 CORS 请求,否则浏览器会阻止资源加载。
https://raw.githubusercontent.com/[repo]/refs/heads/main/[path]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. ) for assets only that label needs
startLabel.id - a route's path (e.g. ,
"/") for assets a specific screen needs"/game/narration" - 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 ( for images/video,
for audio):
pixi-vn-canvaspixi-vn-soundts
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 hook, call
onLoadingLabel(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.Assets.backgroundLoadBundle(label.id) - At label start (blocking) — same hook, but : the label waits for its bundle before its first step runs. Only use this when the label truly can't start without those assets.
await Assets.loadBundle(label.id) - At project start — for anything used on the very first screen (recommended limit: under 100MB, so the initial wait stays short).
await Assets.loadBundle(...) - In the background at project start — for larger, non-blocking preloading (recommended limit: under 2GB).
Assets.backgroundLoadBundle(...) - Before a route/screen renders — inside a router's (e.g. TanStack Router), call
loader, paired with aawait Assets.loadBundle(routePath)so the player sees a loading state instead of a blank screen.pendingComponent
在应用启动阶段注册一次 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-canvaspixi-vn-soundts
const texture = await Assets.load("mc-neutral");默认情况下,资源会按需加载;但等到确切需要某项资源时才加载,可能会造成可见的卡顿。常见的推荐加载时机如下,从最少预加载到最积极预加载排列:
- 在 label 开始时后台加载——在 label 的 hook 中调用
onLoadingLabel(fire-and-forget):label 会立即开始,其 bundle 在不阻塞的情况下流式加载。除非某个步骤确实无法在缺少该资源的情况下继续,否则应优先使用此方式,而不是下方的阻塞方式。Assets.backgroundLoadBundle(label.id) - 在 label 开始时阻塞加载——使用同一个 hook,但执行 :label 会等待其 bundle 加载完成后才运行第一个步骤。仅当 label 确实无法在没有这些资源的情况下启动时才使用。
await Assets.loadBundle(label.id) - 在项目启动时加载——对第一个屏幕会使用的资源执行 (建议限制在 100MB 以下,以缩短初始等待时间)。
await Assets.loadBundle(...) - 在项目启动时后台加载——对较大且无需阻塞的预加载资源执行 (建议限制在 2GB 以下)。
Assets.backgroundLoadBundle(...) - 在 route/屏幕渲染前加载——在 router 的 中(例如 TanStack Router)调用
loader,并配合使用await Assets.loadBundle(routePath),让玩家看到加载状态而不是空白屏幕。pendingComponent
Real-world project convention (official React template)
真实项目约定(官方 React template)
src/lib/utils/assets-utility.tsdefineAssets()Assets.initassetsInitializedawait Assets.loadBundle("/")sound.backgroundLoadBundle(...)Assets.backgroundLoadBundlemain.tsxGame.onLoadingLabel((_stepId, { id }) => Assets.backgroundLoadBundle(id))onLoadingLabelFor 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 list in ) for 7 days by default —
add any CDN/host actually used to that list. See
pixi-vn.com/start/assets#caching-assets.
CACHED_EXTERNAL_HOSTNAMESvite.config.ts官方 React template 中, 的 combines 了上述多种策略:使用 标志防止 重复执行(可安全调用多次),阻塞加载当前 route 的 bundle(),确保第一个屏幕不会显示缺失资源,随后在后台启动 audio bundle(——这是 sound 模块自己的 wrapper,而不是 ,因为它还需要将已加载的媒体接入 channel)。
src/lib/utils/assets-utility.tsdefineAssets()assetsInitializedAssets.initawait Assets.loadBundle("/")sound.backgroundLoadBundle(...)Assets.backgroundLoadBundle此外, 只在全局连接一次 ——这样每个 label 的 bundle(按照上述约定,以其 id 命名)都会在 label 开始时自动后台加载,无需每个 label 自行实现 hook。这是 template 的约定,而不是库的要求——项目可以根据需要选择更积极或更少的预加载策略。
main.tsxGame.onLoadingLabel((_stepId, { id }) => Assets.backgroundLoadBundle(id))onLoadingLabel如果需要缓存在线资源,以免回访玩家重复下载:官方 template 会预配置 VitePWA 的 service worker,默认缓存来自指定 hostname 的响应( 中的 列表),缓存时间为 7 天——请将实际使用的所有 CDN/host 添加到该列表中。参见
pixi-vn.com/start/assets#caching-assets。
vite.config.tsCACHED_EXTERNAL_HOSTNAMESRelated skills
相关 skill
- pixi-vn-getting-started — the plugin's
@drincs/pixi-vn/viteoption bridges a manifest (often this skill'sassetsManifest) into the plugin's generated type file and dev-server endpoint;src/assets/index.tsis where label-scoped bundle loading is wired.Game.onLoadingLabel - 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
/
backgroundLoadBundlewrappers.loadBundle - pixi-vn-narration — labels are a natural bundle-naming unit ().
label.id
- pixi-vn-getting-started——插件的
@drincs/pixi-vn/vite选项可将 manifest(通常是此 skill 中的assetsManifest)接入插件生成的类型文件和开发服务器 endpoint;src/assets/index.ts是连接 label 范围 bundle 加载的位置。Game.onLoadingLabel - pixi-vn-canvas——在 alias 加载完成后显示图像和视频。
- pixi-vn-sound——在 alias 加载完成后播放音频,以及 sound 模块自己的 /
backgroundLoadBundlewrapper。loadBundle - pixi-vn-narration——label 是天然的 bundle 命名单元()。
label.id