pixi-vn-characters
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePixi'VN Characters
Pixi'VN 角色
When to use this skill
何时使用此 skill
Use this skill whenever a task involves creating the characters that appear in a
Pixi'VN game (name, surname, age, color, icon, and any custom fields), storing
them so the engine and narration code can find them by id, or hooking a
character into a line. It does not cover how dialogue/text
itself is displayed (see the narration skill) or how variables are generally
saved (see the storage skill).
narration.dialoguets
import { CharacterBaseModel, RegisteredCharacters } from "@drincs/pixi-vn";(Also available from the narrower subpath — see for when to prefer that.)
@drincs/pixi-vn/characterspixi-vn-getting-startedFull reference: https://pixi-vn.com/start/character
只要任务涉及创建 Pixi'VN 游戏中出现的角色(姓名、姓氏、年龄、颜色、图标和任何自定义字段)、将角色存储起来以便引擎和旁白代码可通过 id 找到它们,或将角色接入 行,就应使用此 skill。它不涉及对话/文本本身的显示方式(参见 narration skill),也不涉及变量通常如何保存(参见 storage skill)。
narration.dialoguets
import { CharacterBaseModel, RegisteredCharacters } from "@drincs/pixi-vn";(也可从范围更窄的 子路径导入——何时优先使用该路径,请参阅 。)
@drincs/pixi-vn/characterspixi-vn-getting-started1. Defining a character
1. 定义角色
CharacterBaseModelidnamesurnameageiconcolorts
import { CharacterBaseModel } from "@drincs/pixi-vn";
export const liam = new CharacterBaseModel("liam", {
name: "Liam",
surname: "Smith",
age: 25,
icon: "https://example.com/liam.png",
color: "#9e2e12",
});
export const emma = new CharacterBaseModel("emma", {
name: "Emma",
surname: "Johnson",
age: 23,
icon: "https://example.com/emma.png",
color: "#c23b7f",
});The is the value used everywhere else in the game (dialogue, lookups,
history) to reference this character, so it must be unique and stable across
saves.
idnamesurnameageCharacterStoredClassCharacterBaseModelliam.name = "..."iconcolorts
import { liam } from "@/content/characters";
console.log(liam.name); // "Liam"
liam.name = "Liam Smith";
console.log(liam.name); // "Liam Smith" — now persisted in game storageIf a character's is ever changed between game versions, the engine does
not migrate the storage data from the old id to the new one — treat ids as
permanent once shipped.
idCharacterBaseModelidnamesurnameageiconcolorts
import { CharacterBaseModel } from "@drincs/pixi-vn";
export const liam = new CharacterBaseModel("liam", {
name: "Liam",
surname: "Smith",
age: 25,
icon: "https://example.com/liam.png",
color: "#9e2e12",
});
export const emma = new CharacterBaseModel("emma", {
name: "Emma",
surname: "Johnson",
age: 23,
icon: "https://example.com/emma.png",
color: "#c23b7f",
});idnamesurnameageCharacterStoredClassCharacterBaseModelliam.name = "..."iconcolorts
import { liam } from "@/content/characters";
console.log(liam.name); // "Liam"
liam.name = "Liam Smith";
console.log(liam.name); // "Liam Smith" — now persisted in game storage如果角色的 在游戏版本之间发生更改,引擎不会将存储数据从旧 id 迁移到新 id——一旦发布,就应将 id 视为永久固定。
idCustom character classes with extra fields
包含额外字段的自定义角色类
If you need fields beyond //// (e.g. a
id or a relationship stat), this is the recommended, docs-endorsed
approach (project templates already ship it as ):
namesurnameageiconcolorspritemodels/Character.ts- Create a class .
Character extends CharacterStoredClass implements CharacterInterface - Augment the (otherwise empty) in a
CharacterInterfacefile viapixi-vn.d.tsso everydeclare module "@drincs/pixi-vn"consumer in the project — includingCharacterInterface— sees the richer, project-specific type.RegisteredCharacters.get() - For any field that must persist across saves, back it with a
getter/setter that calls /
this.getStorageProperty()(inherited fromthis.setStorageProperty()), the same patternCharacterStoredClassuses internally forCharacterBaseModel/name/surname.age
This is exactly what the official (the "TS narration
pixi-vn-react-template- React" project scaffolds) ships as
npm create pixi-vn@latest— stored getters/setters forsrc/models/Character.ts,nameandsurname, and plain (non-stored) fields forage/icon:color
ts
// models/Character.ts
import { type CharacterInterface, CharacterStoredClass } from "@drincs/pixi-vn";
export default class Character
extends CharacterStoredClass
implements CharacterInterface
{
constructor(
id: string | { id: string; emotion: string },
props: CharacterProps,
) {
super(
typeof id === "string" ? id : id.id,
typeof id === "string" ? "" : id.emotion,
);
this.defaultName = props.name;
this.defaultSurname = props.surname;
this.defaultAge = props.age;
this.icon = props.icon;
this.color = props.color;
}
// stored — persists across save/load, same pattern as CharacterBaseModel
private defaultName?: string;
get name(): string {
return (
this.getStorageProperty<string>("name") || this.defaultName || this.id
);
}
set name(value: string | undefined) {
this.setStorageProperty<string>("name", value);
}
private defaultSurname?: string;
get surname(): string | undefined {
return this.getStorageProperty<string>("surname") || this.defaultSurname;
}
set surname(value: string | undefined) {
this.setStorageProperty<string>("surname", value);
}
private defaultAge?: number;
get age(): number | undefined {
return this.getStorageProperty<number>("age") || this.defaultAge;
}
set age(value: number | undefined) {
this.setStorageProperty<number>("age", value);
}
// not stored
readonly icon?: string;
readonly color?: string;
}
interface CharacterProps {
name?: string;
surname?: string;
age?: number;
icon?: string;
color?: string;
}ts
// pixi-vn.d.ts
declare module "@drincs/pixi-vn" {
interface CharacterInterface {
name: string;
surname?: string;
age?: number;
readonly icon?: string;
readonly color?: string;
}
}如果需要 //// 之外的字段(例如 id 或关系值),建议采用官方文档认可的方法(项目模板已经以 的形式提供该方法):
namesurnameageiconcolorspritemodels/Character.ts- 创建一个类 。
Character extends CharacterStoredClass implements CharacterInterface - 在 文件中,通过
pixi-vn.d.ts扩充(原本为空的)declare module "@drincs/pixi-vn",使项目中每个CharacterInterface使用方——包括CharacterInterface——都能看到更丰富的、项目专属类型。RegisteredCharacters.get() - 对于必须在存档/加载后保留的任何字段,使用调用 /
this.getStorageProperty()的 getter/setter 作为其后端(这些方法继承自this.setStorageProperty()),与CharacterStoredClass内部为CharacterBaseModel/name/surname使用的模式相同。age
官方 (即 脚手架生成的“TS narration + React”项目)正是以 的形式提供这一实现——、 和 使用可存储的 getter/setter,而 / 使用普通(非存储)字段:
pixi-vn-react-templatenpm create pixi-vn@latestsrc/models/Character.tsnamesurnameageiconcolorts
// models/Character.ts
import { type CharacterInterface, CharacterStoredClass } from "@drincs/pixi-vn";
export default class Character
extends CharacterStoredClass
implements CharacterInterface
{
constructor(
id: string | { id: string; emotion: string },
props: CharacterProps,
) {
super(
typeof id === "string" ? id : id.id,
typeof id === "string" ? "" : id.emotion,
);
this.defaultName = props.name;
this.defaultSurname = props.surname;
this.defaultAge = props.age;
this.icon = props.icon;
this.color = props.color;
}
// stored — persists across save/load, same pattern as CharacterBaseModel
private defaultName?: string;
get name(): string {
return (
this.getStorageProperty<string>("name") || this.defaultName || this.id
);
}
set name(value: string | undefined) {
this.setStorageProperty<string>("name", value);
}
private defaultSurname?: string;
get surname(): string | undefined {
return this.getStorageProperty<string>("surname") || this.defaultSurname;
}
set surname(value: string | undefined) {
this.setStorageProperty<string>("surname", value);
}
private defaultAge?: number;
get age(): number | undefined {
return this.getStorageProperty<number>("age") || this.defaultAge;
}
set age(value: number | undefined) {
this.setStorageProperty<number>("age", value);
}
// not stored
readonly icon?: string;
readonly color?: string;
}
interface CharacterProps {
name?: string;
surname?: string;
age?: number;
icon?: string;
color?: string;
}ts
// pixi-vn.d.ts
declare module "@drincs/pixi-vn" {
interface CharacterInterface {
name: string;
surname?: string;
age?: number;
readonly icon?: string;
readonly color?: string;
}
}2. Registering and looking up characters
2. 注册和查找角色
Characters are looked up at runtime by id through the
registry (a namespace, not a decorator despite its file name). Calling
is required — a (or
custom class) instance that is only exported/constructed but never passed to
will not be findable by id and will not work in dialogue lookups after a
save/load. Register every character you define once, at module load time
(recommended: import them all at project startup), then reference it by id
elsewhere:
RegisteredCharactersRegisteredCharacters.add(...)CharacterBaseModeladdts
import { RegisteredCharacters } from "@drincs/pixi-vn";
RegisteredCharacters.add(liam, emma);
// or with arrays:
RegisteredCharacters.add([liam, emma]);Other functions:
RegisteredCharacters- — returns the character (or
RegisteredCharacters.get(id), logging a warning if it was never registered).undefined - — checks whether an id is registered.
RegisteredCharacters.has(id) - — returns all registered characters.
RegisteredCharacters.values() - — returns all registered ids.
RegisteredCharacters.keys() - — removes all registered characters (mainly useful in tests).
RegisteredCharacters.clear()
ts
const liam = RegisteredCharacters.get("liam");Re-adding an id that's already registered overwrites the previous character
(a log message notes this), so registration order matters if two files define
the same id.
运行时通过 注册表按 id 查找角色(尽管其文件名如此,它是一个 namespace,而不是 decorator)。调用 是必需的——仅导出或构造、但从未传给 的 (或自定义类)实例无法按 id 找到,并且在存档/加载后无法用于对话查找。将每个定义的角色在模块加载时注册一次(建议在项目启动时导入所有角色),然后在其他位置通过 id 引用它:
RegisteredCharactersRegisteredCharacters.add(...)addCharacterBaseModelts
import { RegisteredCharacters } from "@drincs/pixi-vn";
RegisteredCharacters.add(liam, emma);
// or with arrays:
RegisteredCharacters.add([liam, emma]);RegisteredCharacters- ——返回角色(如果从未注册,则返回
RegisteredCharacters.get(id)并记录警告)。undefined - ——检查某个 id 是否已注册。
RegisteredCharacters.has(id) - ——返回所有已注册的角色。
RegisteredCharacters.values() - ——返回所有已注册的 id。
RegisteredCharacters.keys() - ——移除所有已注册的角色(主要用于测试)。
RegisteredCharacters.clear()
ts
const liam = RegisteredCharacters.get("liam");重新注册已存在的 id 会覆盖之前的角色(系统会记录一条日志消息),因此如果两个文件定义了相同的 id,注册顺序就会产生影响。
Real-world project convention (official React template)
实际项目约定(官方 React 模板)
The official (what
scaffolds for the "TS narration + React" setup) defines its characters as
instances in , then registers all of
them with a single call at module scope:
pixi-vn-react-templatenpm create pixi-vn@latestexport constsrc/content/characters.tsRegisteredCharacters.add([...])ts
// src/content/characters.ts
import Character from "@/models/Character";
import { RegisteredCharacters } from "@drincs/pixi-vn";
export const mc = new Character("mc", { name: "Me" });
export const james = new Character("james", {
name: "James",
color: "#0084ac",
});
export const steph = new Character("steph", {
name: "Steph",
color: "#ac5900",
});
export const sly = new Character("sly", { name: "Sly", color: "#6d00ac" });
RegisteredCharacters.add([mc, james, steph, sly]);This works with no manual import list to maintain because
uses to import every file under for its side
effects at app startup — so the module-scope
call above always runs. That means you never need to remember to import each
new character file yourself (dropping it anywhere under is
enough), but conversely, a character defined (and ed) in a file
outside that nothing else imports never gets its call
executed, and stays silently unregistered.
src/content/index.tsimport.meta.globcontent/RegisteredCharacters.add(...)content/.addcontent/addThis is the official template's convention, not a hard requirement of the
library — can be called from any file, as long as
it's guaranteed to run before the character is looked up. Projects without
this glob-import setup must import each character file explicitly instead
(e.g. from a top-level or the app's entry point).
RegisteredCharacters.addcontent/index.ts官方 ( 为“TS narration + React”设置生成的模板)将角色定义为 中的 实例,然后在模块作用域通过单次 调用注册所有角色:
pixi-vn-react-templatenpm create pixi-vn@latestsrc/content/characters.tsexport constRegisteredCharacters.add([...])ts
// src/content/characters.ts
import Character from "@/models/Character";
import { RegisteredCharacters } from "@drincs/pixi-vn";
export const mc = new Character("mc", { name: "Me" });
export const james = new Character("james", {
name: "James",
color: "#0084ac",
});
export const steph = new Character("steph", {
name: "Steph",
color: "#ac5900",
});
export const sly = new Character("sly", { name: "Sly", color: "#6d00ac" });
RegisteredCharacters.add([mc, james, steph, sly]);这样无需手动维护 import 列表,因为 使用 导入 下的每个文件,以便在应用启动时执行其副作用——因此,上述模块作用域中的 调用总会执行。这意味着你永远不必记住自行导入每个新角色文件(将其放在 下的任意位置即可);但反过来,在 之外 定义(并 )且没有其他任何内容导入的文件,其 调用永远不会执行,角色会悄无声息地保持未注册状态。
src/content/index.tsimport.meta.globcontent/RegisteredCharacters.add(...)content/content/.addadd这是官方模板的约定,而不是库的硬性要求——可以从任何文件调用 ,只要能保证它在角色被查找之前执行即可。不具备这种 glob 导入设置的项目,必须改为显式导入每个角色文件(例如从顶层 或应用入口点导入)。
RegisteredCharacters.addcontent/index.tsTyped ids (optional)
类型化 id(可选)
Character ids are typed as , which is by default.
If a project augments the empty interface (typically
auto-generated by the Vite plugin's option),
narrows to a union of known ids and typos become compile
errors. Don't assume this augmentation exists unless you see a generated
/similar file in the project — if not, treat ids as plain
strings.
CharacterIdTypestringPixivnCharacterIdsvitePluginPixivntypeFilePathCharacterIdTypepixi-vn.gen.d.ts角色 id 的类型为 ,默认是 。如果项目扩充了空的 interface(通常由 Vite 插件的 选项自动生成), 就会收窄为已知 id 的联合类型,拼写错误将变成编译错误。除非在项目中看到生成的 或类似文件,否则不要假定存在这种扩充;如果没有,则将 id 视为普通字符串。
CharacterIdTypestringPixivnCharacterIdsvitePluginPixivntypeFilePathCharacterIdTypepixi-vn.gen.d.ts3. Emotions
3. Emotion
A character id may optionally be paired with an emotion by passing an object
instead of a plain string as the constructor's first argument:
ts
export const alice = new CharacterBaseModel("alice", {
name: "Alice",
icon: "https://example.com/alice.png",
color: "#9e2e12",
});
export const angryAlice = new CharacterBaseModel(
{ id: "alice", emotion: "angry" },
{ icon: "https://example.com/angryAlice.png" },
);
RegisteredCharacters.add([alice, angryAlice]);Internally this stores the character's // under a
separate storage key () while still falling back to the
base id's stored/default values if an emotion-specific value isn't set — the
base id and its emotion variants share the same underlying character data
except where the emotion variant overrides it:
namesurnameage"<id>@<emotion>"ts
console.log(alice.name); // "Alice"
alice.name = "Eleonora";
console.log(alice.name); // "Eleonora"
console.log(angryAlice.name); // "Eleonora" — falls back to the base id's value
angryAlice.name = "Angry Eleonora";
console.log(alice.name); // "Eleonora" — unaffected
console.log(angryAlice.name); // "Angry Eleonora" — its own override now setUse this only if you need per-emotion overrides of a character's stored
fields; for swapping the character's on-screen sprite/image per emotion,
that's driven by the canvas/image APIs, not this module.
角色 id 可以选择通过在构造函数第一个参数中传入对象而非普通字符串来与 emotion 配对:
ts
export const alice = new CharacterBaseModel("alice", {
name: "Alice",
icon: "https://example.com/alice.png",
color: "#9e2e12",
});
export const angryAlice = new CharacterBaseModel(
{ id: "alice", emotion: "angry" },
{ icon: "https://example.com/angryAlice.png" },
);
RegisteredCharacters.add([alice, angryAlice]);在内部,这会将角色的 // 存储在单独的存储键()下,同时仍会回退到基础 id 的已存储值或默认值(如果未设置 emotion 专属值)——基础 id 和它的 emotion 变体共享相同的底层角色数据,只有 emotion 变体明确覆盖的内容会有所不同:
namesurnameage"<id>@<emotion>"ts
console.log(alice.name); // "Alice"
alice.name = "Eleonora";
console.log(alice.name); // "Eleonora"
console.log(angryAlice.name); // "Eleonora" — falls back to the base id's value
angryAlice.name = "Angry Eleonora";
console.log(alice.name); // "Eleonora" — unaffected
console.log(angryAlice.name); // "Angry Eleonora" — its own override now set仅当需要针对每种 emotion 覆盖角色存储字段时使用此功能;对于按 emotion 替换角色屏幕上的 sprite/图像,应使用 canvas/image API,而不是本模块。
4. Using a character in dialogue
4. 在对话中使用角色
Once registered, pass the character instance (or its id string) as the
field of a dialogue, e.g. inside a label step:
characterts
narration.dialogue = {
character: liam,
text: "Which test do you want to perform?",
};
// or by id:
narration.dialogue = { character: "liam", text: "..." };Which form to prefer depends on whether the project's plugin generates
typed character ids (the option covered in , which narrows
from plain to a union of the actually-registered ids):
@drincs/pixi-vn/vitetypeFilePathpixi-vn-getting-startedCharacterIdTypestring- Without it, prefer passing the instance () — a bare string id has no compile-time typo protection, while the instance is a real variable reference the compiler already checks.
character: liam - With it, prefer the string-id form () — it now gets the same compile-time safety (a typo like
character: "liam"fails to typecheck against the generated"liem"union) without needing to import the character module wherever a dialogue line is written, which keeps label files decoupled from character-definition files.CharacterIdType
DialogueInterface.characterCharacterInterface | stringRegisteredCharacters.get注册后,将角色实例(或其 id 字符串)作为 dialogue 的 字段传入,例如在 label 步骤中:
characterts
narration.dialogue = {
character: liam,
text: "Which test do you want to perform?",
};
// or by id:
narration.dialogue = { character: "liam", text: "..." };使用哪种形式取决于项目的 插件是否生成类型化角色 id( 选项在 中有介绍,它会将 从普通 收窄为实际已注册 id 的联合类型):
@drincs/pixi-vn/vitetypeFilePathpixi-vn-getting-startedCharacterIdTypestring- 未启用时,优先传入实例()——普通字符串 id 没有编译期拼写错误保护,而实例是编译器已经检查过的真实变量引用。
character: liam - 启用时,优先使用字符串 id 形式()——它现在具备相同的编译期安全性(例如
character: "liam"这样的拼写错误无法通过生成的"liem"联合类型检查),并且无需在编写每条 dialogue 的地方都导入角色模块,从而让 label 文件与角色定义文件保持解耦。CharacterIdType
DialogueInterface.characterCharacterInterface | stringRegisteredCharacters.getRelated skills
相关 skill
pixi-vn-getting-started, pixi-vn-narration, pixi-vn-storage
pixi-vn-getting-started, pixi-vn-narration, pixi-vn-storage