SuperAPI AI Directory

pixi-vn-ink-characters

Coding & development

View repository

Updated 11-06 16:21

About this skill

Characters in ink

Official docs: pixi-vn.com/ink/character. Assumes Pixi'VN characters are already defined via CharacterBaseModel

  • RegisteredCharacters.add(...) — this skill only covers referencing them from .ink.

When to use this skill

Use whenever an .ink line needs to be spoken by a registered character, needs that character's display name printed inline, needs an emotion-specific character variant, or needs to change a character's name from inside the story (a naming scene, a nickname reveal, ...).

Attributing a line to a character

mc: Hello, I'm Liam.

<character_id>: <text> — Pixi'VN splits on the first ": " (colon, then a space) in the line and checks whether the token before it is a known character id. A known id is either one explicitly passed to importInkText(text, { characters: [...] }) / vitePluginInk({ characters: [...] }), or (the common case) already present in RegisteredCharacters at conversion time. If the token doesn't match a known character, the whole line is left as plain narrator text — including the colon — so a typo'd character id silently becomes garbled narration instead of an error. Because the split requires the literal ": ", mc:Hello (no space) is not recognized as dialogue attribution.

import { CharacterBaseModel, RegisteredCharacters } from "@drincs/pixi-vn";

export const mc = new CharacterBaseModel("mc", { name: "Liam" });
RegisteredCharacters.add(mc);

If characters are registered after the ink importing/build step runs (a common ordering issue with the Vite plugin, since vitePluginInk's JSON export happens independently of when your content/ files execute), pass their ids explicitly via vitePluginInk({ characters: [...] }) or importInkText(text, { characters: [...] }) so the split still recognizes them.

Printing a character's name inline: [characterId]

Hello, [mc].

This is not native ink's {variable} interpolation — it's Pixi'VN's text-replacement mechanism ([key] in square brackets), applied here to substitute a character id with that character's current name. It needs one text-replace handler registered, using the built-in "characterId" validation mode — every official template ships this by default:

import { TextReplaces } from "@drincs/pixi-vn-ink";
import { RegisteredCharacters } from "@drincs/pixi-vn/characters";

TextReplaces.add((key) => RegisteredCharacters.get(key)?.name, {
  name: "character name",
  validation: "characterId",
  type: "after-translation",
  i18nInterpolation: true,
  description: "Replaces a character ID with the character's name in the game.",
});

validation: "characterId" means this handler only fires for keys that are currently a registered character id (checked against RegisteredCharacters) — no regex needed. Because the character's name can change at runtime (see rename below), [mc] always reflects the current name, not the one at story-write time.

Character emotions: id@emotion

mc@happy: Hi, I'm Liam. I'm very happy today.

<character_id>@<emotion>: <text> — the whole mc@happy token is checked against RegisteredCharacters exactly like a plain id above; it resolves because Pixi'VN characters can be registered under a composite { id, emotion } key (internally addressed the same way, id@emotion):

import { CharacterBaseModel, RegisteredCharacters } from "@drincs/pixi-vn";

export const mc = new CharacterBaseModel("mc", { name: "Liam" });
export const mcHappy = new CharacterBaseModel({ id: "mc", emotion: "happy" }, { name: "Liam happy" });

RegisteredCharacters.add([mc, mcHappy]);

An emotion variant is a full second CharacterBaseModel registration — typically with its own name override (here identical) plus whatever sprite/portrait data your project's rendering layer keys off of (see pixi-vn-ink-canvas for showing an emotion-specific sprite alongside the line). Referencing mc@sad without registering that variant behaves like any other unknown id: the line falls back to plain narrator text.

Renaming a character from ink: # rename

=== start ===
mc: Hello, I'm [mc].
# request input string
mc: My name is:
# rename mc {_input_value_}
mc: My name is [mc]
-> DONE

# rename <characterId> <newName> is not a built-in hashtag command — every official template registers it itself, because "which field(s) does renaming touch" is project-specific:

import { characterIdsEnum } from "@/pixi-vn.keys.gen"; // generated by vitePluginPixivn's typeFilePath
import { HashtagCommands } from "@drincs/pixi-vn-ink";
import { RegisteredCharacters } from "@drincs/pixi-vn/characters";
import zod from "zod";

HashtagCommands.add(
  async (script) => {
    const character = RegisteredCharacters.get(script[1]);
    if (character) character.name = script[2];
    return true;
  },
  {
    name: "character rename",
    description: "Renames a character in the game.\n\n```ink\n# rename <characterId> <newName>\n```",
    validation: zod.tuple([zod.literal("rename"), zod.enum(characterIdsEnum), zod.string()]),
  },
);

See pixi-vn-ink-hashtag-commands for the add vs addMapper choice and why this is registered as a plain add handler (it mutates game state directly rather than returning a PixiVNJsonOperation).

Related

  • pixi-vn-ink-text-replacement — the general [key] mechanism [characterId] is built on.
  • pixi-vn-ink-hashtag-commands — how # rename (and any other custom command) is registered.
  • pixi-vn-ink — knots-as-labels, storage mapping, installation.
  • pixi-vn.com/start/character — defining CharacterBaseModels in JS/TS in the first place.