pixi-vn-getting-started
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePixi'VN project initialization and the Game API
Pixi'VN 项目初始化与 Game API
Official docs: pixi-vn.com/start. For a full machine-readable index of every Pixi'VN doc page — useful whenever a task needs more detail than this skill set covers — see pixi-vn.com/llms.txt.
官方文档:pixi-vn.com/start。如需获取所有 Pixi'VN 文档页面的完整机器可读索引——当任务需要比本 skill 集更详细的信息时,该索引会很有用——请参阅 pixi-vn.com/llms.txt。
When to use this skill
何时使用此 skill
Use this skill whenever you are bootstrapping a Pixi'VN project from scratch, wiring up , or need to understand what / actually do before wiring narration, canvas, sound, storage, or history features. Every other Pixi'VN skill assumes has already run.
src/main.tsGame.initGame.startGame.init在从头引导 Pixi'VN 项目、配置 ,或需要在配置 narration、canvas、sound、storage 或 history 功能之前了解 / 的实际作用时,使用此 skill。本集合中的每个其他 Pixi'VN skill 都假定 已经运行。
src/main.tsGame.initGame.startGame.initInstalling the library
安装库
Two paths, depending on whether a project already exists:
-
New project — scaffold from an official template (recommended for anything from scratch):bash
npm create pixi-vn@latestThis generates a working project (index.html, entry point, bundler config) already wired to call, with pre-built menu/narration screens, VS Code launch configs, and (since template v2.0.0) TanStack Router for navigation. See pixi-vn.com/start/templates for the full generated project structure, available templates/demos, and how to run/customize one.Game.initThe wizard asks for a narrative language and defaults/suggests Ink + TypeScript (it's listed first and pre-selected in the prompt for the Visual Novel, Text-based Story, and Point & Click Adventure project types) — plain TypeScript narration (what every skill in this set documents) is the other option, selectable in the same prompt. Ink support is a separate narrative language with its own syntax and its own future skill; nothing in this skill set requires it.The wizard's first question is the project/game type; the docs have a dedicated getting-started walkthrough for some of them — pixi-vn.com/start/make-visual-novel for "Visual Novel", pixi-vn.com/nqtr/make-point-and-click for "Point & Click Adventure" — worth pointing a developer to the matching one for their project type, in addition to the general pixi-vn.com/start/templates page above. -
Existing JS/TS project — add the package directly:bash
npm install @drincs/pixi-vn
根据项目是否已经存在,有两种安装路径:
-
新项目——基于官方模板搭建脚手架(对于任何从零开始的项目均推荐):bash
npm create pixi-vn@latest这会生成一个可运行的项目(index.html、入口点和 bundler 配置),并已配置为调用,同时包含预构建的菜单/narration 屏幕、VS Code 启动配置,以及(自模板 v2.0.0 起)用于导航的 TanStack Router。请参阅 pixi-vn.com/start/templates,了解生成项目的完整结构、可用模板/演示,以及如何运行或自定义模板。Game.init向导的第一个问题是项目/游戏类型;文档为其中一些类型提供了专门的入门教程——pixi-vn.com/start/make-visual-novel 对应“Visual Novel”,pixi-vn.com/nqtr/make-point-and-click 对应“Point & Click Adventure”——除了一般的 pixi-vn.com/start/templates 页面外,还应根据开发者的项目类型将其引导至匹配的教程。向导会询问叙事语言,并默认/建议使用 Ink + TypeScript(在 Visual Novel、Text-based Story 和 Point & Click Adventure 项目类型的提示中,它都列在第一位并被预先选中)——纯 TypeScript narration(也是本 skill 集合中每个 skill 所记录的内容)是另一个选项,可在同一提示中选择。Ink 支持是一种独立的叙事语言,拥有自己的语法和未来的专属 skill;本 skill 集合不要求使用它。 -
现有 JS/TS 项目——直接添加该包:bash
npm install @drincs/pixi-vn
Required peer dependencies
必需的 peer dependencies
@drincs/pixi-vnpackage.json| Package | Required version |
|---|---|
| |
| |
| |
| |
| |
| |
With npm 7+, auto-installs these peer dependencies too — no separate install step is needed in the common case. If a developer is on an older npm, or uses a package manager that doesn't auto-install peers (e.g. pnpm, or yarn in strict mode), they'll need to install them explicitly (). If a developer reports errors about missing modules like , , or , check their package manager/version and whether these ended up installed before looking elsewhere.
npm install @drincs/pixi-vnnpm install pixi.js pixi-filters tone motion @pixi/devtoolspixi.jstonemotion@drincs/pixi-vnpackage.json| 包 | 所需版本 |
|---|---|
| |
| |
| |
| |
| |
| |
在 npm 7+ 中, 也会自动安装这些 peer dependencies——常见情况下无需单独执行安装步骤。如果开发者使用较旧的 npm,或使用不会自动安装 peer dependencies 的包管理器(例如 pnpm,或 strict mode 下的 yarn),则需要显式安装它们()。如果开发者报告缺少 、 或 等模块的错误,应先检查其包管理器/版本,以及这些包是否实际安装完成,再排查其他问题。
npm install @drincs/pixi-vnnpm install pixi.js pixi-filters tone motion @pixi/devtoolspixi.jstonemotionImport paths: @drincs/pixi-vn
vs subpaths
@drincs/pixi-vn导入路径:@drincs/pixi-vn
与子路径
@drincs/pixi-vnEvery module (canvas, characters, history, narration, sound, storage, ...) is available both from the package root and from its own subpath, e.g. // all from , or alone from . Default to importing from the root — that's what every official doc example and template does:
Gamecanvassound@drincs/pixi-vncanvas@drincs/pixi-vn/canvas@drincs/pixi-vnts
import { canvas, Game, narration, sound, storage } from "@drincs/pixi-vn";Reach for a narrower subpath (, , , , , , , ) only when there's a concrete reason to avoid pulling in the whole package surface — e.g. a headless/server-side environment where canvas or sound isn't available or permitted, a size-constrained bundle target, or code that genuinely only ever touches one module. This applies to every skill in this set; it isn't repeated per-module.
@drincs/pixi-vn/canvas/sound/characters/storage/narration/core/pixi.js/motion每个模块(canvas、characters、history、narration、sound、storage 等)都可以从包根目录导入,也可以从各自的子路径导入,例如 // 都可从 导入,或仅从 导入 。默认从根路径 导入——所有官方文档示例和模板都采用这种方式:
Gamecanvassound@drincs/pixi-vn@drincs/pixi-vn/canvascanvas@drincs/pixi-vnts
import { canvas, Game, narration, sound, storage } from "@drincs/pixi-vn";只有在存在避免引入整个包 API 的明确理由时,才应使用更窄的子路径(、、、、、、、)——例如 canvas 或 sound 不可用或不允许使用的 headless/server-side 环境、对包体积有严格限制的构建目标,或代码确实只涉及一个模块。这条规则适用于本集合中的每个 skill,因此不会在各模块中重复说明。
@drincs/pixi-vn/canvas/sound/characters/storage/narration/core/pixi.js/motionInitializing the game: Game.init
Game.init初始化游戏:Game.init
Game.initGame.init-
Full initialization — sets up the internalstate manager and creates the PixiJS
GameUnifierplus the canvas' interfaceApplication, attached to an HTML element:<div>tsexport async function init( element: HTMLElement, options: Partial<ApplicationOptions> & { id?: string; // default: "pixi-vn-canvas" navigate?: (path: string) => void | Promise<void>; resizeMode?: "contain" | "none"; // default: "contain" }, devtoolsOptions?: Devtools, ): Promise<void>;- is
options— i.e. every option accepted by a PixiJSPartial<ApplicationOptions>(Application,width,height,backgroundColor, ...) — plus three Pixi'VN-specific extras:antialias- : id of the generated canvas element.
id - : the app's router navigation function (can also be set later with
navigate).Game.onNavigate - :
resizeMode(fit within the parent, keep aspect ratio — default) or"contain"."none"
- configures the PixiJS Devtools integration.
devtoolsOptions
-
Headless initialization —with no arguments. This initializes only the internal
Game.init()(storage, narration, history bookkeeping) without creating a PixiJSGameUnifieror canvas. Useful if you only need save/load, storage, or narration logic without rendering. If you call the 3-argument form but omitApplication/element,optionslogs a warning and skips canvas setup the same way.Game.init
Game.init-
完整初始化——设置内部状态管理器_并_创建 PixiJS
GameUnifier以及 canvas 的界面Application,再将其附加到 HTML 元素:<div>tsexport async function init( element: HTMLElement, options: Partial<ApplicationOptions> & { id?: string; // default: "pixi-vn-canvas" navigate?: (path: string) => void | Promise<void>; resizeMode?: "contain" | "none"; // default: "contain" }, devtoolsOptions?: Devtools, ): Promise<void>;- 为
options——即 PixiJSPartial<ApplicationOptions>接受的所有选项(Application、width、height、backgroundColor等),另外还有三个 Pixi'VN 专属扩展项:antialias- :生成的 canvas 元素的 id。
id - :应用的路由导航函数(也可稍后通过
navigate设置)。Game.onNavigate - :
resizeMode(适配父容器并保持宽高比——默认值)或"contain"。"none"
- 用于配置 PixiJS Devtools 集成。
devtoolsOptions
-
Headless 初始化——不带参数调用。这只会初始化内部
Game.init()(用于 storage、narration、history 状态管理),不会创建 PixiJSGameUnifier或 canvas。如果调用三参数形式但省略Application/element,options会记录警告并同样跳过 canvas 设置。Game.init
Complete example
完整示例
The official docs' inline example calls and wires as a separate statement (see pixi-vn.com/start); both that style and are valid, and the option can be passed inline instead of via a separate call. Here is the shape matching the current signature in :
.then()Game.onNavigateawaitnavigateGame.onNavigatesrc/index.tsts
// src/main.ts
import { Game } from "@drincs/pixi-vn";
const body = document.body;
if (!body) {
throw new Error("body element not found");
}
await Game.init(body, {
width: 1920,
height: 1080,
backgroundColor: "#303030",
resizeMode: "contain",
navigate: (path) => {
// e.g. window.history.pushState({}, "", path)
},
});
Game.onEnd(async (props) => {
Game.clear();
props.navigate("/end");
});
Game.addOnError((error, props) => {
console.error("Error occurred", error);
});
await Game.start("start", {});Game.startGame.clear()Game.clear()props.navigateStepLabelPropspixi-vn.d.tsStepLabelPropsGame.onEndGame.addOnErrornavigatets
declare module "@drincs/pixi-vn" {
interface StepLabelProps {
navigate: (route: string) => void;
}
}See also: pixi-vn.com/start/interface-navigate for the full navigation pattern (including the ink hashtag command and blocking the browser back/forward buttons). For the full set of fields the official React template actually adds this way (router , i18n, , interface invalidation), see "Real-world project layout" below.
# navigatenavigatetoast官方文档中的内联示例使用 ,并将 作为一条独立语句进行连接(见 pixi-vn.com/start);这种写法和 都有效,也可以在调用 时内联传入 选项,以代替单独调用 。下面是与当前 签名一致的形式:
.then()Game.onNavigateawaitGame.initnavigateGame.onNavigatesrc/index.tsts
// src/main.ts
import { Game } from "@drincs/pixi-vn";
const body = document.body;
if (!body) {
throw new Error("body element not found");
}
await Game.init(body, {
width: 1920,
height: 1080,
backgroundColor: "#303030",
resizeMode: "contain",
navigate: (path) => {
// e.g. window.history.pushState({}, "", path)
},
});
Game.onEnd(async (props) => {
Game.clear();
props.navigate("/end");
});
Game.addOnError((error, props) => {
console.error("Error occurred", error);
});
await Game.start("start", {});Game.startGame.clear()Game.clear()上面的 并不是 Pixi'VN 内置 prop——它之所以存在,是因为项目通过声明合并(declaration merging)扩展了 (通常放在 文件中,所有官方模板都采用这种方式)。传给 、 以及每个 narration step 的 props 也使用同一个 类型,因此无论在其中添加什么( 函数或任何其他自定义 prop),都会在上述所有位置可用:
props.navigateStepLabelPropspixi-vn.d.tsGame.onEndGame.addOnErrorStepLabelPropsnavigatets
declare module "@drincs/pixi-vn" {
interface StepLabelProps {
navigate: (route: string) => void;
}
}另请参阅 pixi-vn.com/start/interface-navigate,了解完整的导航模式(包括 ink hashtag 命令,以及阻止浏览器前进/后退按钮)。若要了解官方 React 模板实际以这种方式添加的完整字段集合(router 、i18n、、interface invalidation),请参阅下方的“实际项目结构”。
# navigatenavigatetoastThe Game.*
functions
Game.*Game.*
函数
Game.*-
— must be called first; see above. Returns a
Game.init(element?, options?, devtoolsOptions?).Promise<void> -
— clears all game data (
Game.start<T>(label, props)) and begins narration from the given label (aGame.clear()string, or aLabelIdTypeinstance). Throws if the label isn't found.LabelAbstracttsawait Game.start("start", {}); -
— resets everything: storage, canvas (if initialized), sound, narration, and history. Used to reset the game (e.g. returning to the main menu).
Game.clear() -
— registers the function run when all
Game.onEnd(value: StepLabelType)of allstepshave been executed (no more steps/labels to continue into). Set it once during initialization. The docs note three common ending strategies: the game simply ends here; the game has no real end and this handler restarts/loops back into a label; or the game ends earlier at a specific point (e.g. game over) without ever relying on this handler.labelsts// end the game and navigate to an end screen Game.onEnd(async (props) => { props.navigate("/end"); });ts// no real end: loop back into the start label instead Game.onEnd(async (props) => { narration.call(startLabel, props); }); -
— registers an error handler; multiple handlers can be added and run in registration order (sync or async). Returns a function that removes the handler. Check
Game.addOnError(handler: OnErrorHandler)(fromerror instanceof PixiError) to special-case Pixi'VN's own errors (e.g. missing assets). A companion helper,@drincs/pixi-vn/core(fromdrawCanvasErrorHandler()), returns a ready-made handler that draws a red placeholder box on the canvas when a@drincs/pixi-vn/canvascarries canvas element info — useful during development to visualize broken canvas elements instead of silently failing.PixiErrortsGame.addOnError((error, props) => { if (error instanceof PixiError) { // Pixi'VN-specific error (has error.code, maybe error.canvasElementInfo) } // any extra field on `props` (e.g. a `toast` helper for showing an error // notification) is only available if you added it to `StepLabelProps` // yourself — see the `navigate` example above, and the fuller // `toast`/`invalidateInterfaceData` example later in this skill. });unregisters a previously added handler.Game.removeOnError(handler) -
— registers (or replaces) the app's routing function, used whenever Pixi'VN needs to navigate (e.g. on
Game.onNavigate(value: (path: string) => void | Promise<void>), or restoring a save). Equivalent to passingGame.onEndinline tonavigate.Game.inittsGame.onNavigate((path) => router.push(path)); -
/
Game.exportGameState()/Game.restoreGameState(data)— the save/load trio. Full detail, including the official template's save-slot/quick-save/IndexedDB convention, is covered byGame.jsonToGameState(json).pixi-vn-saves -
Narration lifecycle hooks —,
Game.onStepStart,Game.onStepEnd(fires on step 0 and on save-load — the hook a label uses to load/background-load its own asset bundle, seeGame.onLoadingLabel),pixi-vn-assets, andGame.onLabelStartinglet you intercept/defer label transitions (e.g. to run a UI transition before the next label actually starts). These are advanced hooks — most projects only needGame.onLabelClosing.onEnd -
/
Game.addOnPreContinue(handler)— register handlers that run immediately before every narration "continue" step (e.g. auto-save on each step).Game.removeOnPreContinue(handler)
-
——必须最先调用;见上文。返回
Game.init(element?, options?, devtoolsOptions?)。Promise<void> -
——清除所有游戏数据(
Game.start<T>(label, props)),并从给定 label(一个Game.clear()字符串,或一个LabelIdType实例)开始 narration。如果找不到该 label,则会抛出异常。LabelAbstracttsawait Game.start("start", {}); -
——重置所有内容:storage、canvas(如果已初始化)、sound、narration 和 history。用于重置游戏(例如返回主菜单)。
Game.clear() -
——注册当所有
Game.onEnd(value: StepLabelType)的所有labels执行完毕时运行的函数(没有更多 steps/labels 可继续执行)。应在初始化期间设置一次。文档列出了三种常见的结束策略:游戏就在此处结束;游戏没有真正的结局,此处理器重新启动/循环回到某个 label;或者游戏在更早的特定位置结束(例如 game over),完全不依赖此处理器。stepsts// end the game and navigate to an end screen Game.onEnd(async (props) => { props.navigate("/end"); });ts// no real end: loop back into the start label instead Game.onEnd(async (props) => { narration.call(startLabel, props); }); -
——注册错误处理器;可以添加多个处理器,并按注册顺序运行(同步或异步)。返回一个用于移除该处理器的函数。可通过检查
Game.addOnError(handler: OnErrorHandler)(来自error instanceof PixiError)来单独处理 Pixi'VN 自身的错误(例如缺少 assets)。配套辅助函数@drincs/pixi-vn/core(来自drawCanvasErrorHandler())返回一个现成的 handler:当@drincs/pixi-vn/canvas带有 canvas element info 时,它会在 canvas 上绘制一个红色占位框——这在开发期间很有用,可以将损坏的 canvas 元素可视化,而不是静默失败。PixiErrortsGame.addOnError((error, props) => { if (error instanceof PixiError) { // Pixi'VN-specific error (has error.code, maybe error.canvasElementInfo) } // any extra field on `props` (e.g. a `toast` helper for showing an error // notification) is only available if you added it to `StepLabelProps` // yourself — see the `navigate` example above, and the fuller // `toast`/`invalidateInterfaceData` example later in this skill. });用于注销之前添加的 handler。Game.removeOnError(handler) -
——注册(或替换)应用的路由函数,Pixi'VN 需要进行导航时会调用它(例如在
Game.onNavigate(value: (path: string) => void | Promise<void>)时或恢复存档时)。这等同于在Game.onEnd中内联传入Game.init。navigatetsGame.onNavigate((path) => router.push(path)); -
/
Game.exportGameState()/Game.restoreGameState(data)——存档/读档三件套。完整细节,包括官方模板的存档槽/快速存档/IndexedDB 约定,均由Game.jsonToGameState(json)涵盖。pixi-vn-saves -
Narration 生命周期 hooks——、
Game.onStepStart、Game.onStepEnd(在 step 0 和存档/读档时触发;label 可通过此 hook 加载/后台加载自己的 asset bundle,参见Game.onLoadingLabel)、pixi-vn-assets和Game.onLabelStarting允许拦截或延迟 label 转换(例如在下一个 label 实际开始前运行 UI 转场)。这些属于高级 hook——大多数项目只需要Game.onLabelClosing。onEnd -
/
Game.addOnPreContinue(handler)——注册处理器,在每个 narration “continue” step 之前立即运行(例如在每一步执行 autosave)。Game.removeOnPreContinue(handler)
Project structure
项目结构
A typical Pixi'VN project (as generated by , or as set up manually):
npm create pixi-vn@latestindex.html # loads /src/main.ts as an ES module
src/main.ts # calls Game.init(), Game.onEnd(), Game.addOnError(), Game.onNavigate(), Game.start()
styles.css # full-viewport, no scroll/margin, dark background behind the canvasThe canonical minimal mounts a root that becomes (or contains) the element passed to , and loads the entry point as an ES module:
index.html<div id="root">Game.inithtml
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Game</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>Paired with a that makes the canvas fill the viewport with no scrollbars or margin:
styles.csscss
html,
body {
background-color: #242424;
height: 100%;
}
body {
margin: 0;
min-height: 100vh;
display: flex;
overflow: hidden;
}Real templates generated by are richer than this minimal sketch: narrative content lives under ( scripts) and (, ), UI screens under / (TanStack Router), and is exactly where the augmentation shown above lives. Full breakdown: pixi-vn.com/start/templates#project-structure.
npm create pixi-vn@latestink/.inksrc/content/labels/*.label.tscharacters.tssrc/components/src/routes/src/pixi-vn.d.tsStepLabelPropsNote also that Pixi'VN deliberately has no built-in UI components — buttons, menus, and HUD are built with a regular JS framework (React/Vue/PixiJS) layered over the canvas via /. Canvas state is included in saves; UI state is not, so persist any UI-only state yourself. See pixi-vn.com/start/interface.
canvas.htmlLayers.addcanvas.layers.addA few constants worth knowing about (exported from , defined in ): (the installed library version, embedded in for save-file compatibility checks) and (the alias of the root canvas layer). lists storage keys Pixi'VN reserves internally (dialogue, choices, input, characters, etc.) — avoid reusing these names for custom storage variables.
@drincs/pixi-vnsrc/constants.tsPIXIVN_VERSIONGameState.pixivn_versionCANVAS_APP_GAME_LAYER_ALIASSYSTEM_RESERVED_STORAGE_KEYS典型的 Pixi'VN 项目(由 生成,或手动搭建):
npm create pixi-vn@latestindex.html # loads /src/main.ts as an ES module
src/main.ts # calls Game.init(), Game.onEnd(), Game.addOnError(), Game.onNavigate(), Game.start()
styles.css # full-viewport, no scroll/margin, dark background behind the canvas规范的最小 会挂载根 ,该元素将成为(或包含)传给 的元素,并将入口点作为 ES module 加载:
index.html<div id="root">Game.inithtml
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Game</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>再配上一份让 canvas 填满视口且不出现滚动条或边距的 :
styles.csscss
html,
body {
background-color: #242424;
height: 100%;
}
body {
margin: 0;
min-height: 100vh;
display: flex;
overflow: hidden;
}由 生成的实际模板比上述最小草图丰富:叙事内容位于 ( 脚本)和 (、)下,UI 屏幕位于 /(TanStack Router),而 正是上文 扩展所在的文件。完整说明请参阅 pixi-vn.com/start/templates#project-structure。
npm create pixi-vn@latestink/.inksrc/content/labels/*.label.tscharacters.tssrc/components/src/routes/src/pixi-vn.d.tsStepLabelProps还请注意,Pixi'VN 有意不提供内置 UI 组件——按钮、菜单和 HUD 使用常规 JS 框架(React/Vue/PixiJS)构建,并通过 / 叠加在 canvas 上。Canvas 状态会纳入存档;UI 状态不会,因此任何仅属于 UI 的状态都需要自行持久化。参见 pixi-vn.com/start/interface。
canvas.htmlLayers.addcanvas.layers.add还有一些值得了解的常量(从 导出,定义于 ):(已安装的库版本,会嵌入 ,用于检查存档文件兼容性)以及 (根 canvas layer 的别名)。 列出了 Pixi'VN 内部保留的 storage key(dialogue、choices、input、characters 等)——不要将这些名称用于自定义 storage 变量。
@drincs/pixi-vnsrc/constants.tsPIXIVN_VERSIONGameState.pixivn_versionCANVAS_APP_GAME_LAYER_ALIASSYSTEM_RESERVED_STORAGE_KEYSThe Vite plugin: @drincs/pixi-vn/vite
@drincs/pixi-vn/viteVite 插件:@drincs/pixi-vn/vite
@drincs/pixi-vn/viteEvery official template wires up in — for any Vite-based project, this plugin is the backbone that makes content (characters/labels) and asset manifests available consistently in dev and build, and it is what generates the string-literal ID types (, , ...) used everywhere else in this skill set. Reach for it whenever a developer's isn't getting autocomplete/type-checking on the label id, or asks how characters/labels "just work" without a manual import list.
vitePluginPixivnvite.config.tsCharacterIdTypeLabelIdTypenarration.call("someLabel")ts
// vite.config.ts
import { vitePluginPixivn } from "@drincs/pixi-vn/vite";
export default defineConfig({
plugins: [
vitePluginPixivn({
content: "./src/content/index.ts",
characters: "./src/content/characters.ts",
labels: "./src/content/labels/*.label.ts",
typeFilePath: "./src/pixi-vn.keys.gen.ts",
assetsManifest: async (ssrLoadModule) => {
const mod = await ssrLoadModule("./src/assets/index.ts");
return mod.manifest;
},
}),
],
});What it actually does:
- Loads content before anything else needs it. The /
content/charactersglob options are executed server-side (via Vite SSR, in dev and duringlabels) at startup, sovite build/RegisteredCharactersare fully populated before any other plugin (notably an ink compiler) or the app itself runs. Without this, whether your content has actually registered by the time something reads it would depend on unpredictable import ordering.RegisteredLabels - Generates type-safe ID unions. When is set, the plugin (re)writes that file on every startup and every content hot-reload with
typeFilePathaugmentations that narrowdeclare module,CharacterIdType,LabelIdType, andBundleIdTypefrom plainAssetAliasIdTypeto a union of the actual known literals — sostringbecomes a compile error instead of a runtime one. The same file also exports plainnarration.call("typo_label")arrays/enums (as const,characterIds,labelIds,bundleIds) for runtime validation (e.g.assetAliasIds). This generated file is excluded from HMR, so regenerating it never triggers a full page reload.z.enum(characterIdsEnum) - Bridges the PixiJS assets manifest. The option (a value or an async function, typically resolving to the
assetsManifestbuilt inmanifest— seesrc/assets/index.tsfor how that file is authored) feeds bundle/asset-alias ids into the same generated file, and seeds a dev-server endpoint (pixi-vn-assets) so devtools/other tooling can read the current manifest without the client having to push it first.GET /__pixi-vn/assets/manifest - Hot-reloads content correctly. Editing a watched label/character file clears the relevant registry and reloads just those files, regenerating the keys file — without a full page reload.
Don't hand-configure the underlying dev-server endpoints or / unless building a plugin that integrates with (e.g. an ink compiler) — for an app, the four options above (, , , , ) are the whole interface that matters.
GET/POST /__pixi-vn/*api.setExternalLabelsapi.setAssetsManifestvitePluginPixivncontentcharacterslabelstypeFilePathassetsManifestA related, smaller export, , provides — a client-side, dev-only function that POSTs the runtime assets manifest and current canvas size back to the same dev-server endpoints. Call it once after content is loaded () only as a fallback for cases where the manifest truly isn't knowable at time; the official templates prefer the function option above and don't need it.
@drincs/pixi-vn/vite-listenersetupPixivnViteData()await setupPixivnViteData()vite.config.tsassetsManifest每个官方模板都会在 中配置 ——对于任何基于 Vite 的项目,该插件都是核心骨干,可让 content(characters/labels)与 assets manifest 在开发和构建期间始终一致地可用;它还负责生成本 skill 集合其他各处使用的字符串字面量 ID 类型(、 等)。当开发者的 无法为 label id 提供自动补全/类型检查,或询问 characters/labels 如何在无需手动维护导入列表的情况下“正常工作”时,应使用该插件。
vite.config.tsvitePluginPixivnCharacterIdTypeLabelIdTypenarration.call("someLabel")ts
// vite.config.ts
import { vitePluginPixivn } from "@drincs/pixi-vn/vite";
export default defineConfig({
plugins: [
vitePluginPixivn({
content: "./src/content/index.ts",
characters: "./src/content/characters.ts",
labels: "./src/content/labels/*.label.ts",
typeFilePath: "./src/pixi-vn.keys.gen.ts",
assetsManifest: async (ssrLoadModule) => {
const mod = await ssrLoadModule("./src/assets/index.ts");
return mod.manifest;
},
}),
],
});它实际执行的工作如下:
- 在任何其他内容需要之前加载 content。 /
content/charactersglob 选项会在启动时通过 Vite SSR 在服务端执行(开发和labels期间皆然),因此在任何其他插件(尤其是 ink compiler)或应用本身运行之前,vite build/RegisteredCharacters就已完全填充。否则,内容在读取时是否已注册将取决于不可预测的 import 顺序。RegisteredLabels - 生成类型安全的 ID 联合类型。 设置 后,插件会在每次启动和每次 content 热重载时通过
typeFilePath扩展重写该文件,将declare module、CharacterIdType、LabelIdType和BundleIdType从普通AssetAliasIdType收窄为已知字面量的联合类型——因此string会在编译期而非运行时报错。同一文件还导出普通的narration.call("typo_label")数组/枚举(as const、characterIds、labelIds、bundleIds),用于运行时验证(例如assetAliasIds)。这个生成的文件不参与 HMR,因此重新生成它不会触发整页刷新。z.enum(characterIdsEnum) - 衔接 PixiJS assets manifest。 选项(一个值或 async 函数,通常解析为
assetsManifest中构建的src/assets/index.ts——参见manifest了解该文件的编写方式)会将 bundle/asset-alias id 注入同一个生成文件,并预置一个 dev-server endpoint(pixi-vn-assets),使 devtools 或其他工具无需等待客户端主动推送即可读取当前 manifest。GET /__pixi-vn/assets/manifest - 正确热重载 content。 编辑受监视的 label/character 文件时,会清除相应 registry,只重新加载这些文件并重新生成 keys file——无需整页刷新。
不要手动配置底层的 dev-server endpoint 或 /,除非正在构建一个与 集成的插件(例如 ink compiler)——对于应用而言,上述四个选项(、、、、)才是需要关注的完整接口。
GET/POST /__pixi-vn/*api.setExternalLabelsapi.setAssetsManifestvitePluginPixivncontentcharacterslabelstypeFilePathassetsManifest另一个相关但更小的导出 提供了 ——一个仅限客户端、开发环境使用的函数,将运行时 assets manifest 和当前 canvas 尺寸通过 POST 发送回同一组 dev-server endpoint。仅当 manifest 确实无法在 运行时确定时,才在 content 加载完成后调用一次()作为兜底;官方模板更推荐上述 函数选项,不需要此方法。
@drincs/pixi-vn/vite-listenersetupPixivnViteData()vite.config.tsawait setupPixivnViteData()assetsManifestReal-world project layout (official React template)
实际项目结构(官方 React 模板)
Everything below reflects the official 's convention (what generates for the "TS narration + React" option) — it is one working way to organize a project, not a rule the library enforces. Other official/community templates (ink narration, non-React UI, headless) are free to lay things out differently; only running first is actually required.
pixi-vn-react-templatenpm create pixi-vn@latestGame.init以下所有内容都反映官方 的约定(即 为“TS narration + React”选项生成的内容)——它是一种可行的项目组织方式,并非库强制规定的规则。其他官方/社区模板(ink narration、非 React UI、headless)可以自由采用不同布局;实际唯一的要求是 最先运行。
pixi-vn-react-templatenpm create pixi-vn@latestGame.initNamed constants instead of hardcoded strings
使用命名常量而不是硬编码字符串
The template defines layer/channel ids as constants in instead of inlining string literals at each call site:
src/constants.tsts
// src/constants.ts
export const CANVAS_UI_LAYER_NAME = "ui";
export const HTML_UI_LAYER_NAME = "ui";
export const HTML_CANVAS_LAYER_NAME = "canvas";
export const BGM_CHANNEL_NAME = "bgm";
export const SFX_CHANNEL_NAME = "sfx";This keeps the , , and calls below typo-proof and greppable.
canvas.layers.add(...)canvas.htmlLayers.add(...)sound.channels.add(...)模板在 中将 layer/channel id 定义为常量,而不是在每个调用点直接写入字符串字面量:
src/constants.tsts
// src/constants.ts
export const CANVAS_UI_LAYER_NAME = "ui";
export const HTML_UI_LAYER_NAME = "ui";
export const HTML_CANVAS_LAYER_NAME = "canvas";
export const BGM_CHANNEL_NAME = "bgm";
export const SFX_CHANNEL_NAME = "sfx";这样可以防止下方 、 和 调用出现拼写错误,也更便于 grep。
canvas.layers.add(...)canvas.htmlLayers.add(...)sound.channels.add(...)Full main.tsx
wiring
main.tsx完整的 main.tsx
连接
main.tsxtsx
Game.init(body, {
id: HTML_CANVAS_LAYER_NAME,
height: 1080,
width: 1920,
backgroundColor: "#303030",
resizeMode: "contain",
}).then(() => {
// A PixiJS-rendered layer for in-canvas UI elements
canvas.layers.add(CANVAS_UI_LAYER_NAME, new Container());
// One background music channel, one default channel for one-off sfx
sound.channels.add(BGM_CHANNEL_NAME, { background: true });
sound.channels.add(SFX_CHANNEL_NAME);
sound.defaultChannelAlias = SFX_CHANNEL_NAME;
// Mount a UI framework (React here) INSIDE the PixiJS canvas tree
const root = document.getElementById("root")!;
const htmlLayout = canvas.htmlLayers.add(HTML_UI_LAYER_NAME, root);
if (!htmlLayout) throw new Error("htmlLayout not found");
createRoot(htmlLayout).render(<App />);
});
Game.onEnd(async ({ navigate }) => {
Game.clear();
navigate({ to: "/" });
});
Game.addOnError(drawCanvasErrorHandler());
Game.addOnError((error, { toast, uiTransition }) => {
toast?.error(uiTransition?.("allert_error_occurred"));
console.error("Error occurred", error);
});
Game.onLoadingLabel((_stepId, { id }) => Assets.backgroundLoadBundle(id));Key points:
- adds a PixiJS container drawn directly in the canvas render tree;
canvas.layers.addinstead hands back a DOM node kept in sync with a canvas-tracked layer, and is where any HTML/React/Vue UI gets mounted. This is the concrete mechanism behind "Pixi'VN has no built-in UI, bring your own framework" (seecanvas.htmlLayers.add).pixi-vn-ui - Multiple error handlers can be stacked: the library's own (visualizes broken canvas elements) runs alongside a project-specific handler that shows a toast and logs to console.
drawCanvasErrorHandler() - fires as a label is about to run; the template uses it to kick off
Game.onLoadingLabel, pre-warming an asset bundle named after the label id so images referenced by that label are already cached when needed.Assets.backgroundLoadBundle(id)
tsx
Game.init(body, {
id: HTML_CANVAS_LAYER_NAME,
height: 1080,
width: 1920,
backgroundColor: "#303030",
resizeMode: "contain",
}).then(() => {
// A PixiJS-rendered layer for in-canvas UI elements
canvas.layers.add(CANVAS_UI_LAYER_NAME, new Container());
// One background music channel, one default channel for one-off sfx
sound.channels.add(BGM_CHANNEL_NAME, { background: true });
sound.channels.add(SFX_CHANNEL_NAME);
sound.defaultChannelAlias = SFX_CHANNEL_NAME;
// Mount a UI framework (React here) INSIDE the PixiJS canvas tree
const root = document.getElementById("root")!;
const htmlLayout = canvas.htmlLayers.add(HTML_UI_LAYER_NAME, root);
if (!htmlLayout) throw new Error("htmlLayout not found");
createRoot(htmlLayout).render(<App />);
});
Game.onEnd(async ({ navigate }) => {
Game.clear();
navigate({ to: "/" });
});
Game.addOnError(drawCanvasErrorHandler());
Game.addOnError((error, { toast, uiTransition }) => {
toast?.error(uiTransition?.("allert_error_occurred"));
console.error("Error occurred", error);
});
Game.onLoadingLabel((_stepId, { id }) => Assets.backgroundLoadBundle(id));要点:
- 会添加一个直接绘制在 canvas 渲染树中的 PixiJS container;相比之下,
canvas.layers.add会返回一个 DOM 节点,该节点会与由 canvas 跟踪的 layer 保持同步,HTML/React/Vue UI 就挂载在这里。这正是“Pixi'VN 没有内置 UI,请自带框架”的具体实现机制(参见canvas.htmlLayers.add)。pixi-vn-ui - 可以叠加多个 error handler:库自带的 (用于可视化损坏的 canvas 元素)会与项目自定义 handler 并行运行;后者显示 toast 并记录到 console。
drawCanvasErrorHandler() - 会在 label 即将运行前触发;模板利用它启动
Game.onLoadingLabel,预热以 label id 命名的 asset bundle,使该 label 引用的图片在需要时已经完成缓存。Assets.backgroundLoadBundle(id)
Auto-importing narrative content: content/index.ts
content/index.ts自动导入叙事内容:content/index.ts
content/index.tsInstead of hand-maintaining an import list for every label/character file (and remembering to update it each time one is added), the template eagerly imports everything under purely for side effects, using Vite's :
content/import.meta.globts
// src/content/index.ts
void import.meta.glob(["./**/*.ts", "./**/*.tsx", "!./index.ts"], {
eager: true,
});main.tsxApp.tsximport "@/content"CharacternewLabel(...)content/require.context模板使用 Vite 的 ,仅为副作用而提前导入 下的所有内容,而不是手动维护每个 label/character 文件的导入列表(也不必在每次新增文件后记得更新列表):
import.meta.globcontent/ts
// src/content/index.ts
void import.meta.glob(["./**/*.ts", "./**/*.tsx", "!./index.ts"], {
eager: true,
});随后 / 只需执行一次 。之所以可行,是因为定义 或调用 都会在模块执行时注册自身——glob 保证 下的每个文件都会在启动时运行,无需逐个导入。(webpack 等其他 bundler 需要自己的等效方案,例如 。)
main.tsxApp.tsximport "@/content"CharacternewLabel(...)content/require.contextAugmenting StepLabelProps
for custom per-step context
StepLabelProps扩展 StepLabelProps
以提供自定义的逐步上下文
StepLabelPropssrc/pixi-vn.d.tsnavigateStepLabelPropsts
// src/pixi-vn.d.ts
declare module "@drincs/pixi-vn" {
interface StepLabelProps {
navigate: UseNavigateResult<string>; // TanStack Router
t: TFunction<[string], undefined>; // i18next: narration text
uiTransition: TFunction<[string], undefined>; // i18next: UI chrome text
toast: typeof toast; // sonner's toast function
invalidateInterfaceData: (delay?: number) => Promise<void> | void;
}
}Whatever is added here becomes available on the argument of every narration step, , and alike — a single place to inject router/i18n/toast/anything-else a project's narration layer needs. vs splits narration-text translations from UI-chrome translations; is a template-specific hook that forces UI data (e.g. a React Query cache) to refetch after a step changes something the interface depends on.
propsGame.onEndGame.addOnErrortuiTransitioninvalidateInterfaceDatasrc/pixi-vn.d.tsStepLabelPropsts
// src/pixi-vn.d.ts
declare module "@drincs/pixi-vn" {
interface StepLabelProps {
navigate: UseNavigateResult<string>; // TanStack Router
t: TFunction<[string], undefined>; // i18next: narration text
uiTransition: TFunction<[string], undefined>; // i18next: UI chrome text
toast: typeof toast; // sonner's toast function
invalidateInterfaceData: (delay?: number) => Promise<void> | void;
}
}在这里添加的任何内容都会出现在每个 narration step、 和 的 参数中——只需一个位置,即可注入项目 narration 层所需的 router/i18n/toast 或其他内容。 与 用于区分 narration 文本翻译和 UI chrome 翻译; 是模板专属 hook,用于在某一步更改了界面所依赖的数据后,强制 UI 数据(例如 React Query cache)重新获取。
Game.onEndGame.addOnErrorpropstuiTransitioninvalidateInterfaceDataOptional external plugins
可选的外部插件
Install only the integrations the game uses; these are separate npm packages, not exports
of :
@drincs/pixi-vn- : runtime dialogue/image generation. Await its
@drincs/pixi-vn-aiduring startup; see narration's AI notes.ai.init(...) - : Live2D models. Register
@drincs/pixi-vn-live2dbefore initializing the canvas; see canvas plugin setup.Live2DPlugin - : Spine skeletal animation. Import the package in the app entry point so saved components can be restored even before lazy scenes load; see canvas plugin setup.
@drincs/pixi-vn-spine
Check each plugin's installed peer dependencies against the project's engine/PixiJS version.
仅安装游戏实际使用的集成;它们是独立的 npm 包,而不是 的导出:
@drincs/pixi-vn- :运行时 dialogue/image 生成。在启动期间
@drincs/pixi-vn-ai其await;参见 narration 的 AI 说明。ai.init(...) - :Live2D models。在初始化 canvas 前注册
@drincs/pixi-vn-live2d;参见 canvas 插件设置。Live2DPlugin - :Spine skeletal animation。在应用入口点导入该包,以便即使在 lazy scenes 加载之前也能恢复已保存的 components;参见 canvas 插件设置。
@drincs/pixi-vn-spine
检查每个插件所安装的 peer dependencies 是否与项目的 engine/PixiJS 版本匹配。
Related skills
相关 skills
- pixi-vn-assets — registering local/online assets, the manifest/bundle/alias system, and when to load them.
- pixi-vn-canvas — adding/animating images, sprites, text, video and containers on the PixiJS canvas after .
Game.init - pixi-vn-characters — defining and registering instances used in dialogue.
Character - pixi-vn-history — the step history system (), going back/forward through played steps, and save-checkpoint behavior.
stepHistory - pixi-vn-narration — labels, steps, dialogue, choices, and the object driving the story.
narration - pixi-vn-saves — /
Game.exportGameStateand the official save-slot/quick-save convention.restoreGameState - pixi-vn-sound — playing music and sound effects, channels, and volume control.
- pixi-vn-storage — reading/writing persistent and temporary game variables and flags.
- pixi-vn-ui — building/mounting UI layers (HTML or PixiJS) on top of the canvas, screen navigation, theming, and connecting UI to storage.
- pixi-vn-migration — upgrading an existing project to the current version.
- pixi-vn-assets——注册本地/在线 assets、manifest/bundle/alias system,以及加载 assets 的时机。
- pixi-vn-canvas——在 之后向 PixiJS canvas 添加/动画化 images、sprites、text、video 和 containers。
Game.init - pixi-vn-characters——定义并注册 dialogue 中使用的 instances。
Character - pixi-vn-history——step history system(),在已播放的 steps 之间前进/后退,以及 save-checkpoint 行为。
stepHistory - pixi-vn-narration——labels、steps、dialogue、choices,以及驱动故事的 object。
narration - pixi-vn-saves——/
Game.exportGameState和官方 save-slot/quick-save 约定。restoreGameState - pixi-vn-sound——播放音乐和音效、管理 channels 及控制音量。
- pixi-vn-storage——读取/写入持久和临时 game variables 与 flags。
- pixi-vn-ui——在 canvas 上构建/挂载 UI layers(HTML 或 PixiJS)、屏幕导航、主题,以及将 UI 连接到 storage。
- pixi-vn-migration——将现有项目升级到当前版本。