pixi-vn-canvas

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Pixi'VN Canvas

Pixi'VN Canvas

The Canvas module is Pixi'VN's 2D scene graph, built directly on top of PixiJS. It manages everything that is visually rendered in the game: background images, character sprites, text, video, and the transitions/animations between them. Official docs: pixi-vn.com/start/canvas.
Every symbol used below (
canvas
,
showImage
,
Sprite
, transition helpers, etc.) is exported from the main package entry point:
ts
import { canvas } from "@drincs/pixi-vn";
(Also available from the narrower
@drincs/pixi-vn/canvas
subpath — see
pixi-vn-getting-started
for when to prefer that.)
Canvas 模块是 Pixi'VN 的 2D 场景图,直接构建于 PixiJS 之上。它负责管理游戏中所有以视觉方式呈现的内容:背景图像、角色精灵、文本、视频,以及它们之间的转场/动画。官方文档: pixi-vn.com/start/canvas。
以下使用的所有符号(
canvas
、
showImage
、
Sprite
、转场辅助函数等)均从主包入口导出:
ts
import { canvas } from "@drincs/pixi-vn";
也可以从范围更窄的
@drincs/pixi-vn/canvas
子路径导入——何时优先使用它,请参阅
pixi-vn-getting-started
。

When to use this skill

何时使用此 skill

Load this skill whenever a task involves:
  • Showing, replacing, or removing an image/sprite/video/text on screen.
  • Building a composite character sprite out of several image layers.
  • Applying a transition (dissolve, fade, move, zoom, push, wipe, iris, split, flash, blur, pixelate) when a background or sprite changes.
  • Adding a shake effect or a custom PixiJS-ticker-driven animation.
  • Reading or modifying canvas element position/anchor/alpha/zIndex.
For building/mounting UI layers (HTML or PixiJS) on top of the canvas, use
pixi-vn-ui
instead — this skill is about the
gameLayer
save-able scene graph, not UI chrome.
For dialogue/character-portrait logic use
pixi-vn-characters
and
pixi-vn-narration
instead; this skill is about the underlying rendering primitives.
只要任务涉及以下内容,就应加载此 skill:
  • 在屏幕上显示、替换或移除图像、精灵、视频或文本。
  • 使用多个图像层构建复合角色精灵。
  • 在背景或精灵发生变化时应用转场(溶解、淡入淡出、移动、缩放、推移、擦除、光圈、分割、闪烁、模糊、像素化)。
  • 添加抖动效果或由 PixiJS ticker 驱动的自定义动画。
  • 读取或修改 canvas 元素的位置、锚点、透明度或 zIndex。
如需在 Canvas 上构建或挂载 UI 层(HTML 或 PixiJS),请使用
pixi-vn-ui
——此 skill 关注的是可保存的
gameLayer
场景图,而不是 UI 框架。
如需处理对话或角色立绘逻辑,请使用
pixi-vn-characters
和
pixi-vn-narration
;此 skill 关注的是底层渲染原语。

Core mental model

核心心智模型

  • canvas
    (from
    @drincs/pixi-vn
    ) is the single entry-point object developers use. It wraps a PixiJS
    Application
    and exposes methods to manage canvas elements and layers. Call
    await canvas.init(element, options)
    once at startup (this is normally done for you by
    Game.init(...)
    from the core package — see
    pixi-vn-getting-started
    ).
  • Elements are tracked by alias, not by variable (docs).
    canvas.add(alias, component)
    inserts a component (an instance of
    Container
    ,
    Sprite
    ,
    ImageSprite
    ,
    ImageContainer
    ,
    Text
    , or
    VideoSprite
    ) into
    canvas.layers.gameLayer
    (a single PixiJS
    Container
    that holds all "in-scene" elements) and sets
    component.label = alias
    . Use
    canvas.find<T>(alias)
    to look elements back up and
    canvas.remove(alias)
    to remove them. Aliases are also how Pixi'VN saves/restores canvas state (see
    pixi-vn-storage
    ), so prefer the alias-based helpers below over holding onto raw object references across steps.
  • Every element class is a save-able extension of a PixiJS class:
    Container
    extends
    PIXI.Container
    ,
    Sprite
    /
    ImageSprite
    /
    VideoSprite
    extend
    PIXI.Sprite
    ,
    Text
    extends
    PIXI.Text
    . They add a
    .memory
    getter /
    .setMemory()
    for serialization, plus convenience properties (
    anchor
    ,
    align
    ,
    percentagePosition
    ,
    xAlign
    /
    yAlign
    ,
    percentageX
    /
    percentageY
    ) as alternative ways to position an element without doing pixel math.
  • Layer
    is just a type alias for a plain
    PIXI.Container<ContainerChild>
    — there's no special Layer class. A separate, non-save-able PixiJS Container can be attached directly to the stage as a sibling of
    gameLayer
    (
    canvas.layers.add
    /
    get
    /
    remove
    ) — see
    pixi-vn-ui
    for that API, it's how UI layers (HTML or PixiJS) are built on top of the canvas.
  • Tickers are how frame-based animation and effects work under the hood;
    canvas.animate(...)
    (built on the
    motion
    library) is the high-level way to animate numeric properties over time, and it is what all the built-in transition helpers use internally. Running tickers are managed through the separate
    tickers
    singleton (also from
    @drincs/pixi-vn
    , not a member of
    canvas
    — the old
    canvas.tickers
    is a deprecated alias of it).
  • canvas
    (来自
    @drincs/pixi-vn
    )是开发者使用的单一入口对象。它包装了一个 PixiJS
    Application
    ,并提供用于管理 canvas 元素和图层的方法。在启动时调用一次
    await canvas.init(element, options)
    (通常由核心包中的
    Game.init(...)
    自动完成——请参阅
    pixi-vn-getting-started
    )。
  • 元素通过 alias 跟踪,而不是通过变量跟踪(文档)。
    canvas.add(alias, component)
    会将组件(
    Container
    、
    Sprite
    、
    ImageSprite
    、
    ImageContainer
    、
    Text
    或
    VideoSprite
    的实例)插入
    canvas.layers.gameLayer
    (一个包含所有场景内元素的 PixiJS
    Container
    ),并将
    component.label
    设置为 alias。使用
    canvas.find<T>(alias)
    查找元素,使用
    canvas.remove(alias)
    移除元素。Pixi'VN 同样通过 alias 保存和恢复 canvas 状态(请参阅
    pixi-vn-storage
    ),因此在多个步骤之间应优先使用以下基于 alias 的辅助方法,而不是持有原始对象引用。
  • 每个元素类都是某个 PixiJS 类的可保存扩展版本:
    Container
    扩展
    PIXI.Container
    ,
    Sprite
    /
    ImageSprite
    /
    VideoSprite
    扩展
    PIXI.Sprite
    ,
    Text
    扩展
    PIXI.Text
    。它们新增了用于序列化的
    .memory
    getter/
    .setMemory()
    ,以及一些便捷属性(
    anchor
    、
    align
    、
    percentagePosition
    、
    xAlign
    /
    yAlign
    、
    percentageX
    /
    percentageY
    ),可通过这些属性定位元素,而无须进行像素计算。
  • Layer
    只是普通
    PIXI.Container<ContainerChild>
    的类型别名——不存在特殊的 Layer 类。可以将一个独立的、不可保存的 PixiJS Container 作为
    gameLayer
    的同级节点直接挂载到 stage(
    canvas.layers.add
    /
    get
    /
    remove
    )——相关 API 请参阅
    pixi-vn-ui
    ,它正是用于在 Canvas 上构建 UI 层的方式。
  • Ticker 是逐帧动画和效果在底层的工作机制;
    canvas.animate(...)
    (基于
    motion
    库构建)是对数值属性进行随时间动画处理的高级方式,所有内置转场辅助函数在内部都使用它。运行中的 ticker 由独立的
    tickers
    单例管理(同样来自
    @drincs/pixi-vn
    ,并不是
    canvas
    的成员——旧的
    canvas.tickers
    是它的弃用别名)。

Showing and removing an image

显示和移除图像

ts
import { canvas, addImage, showImage } from "@drincs/pixi-vn";

// addImage() creates + registers the sprite but does NOT load/display the texture yet
const bunny1 = addImage("bunny1"); // "bunny1" is an alias registered in the assets manifest
await bunny1.load();

// showImage() is addImage() + load() in one call
const bunny2 = await showImage("bunny2");
bunny2.anchor = 0.5;
bunny2.x = canvas.width / 2;
bunny2.y = canvas.height / 2;

// remove it later by alias
canvas.remove("bunny1");
Always pass a manifest alias (as above), not a raw URL/path — see
pixi-vn-assets
for how assets get registered (local vs. online) and loaded.
addImage
/
showImage
do also accept a raw URL as a second argument for quick prototyping, but that couples code to a specific file location;
addImage
/
showImage
throw a
PixiError
if neither a URL nor a registered alias resolves. Both return an
ImageSprite
.
For a composite image made of multiple stacked textures (e.g. a character body + outfit + face), use
ImageContainer
via
addImageCointainer
/
showImageContainer
(note the exported spelling "Cointainer"):
ts
import { showImageContainer } from "@drincs/pixi-vn";

const liam = await showImageContainer("liam", ["liam-body", "liam-head"]);
ts
import { canvas, addImage, showImage } from "@drincs/pixi-vn";

// addImage() creates + registers the sprite but does NOT load/display the texture yet
const bunny1 = addImage("bunny1"); // "bunny1" is an alias registered in the assets manifest
await bunny1.load();

// showImage() is addImage() + load() in one call
const bunny2 = await showImage("bunny2");
bunny2.anchor = 0.5;
bunny2.x = canvas.width / 2;
bunny2.y = canvas.height / 2;

// remove it later by alias
canvas.remove("bunny1");
始终传入资源清单中的 alias(如上例),而不是原始 URL/路径——有关如何注册资源(本地或在线)并加载资源,请参阅
pixi-vn-assets
。为了快速原型开发,
addImage
/
showImage
也接受作为第二个参数传入的原始 URL,但这会将代码耦合到特定文件位置;如果 URL 和已注册的 alias 均无法解析,
addImage
/
showImage
会抛出
PixiError
。两者都会返回
ImageSprite
。
如需创建由多个堆叠纹理组成的复合图像(例如角色身体、服装和脸部),请使用
ImageContainer
,并通过
addImageCointainer
/
showImageContainer
调用(请注意导出名称的拼写为
Cointainer
):
ts
import { showImageContainer } from "@drincs/pixi-vn";

const liam = await showImageContainer("liam", ["liam-body", "liam-head"]);

Adding text

添加文本

ts
import { showText } from "@drincs/pixi-vn";

showText("score-label", "Score: 0", {
  fontSize: 24,
  fill: "white",
  x: 20,
  y: 20,
});
showText(alias, text, options)
creates/replaces a
Text
element (extends
PIXI.Text
) and adds it to the canvas immediately.
options
is
TextOptions
(PixiJS
CanvasTextOptions
plus the
align
/
percentagePosition
positioning extensions), so
style
,
fontSize
,
fill
,
x
/
y
, etc. all work as in plain PixiJS.
ts
import { showText } from "@drincs/pixi-vn";

showText("score-label", "Score: 0", {
  fontSize: 24,
  fill: "white",
  x: 20,
  y: 20,
});
showText(alias, text, options)
创建一个
Text
元素(扩展
PIXI.Text
)并立即将其添加到 canvas,同时替换原有元素。
options
的类型为
TextOptions
(PixiJS
CanvasTextOptions
加上
align
/
percentagePosition
定位扩展),因此
style
、
fontSize
、
fill
、
x
/
y
等属性均可像在原生 PixiJS 中一样使用。

Playing a video

播放视频

ts
import { showVideo } from "@drincs/pixi-vn";

const film = await showVideo("intro-film"); // "intro-film" resolves via the assets manifest
film.loop = true;
// film.pause(), film.play(), film.restart(), film.currentTime = 2
addVideo
/
showVideo
mirror
addImage
/
showImage
and return a
VideoSprite
(extends
ImageSprite
). Files are also recognized as videos by extension automatically anywhere a transition helper accepts an image URL (
.mp4
,
.webm
,
.mov
, etc. — see
checkIfVideo
).
ts
import { showVideo } from "@drincs/pixi-vn";

const film = await showVideo("intro-film"); // "intro-film" resolves via the assets manifest
film.loop = true;
// film.pause(), film.play(), film.restart(), film.currentTime = 2
addVideo
/
showVideo
与
addImage
/
showImage
的行为一致,并返回
VideoSprite
(扩展
ImageSprite
)。在转场辅助函数接受图像 URL 的所有位置,系统也会根据扩展名自动将文件识别为视频(
.mp4
、
.webm
、
.mov
等——请参阅
checkIfVideo
)。

Transitions

转场

All transition helpers take
(alias, componentOrUrl?, props?, priority?)
. If you omit the component/URL argument, the
alias
itself is used as the texture URL/alias. Each function replaces (or removes) whatever is currently registered under
alias
, transferring position and running tickers from the old element automatically. They all return a
Promise
(or array) of ticker ids you can pass to
tickers.forceCompletion
if you need to await completion, but usually you just call and move on.
ts
import {
  showWithDissolve,
  removeWithDissolve, // fade the new image in / fade the current one out
  showWithFade,
  removeWithFade, // cross-fade old -> new (falls back to dissolve if none exists)
  moveIn,
  moveOut, // slide in/out from a screen edge
  zoomIn,
  zoomOut, // scale in/out from a screen edge
  pushIn,
  pushOut, // new image pushes the old one off-screen
} from "@drincs/pixi-vn";

// change background with a 1s dissolve ("bg-forest" is a manifest alias)
await showWithDissolve("background", "bg-forest", { duration: 1 });

// slide a character sprite in from the left
await moveIn("liam", "liam-neutral", { direction: "left", duration: 0.5 });

// remove a character with a zoom-out toward the right edge
zoomOut("liam", { direction: "right", duration: 0.5 });
props
extends
AnimationOptions
from
motion
(e.g.
duration
,
ease
) plus a
completeOnContinue
flag (default
true
) that finishes the transition immediately when the player advances the narration before the animation ends — leave this at its default unless you specifically want an animation to be interruptible/ignored.
所有转场辅助函数都采用
(alias, componentOrUrl?, props?, priority?)
签名。如果省略组件/URL 参数,则会使用
alias
本身作为纹理 URL/alias。每个函数都会替换或移除当前注册在对应
alias
下的元素,并自动转移其位置和正在运行的 ticker。它们会返回一个 Promise(或 Promise 数组),其中包含可供传递给
tickers.forceCompletion
的 ticker id;如果需要等待动画完成,可以使用该结果,但通常只需调用后继续执行即可。
ts
import {
  showWithDissolve,
  removeWithDissolve, // fade the new image in / fade the current one out
  showWithFade,
  removeWithFade, // cross-fade old -> new (falls back to dissolve if none exists)
  moveIn,
  moveOut, // slide in/out from a screen edge
  zoomIn,
  zoomOut, // scale in/out from a screen edge
  pushIn,
  pushOut, // new image pushes the old one off-screen
} from "@drincs/pixi-vn";

// change background with a 1s dissolve ("bg-forest" is a manifest alias)
await showWithDissolve("background", "bg-forest", { duration: 1 });

// slide a character sprite in from the left
await moveIn("liam", "liam-neutral", { direction: "left", duration: 0.5 });

// remove a character with a zoom-out toward the right edge
zoomOut("liam", { direction: "right", duration: 0.5 });
props
扩展了
motion
中的
AnimationOptions
(例如
duration
、
ease
),并增加了
completeOnContinue
标志(默认值为
true
);当玩家在动画结束前推进旁白时,它会立即完成转场——除非你明确希望动画可被中断或忽略,否则请保留此默认值。

Generic reveal/filter transitions: wipe, iris, split, flash, blur, pixelate, glitch, twist, warp, ripple, noiseDissolve, tv, pinch

通用显示/滤镜转场:wipe、iris、split、flash、blur、pixelate、glitch、twist、warp、ripple、noiseDissolve、tv、pinch

These are only exposed through the
transitions
namespace (no flat top-level export like the older
moveIn
/
showWithDissolve
), each as a matched
xIn
/
xOut
pair with the same
(alias, componentOrUrl?, props?, priority?)
/
(alias, props?, priority?)
shapes as above. They favor a handful of configurable options over narrative-specific variants — e.g. there's one
wipeIn
, not
wipeLeft
/
wipeRight
.
ts
import { transitions } from "@drincs/pixi-vn";

// wipe: a moving boundary reveals/conceals the image. `angle` in degrees (0 = left-to-right, 90 =
// bottom-to-top, ...); `direction` ("up"/"down"/"left"/"right") is a shorthand for the 4 cardinal angles.
await transitions.wipeIn("background", "bg-forest", { direction: "left", duration: 1 });
await transitions.wipeIn("background", "bg-night", { angle: 45, duration: 1 }); // diagonal wipe
transitions.wipeOut("background", { angle: 180, invert: true, duration: 1 });

// iris: an expanding/contracting radial mask. `origin` is normalized (0-1) to the element's own bounds.
await transitions.irisIn("liam", "liam-happy", { origin: { x: 0.5, y: 0.3 }, duration: 0.8 });
transitions.irisOut("liam", { aspect: 2, duration: 0.8 }); // aspect > 1 = wide ellipse instead of a circle

// split: two mask panels move apart/together - covers "curtain" effects without a dedicated API.
await transitions.splitIn("background", "bg-forest", { orientation: "horizontal", duration: 1 });
transitions.splitOut("background", { orientation: "vertical", origin: 0.3, duration: 1 });

// flash: a configurable color overlay (not just white) fades in/hold/out, optionally pulsing.
await transitions.flashIn("background", "bg-forest", { duration: 0.15 }); // white camera-flash reveal
transitions.flashOut("liam", { color: 0xff0033, holdDuration: 0.05, pulses: 3, duration: 0.1 }); // red damage flash

// blur / pixelate: the image is shown already blurred/pixelated and resolves into focus (or the reverse for *Out).
await transitions.blurIn("liam", "liam-happy", { strength: 40, duration: 1 });
transitions.blurOut("liam", { duration: 1 });
await transitions.pixelateIn("background", "bg-forest", { pixelSize: 48, duration: 1 });
transitions.pixelateOut("background", { duration: 1 });

// glitch: jittery slices + red/blue split, settling on *In, building up on *Out.
await transitions.glitchIn("liam", "liam-happy", { strength: 40, bursts: 3 });
// twist: unwinds out of (or winds into) a swirl - `angle` in degrees, `origin` normalized.
transitions.twistOut("liam", { angle: 540 });
// warp: radial zoom-blur streaks, like dropping out of / jumping into hyperspace.
await transitions.warpIn("background", "bg-space", { strength: 0.6 });
// ripple: fades in/out through a spreading water ring - dreams, magic, memories.
await transitions.rippleIn("liam", "liam-dream", { origin: { x: 0.5, y: 0.3 } });
// noiseDissolve: the classic VN image dissolve - organic noise blotches (`edge: "soft"` for a cloudy fade).
await transitions.noiseDissolveIn("background", "bg-night", { edge: "hard", noiseScale: 8 });
// tv: old TV switching on (dot -> glowing line -> image) / off; scales around the anchor (center it).
transitions.tvOut("liam", { lineThickness: 0.02, brightness: 3 });
// pinch: emerges from / is sucked into a point (`mode: "bulge"` puffs out instead).
transitions.pinchOut("liam", { mode: "pinch" });

// move/push also take an optional motion-blur trail (true = 40px, or a length in pixels).
await transitions.moveIn("liam", "liam-happy", { direction: "right", duration: 0.6, motionBlur: true });
Distortion transitions (glitch, twist, warp, ripple, pinch) move pixels around, so they read best on images with detail - on a flat, single-color element they're much less visible.
They compose freely since each drives its own mask (
wipe
/
iris
/
split
) or filter (
blur
/
pixelate
/
glitch
/...) independently — e.g. call
blurIn
and then
wipeIn
on the same alias to combine both. Common narrative effects are just recipes built from these primitives rather than dedicated functions: a "blink"/eyes-opening effect is a color overlay plus an iris reveal, a "dream"/flashback is
blurIn
+
showWithFade
, a memory transition is a color overlay plus
blurIn
, a "curtain" is a configured
splitOut
, and a diagonal wipe is just
wipeIn
/
wipeOut
with a non-cardinal
angle
.
这些转场仅通过
transitions
命名空间公开(不像旧版
moveIn
/
showWithDissolve
那样提供扁平的顶层导出),每种转场都有相互对应的
xIn
/
xOut
形式,其
(alias, componentOrUrl?, props?, priority?)
或
(alias, props?, priority?)
参数结构与上述函数相同。相比面向叙事的专用变体,它们更倾向于提供少量可配置选项——例如只有一个
wipeIn
,而不是
wipeLeft
/
wipeRight
。
ts
import { transitions } from "@drincs/pixi-vn";

// wipe: a moving boundary reveals/conceals the image. `angle` in degrees (0 = left-to-right, 90 =
// bottom-to-top, ...); `direction` ("up"/"down"/"left"/"right") is a shorthand for the 4 cardinal angles.
await transitions.wipeIn("background", "bg-forest", { direction: "left", duration: 1 });
await transitions.wipeIn("background", "bg-night", { angle: 45, duration: 1 }); // diagonal wipe
transitions.wipeOut("background", { angle: 180, invert: true, duration: 1 });

// iris: an expanding/contracting radial mask. `origin` is normalized (0-1) to the element's own bounds.
await transitions.irisIn("liam", "liam-happy", { origin: { x: 0.5, y: 0.3 }, duration: 0.8 });
transitions.irisOut("liam", { aspect: 2, duration: 0.8 }); // aspect > 1 = wide ellipse instead of a circle

// split: two mask panels move apart/together - covers "curtain" effects without a dedicated API.
await transitions.splitIn("background", "bg-forest", { orientation: "horizontal", duration: 1 });
transitions.splitOut("background", { orientation: "vertical", origin: 0.3, duration: 1 });

// flash: a configurable color overlay (not just white) fades in/hold/out, optionally pulsing.
await transitions.flashIn("background", "bg-forest", { duration: 0.15 }); // white camera-flash reveal
transitions.flashOut("liam", { color: 0xff0033, holdDuration: 0.05, pulses: 3, duration: 0.1 }); // red damage flash

// blur / pixelate: the image is shown already blurred/pixelated and resolves into focus (or the reverse for *Out).
await transitions.blurIn("liam", "liam-happy", { strength: 40, duration: 1 });
transitions.blurOut("liam", { duration: 1 });
await transitions.pixelateIn("background", "bg-forest", { pixelSize: 48, duration: 1 });
transitions.pixelateOut("background", { duration: 1 });

// glitch: jittery slices + red/blue split, settling on *In, building up on *Out.
await transitions.glitchIn("liam", "liam-happy", { strength: 40, bursts: 3 });
// twist: unwinds out of (or winds into) a swirl - `angle` in degrees, `origin` normalized.
transitions.twistOut("liam", { angle: 540 });
// warp: radial zoom-blur streaks, like dropping out of / jumping into hyperspace.
await transitions.warpIn("background", "bg-space", { strength: 0.6 });
// ripple: fades in/out through a spreading water ring - dreams, magic, memories.
await transitions.rippleIn("liam", "liam-dream", { origin: { x: 0.5, y: 0.3 } });
// noiseDissolve: the classic VN image dissolve - organic noise blotches (`edge: "soft"` for a cloudy fade).
await transitions.noiseDissolveIn("background", "bg-night", { edge: "hard", noiseScale: 8 });
// tv: old TV switching on (dot -> glowing line -> image) / off; scales around the anchor (center it).
transitions.tvOut("liam", { lineThickness: 0.02, brightness: 3 });
// pinch: emerges from / is sucked into a point (`mode: "bulge"` puffs out instead).
transitions.pinchOut("liam", { mode: "pinch" });

// move/push also take an optional motion-blur trail (true = 40px, or a length in pixels).
await transitions.moveIn("liam", "liam-happy", { direction: "right", duration: 0.6, motionBlur: true });
畸变转场(glitch、twist、warp、ripple、pinch)会移动像素,因此最适合用于包含丰富细节的图像;在纯色的单一元素上,其效果会明显弱得多。
这些转场可以自由组合,因为每种转场都会独立驱动自己的遮罩(
wipe
/
iris
/
split
)或滤镜(
blur
/
pixelate
/
glitch
等)——例如,可以对同一个 alias 依次调用
blurIn
和
wipeIn
,将两种效果结合起来。常见的叙事效果通常只是由这些原语组合而成的配方,而不是专用函数:例如“眨眼”/睁眼效果由颜色叠加和光圈显示组成,“梦境”/闪回由
blurIn
和
showWithFade
组成,记忆转场由颜色叠加和
blurIn
组成,“幕布”效果是配置后的
splitOut
,对角擦除则只需使用非正交
angle
调用
wipeIn
/
wipeOut
。

Creating custom transitions

创建自定义转场

A custom transition is just a plain function that adds/replaces a component and drives it with
canvas.animate
— the same primitive
showWithDissolve
,
moveIn
, etc. are built on:
ts
import { canvas, ImageSprite, UPDATE_PRIORITY } from "@drincs/pixi-vn";
import { AnimationOptions } from "@drincs/pixi-vn/motion";

export default async function showWithDissolve(
  alias: string,
  component: ImageSprite,
  props: AnimationOptions = {},
  priority?: UPDATE_PRIORITY,
): Promise<string[] | undefined> {
  const { completeOnContinue = true, ...options } = props;
  canvas.add(alias, component);
  component.alpha = 0;
  const id = canvas.animate(alias, { alpha: 1 }, { ...options, completeOnContinue }, priority);
  if (component.haveEmptyTexture) await component.load();
  if (id) return [id];
}
If a component under the same alias may already exist, either let
canvas.add
replace it outright (per the heredity-factor gotcha above), or explicitly swap it in first — rename the old component's alias, add the new one, restore z-order, then transfer its properties/tickers — so an in-flight animation on the old element carries over instead of snapping:
ts
let oldComponentAlias: string | undefined;
const oldComponent = canvas.find(alias);
if (oldComponent) {
  oldComponentAlias = `${alias}_temp`;
  canvas.editAlias(alias, oldComponentAlias);
}
canvas.add(alias, component);
oldComponent?.parent?.setChildIndex(oldComponent, oldComponent.parent.getChildIndex(oldComponent) - 0.1);
oldComponentAlias && canvas.copyCanvasElementProperty(oldComponentAlias, alias);
oldComponentAlias && tickers.transfer(oldComponentAlias, alias, "duplicate");
To remove the old component only once the new one's transition finishes, pass
aliasToRemoveAfter: oldComponentAlias
in
canvas.animate
's
options
instead of removing it manually. To have the old component run its own transition-out (instead of a hard cut), give it a second
canvas.animate
call paused with
tickers.pause({ id })
and resumed via the new call's
tickerIdToResume
option — see the wiki's own two worked examples for the exact sequencing.
Animating a filter's own property instead of a component property — for effects like blur, pixelate, glow, or color grading, use
filters.animate
(from
@drincs/pixi-vn/filters
, available since v1.9.4) instead of
canvas.animate
. It mirrors the same
(alias, keyframes, options, priority)
shape with the
Filter
instance inserted as the second argument; you attach the filter to
component.filters
yourself before animating, and detach + destroy it in the
cleanup
callback (the 7th argument) once the animation completes — this is exactly how
transitions.blurIn
/
blurOut
/
pixelateIn
/
pixelateOut
are implemented:
ts
import { canvas, ImageSprite, UPDATE_PRIORITY } from "@drincs/pixi-vn";
import { filters } from "@drincs/pixi-vn/filters";
import { AnimationOptions } from "@drincs/pixi-vn/motion";

export default async function blurIn(
  alias: string,
  component: ImageSprite,
  props: AnimationOptions & { strength?: number } = {},
  priority?: UPDATE_PRIORITY,
): Promise<string[] | undefined> {
  const { strength = 32, completeOnContinue = true, ...options } = props;
  canvas.add(alias, component);
  const filter = new filters.BlurFilter({ strength });
  component.filters = [filter];
  const id = filters.animate(
    alias,
    filter,
    { strength: [strength, 0] },
    { ...options, completeOnContinue },
    priority,
    undefined, // apply - only needed when filter is undefined (see below)
    () => {
      component.filters = null;
      filter.destroy();
    },
  );
  if (component.haveEmptyTexture) await component.load();
  if (id) return [id];
}
filters.animate
can also drive a plain numeric value with no live filter — pass
undefined
as the filter and an
apply
callback (called every frame with the interpolated value) instead. This is how
wipeIn
/
irisIn
/
splitIn
animate their mask geometry (a growing radius, a moving boundary), since a mask has no filter property to write directly.
自定义转场只是一个普通函数,用于添加或替换组件,并通过
canvas.animate
驱动组件——
showWithDissolve
、
moveIn
等内置转场也是基于这一原语构建的:
ts
import { canvas, ImageSprite, UPDATE_PRIORITY } from "@drincs/pixi-vn";
import { AnimationOptions } from "@drincs/pixi-vn/motion";

export default async function showWithDissolve(
  alias: string,
  component: ImageSprite,
  props: AnimationOptions = {},
  priority?: UPDATE_PRIORITY,
): Promise<string[] | undefined> {
  const { completeOnContinue = true, ...options } = props;
  canvas.add(alias, component);
  component.alpha = 0;
  const id = canvas.animate(alias, { alpha: 1 }, { ...options, completeOnContinue }, priority);
  if (component.haveEmptyTexture) await component.load();
  if (id) return [id];
}
如果同一 alias 下可能已经存在组件,可以直接让
canvas.add
替换它(遵循上述 heredity-factor 注意事项),也可以先显式交换组件——重命名旧组件的 alias,添加新组件,恢复 z-order,然后转移其属性/ticker——这样旧元素上正在进行的动画便能延续,而不会突然跳变:
ts
let oldComponentAlias: string | undefined;
const oldComponent = canvas.find(alias);
if (oldComponent) {
  oldComponentAlias = `${alias}_temp`;
  canvas.editAlias(alias, oldComponentAlias);
}
canvas.add(alias, component);
oldComponent?.parent?.setChildIndex(oldComponent, oldComponent.parent.getChildIndex(oldComponent) - 0.1);
oldComponentAlias && canvas.copyCanvasElementProperty(oldComponentAlias, alias);
oldComponentAlias && tickers.transfer(oldComponentAlias, alias, "duplicate");
如果只应在新组件的转场完成后移除旧组件,请在
canvas.animate
的
options
中传入
aliasToRemoveAfter: oldComponentAlias
,而不要手动移除。如果希望旧组件运行自己的转场退出效果(而不是直接切换),可以再发起一次
canvas.animate
调用,先使用
tickers.pause({ id })
将其暂停,再通过新调用的
tickerIdToResume
选项恢复——准确的执行顺序请参阅 wiki 中的两个完整示例。
不对组件属性、而是对滤镜自身的属性进行动画处理——对于模糊、像素化、辉光或色彩分级等效果,应使用
filters.animate
(来自
@drincs/pixi-vn/filters
,自 v1.9.4 起提供),而不是
canvas.animate
。它具有相同的
(alias, keyframes, options, priority)
参数结构,第二个参数为
Filter
实例;你需要在动画前自行将滤镜附加到
component.filters
,并在动画完成后的
cleanup
回调(第 7 个参数)中分离和销毁滤镜——
transitions.blurIn
/
blurOut
/
pixelateIn
/
pixelateOut
正是这样实现的:
ts
import { canvas, ImageSprite, UPDATE_PRIORITY } from "@drincs/pixi-vn";
import { filters } from "@drincs/pixi-vn/filters";
import { AnimationOptions } from "@drincs/pixi-vn/motion";

export default async function blurIn(
  alias: string,
  component: ImageSprite,
  props: AnimationOptions & { strength?: number } = {},
  priority?: UPDATE_PRIORITY,
): Promise<string[] | undefined> {
  const { strength = 32, completeOnContinue = true, ...options } = props;
  canvas.add(alias, component);
  const filter = new filters.BlurFilter({ strength });
  component.filters = [filter];
  const id = filters.animate(
    alias,
    filter,
    { strength: [strength, 0] },
    { ...options, completeOnContinue },
    priority,
    undefined, // apply - only needed when filter is undefined (see below)
    () => {
      component.filters = null;
      filter.destroy();
    },
  );
  if (component.haveEmptyTexture) await component.load();
  if (id) return [id];
}
filters.animate
也可以在没有实时滤镜的情况下驱动普通数值——此时将 filter 传入
undefined
,并提供一个每帧接收插值结果的
apply
回调。
wipeIn
/
irisIn
/
splitIn
正是通过这种方式动画化遮罩几何形状,例如不断增大的半径或移动的边界,因为遮罩没有可直接写入的滤镜属性。

Shake and other articulated animations

抖动及其他 articulated animations

The
effects
namespace holds "articulated animations" — helpers built on top of
canvas.animate
that bake a full keyframe array once and animate it in a single call (docs: canvas-articulated-animations-effects, canvas-motion). Like the transitions above, these are generic primitives — the effect describes how a component moves, the game decides what that means (a hit reaction, an idle fidget, an emphasis beat, ...).
ts
import { effects, canvas } from "@drincs/pixi-vn";

// shake a canvas element horizontally
await effects.shakeEffect("screen-flash-target", {
  shakeType: "horizontal",
  maxShockSize: 15,
  shocksNumber: 10,
});

// bounce: one-directional decaying displacement, e.g. a character landing a jump
await effects.bounceEffect("liam", { direction: "up", distance: 30, bounces: 3 });

// pulse: decaying scale bump, e.g. emphasizing a UI element or a heartbeat
await effects.pulseEffect("liam", { scale: 1.3, pulses: 2 });

// hop: a single displacement-and-return, optionally with smaller decaying follow-up hops
await effects.hopEffect("liam", { direction: "up", distance: 40, secondaryHops: 2 });

// wiggle: decaying rotation oscillation, e.g. a "no" head-shake or a nervous tic
await effects.wiggleEffect("liam", { angle: 15, repetitions: 3 });

// nod: decaying positional oscillation along one axis, e.g. a "yes" nod or a flinch
await effects.nodEffect("liam", { axis: "vertical", distance: 10, repetitions: 3 });

// sway: smooth combined position+rotation drift, e.g. an idle breathing/swaying loop
// (decay defaults to 0 = constant amplitude, unlike the other primitives above)
await effects.swayEffect("liam", { distance: 8, angle: 3, repetitions: 4 });

// punch: a single fast impulse with a settle-back overshoot, e.g. taking a hit
await effects.punchEffect("liam", { mode: "scale", strength: 0.3, overshoot: 0.3 });
await effects.punchEffect("liam", { mode: "rotation", strength: 20 });

// low-level: animate arbitrary numeric properties with motion-style keyframes
canvas.animate("liam", { alpha: [0, 1], y: [50, 0] }, { duration: 0.8 });
There's no separate "tremble"/"vibration" primitive — that's just
shakeEffect
configured with a small
maxShockSize
and a high
shocksNumber
(high-frequency, low-amplitude shaking).
effects
命名空间包含“articulated animations”——一组构建于
canvas.animate
之上的辅助函数,它们会一次性生成完整的关键帧数组,并通过单次调用完成动画(文档:canvas-articulated-animations-effects、canvas-motion)。与上述转场一样,这些是通用原语——效果负责描述组件如何运动,而游戏负责决定这一运动在语义上代表什么(受击反应、闲置小动作、强调节拍等)。
ts
import { effects, canvas } from "@drincs/pixi-vn";

// shake a canvas element horizontally
await effects.shakeEffect("screen-flash-target", {
  shakeType: "horizontal",
  maxShockSize: 15,
  shocksNumber: 10,
});

// bounce: one-directional decaying displacement, e.g. a character landing a jump
await effects.bounceEffect("liam", { direction: "up", distance: 30, bounces: 3 });

// pulse: decaying scale bump, e.g. emphasizing a UI element or a heartbeat
await effects.pulseEffect("liam", { scale: 1.3, pulses: 2 });

// hop: a single displacement-and-return, optionally with smaller decaying follow-up hops
await effects.hopEffect("liam", { direction: "up", distance: 40, secondaryHops: 2 });

// wiggle: decaying rotation oscillation, e.g. a "no" head-shake or a nervous tic
await effects.wiggleEffect("liam", { angle: 15, repetitions: 3 });

// nod: decaying positional oscillation along one axis, e.g. a "yes" nod or a flinch
await effects.nodEffect("liam", { axis: "vertical", distance: 10, repetitions: 3 });

// sway: smooth combined position+rotation drift, e.g. an idle breathing/swaying loop
// (decay defaults to 0 = constant amplitude, unlike the other primitives above)
await effects.swayEffect("liam", { distance: 8, angle: 3, repetitions: 4 });

// punch: a single fast impulse with a settle-back overshoot, e.g. taking a hit
await effects.punchEffect("liam", { mode: "scale", strength: 0.3, overshoot: 0.3 });
await effects.punchEffect("liam", { mode: "rotation", strength: 20 });

// low-level: animate arbitrary numeric properties with motion-style keyframes
canvas.animate("liam", { alpha: [0, 1], y: [50, 0] }, { duration: 0.8 });
系统没有单独的“颤抖”/“振动”原语——只需将
shakeEffect
配置为较小的
maxShockSize
和较高的
shocksNumber
,即可实现高频、低振幅的抖动。

Filter-based articulated animations

基于滤镜的 articulated animations

The same
effects
namespace also has primitives built on the
filters
module (raw
pixi-filters
/
pixi.js
filter classes, re-exported as
filters.*
- see
@drincs/pixi-vn/filters
) instead of
canvas.animate
: each one attaches a filter to the component, drives one of the filter's own properties through a decaying (or one-shot) keyframe array via
filters.animate
, and detaches/destroys the filter once done - the component is left exactly as it was before, same guarantee as every effect above.
ts
import { effects } from "@drincs/pixi-vn";

// glitch: jittery, decaying bursts of slice displacement with a matching red/blue channel split
// (returns two ticker ids; `rgbSplit: 0` drops the channel split)
await effects.glitchEffect("liam", { strength: 40, bursts: 3, rgbSplit: 6 });

// chromaticAberration: red/blue channels split apart and snap back, in a decaying burst
await effects.chromaticAberrationEffect("liam", { strength: 8, axis: "horizontal" });

// shockwave: a single ripple distortion travels outward from `origin` (normalized to the element's
// bounds) - by default until it has fully left the element
await effects.shockwaveEffect("liam", { origin: { x: 0.5, y: 0.5 } });

// radialBlur: a decaying burst of zoom-blur radiating from `origin` (normalized to the element's bounds)
await effects.radialBlurEffect("liam", { strength: 0.3, bursts: 1 });

// blurPulse: a repeated, decaying blur bump (distinct from blurIn/blurOut, which are one-shot
// reveal/conceal transitions, not a repeated pulse)
await effects.blurPulseEffect("liam", { strength: 8, pulses: 3 });

// vignettePulse: the edges darken and recover, in a repeated, decaying pulse - it darkens the corners
// of the element's own rectangle, so use it on backgrounds/full-frame images, not on characters
await effects.vignettePulseEffect("background", { strength: 1, pulses: 1, radius: 0.5 });

// desaturate: color drains out and recovers - a single dip, not a repeated pulse
await effects.desaturateEffect("liam", { amount: 0, holdDuration: 0.2 });

// glowPulse: a repeated, decaying outward glow
await effects.glowPulseEffect("liam", { strength: 4, pulses: 3, color: 0xffee00 });
All 8 are per-component (same
alias
pattern as everything else here) - there's no screen-wide/global filter effect yet. Distortion effects (glitch, chromatic aberration, shockwave, radial blur) move pixels around, so they only read on an image with detail/edges - on a flat, single-color element they look like nothing happened. Build a custom one the same way: construct any
filters.*
class, attach it to
component.filters
yourself, and drive it with
filters.animate
(see the low-level example just above this section) -
AdjustmentFilter
/
HslAdjustmentFilter
(color grading),
CRTFilter
/
OldFilmFilter
(retro looks), and
BloomFilter
/
AdvancedBloomFilter
(glow) are good starting points not covered above.
canvas.animate(componentOrAlias, keyframes, options, priority)
is the primitive all transition helpers are built on (motion's
animate
semantics: keyframes are arrays of target values,
options
supports
duration
,
ease
,
repeat
, etc.). Pixi'VN tracks animation state so it can be saved/restored — this is why you should prefer
canvas.animate
(or the transition helpers) over driving PixiJS directly with your own
requestAnimationFrame
loop. For UI that is not part of the saved scene graph (e.g. a PixiJS UI overlay), the docs recommend importing the raw
animate
from
@drincs/pixi-vn/motion
instead — it skips the save-state bookkeeping and is cheaper.
同一个
effects
命名空间还提供了一组基于
filters
模块构建的原语,而不是使用
canvas.animate
。这些原语使用原始的
pixi-filters
/
pixi.js
滤镜类(通过
filters.*
重新导出——请参阅
@drincs/pixi-vn/filters
):每种效果都会将滤镜附加到组件,通过
filters.animate
使用衰减式或一次性关键帧数组驱动滤镜自身的属性,并在完成后分离和销毁滤镜——组件最终会恢复到执行前的状态,与上述所有效果具有相同的保证。
ts
import { effects } from "@drincs/pixi-vn";

// glitch: jittery, decaying bursts of slice displacement with a matching red/blue channel split
// (returns two ticker ids; `rgbSplit: 0` drops the channel split)
await effects.glitchEffect("liam", { strength: 40, bursts: 3, rgbSplit: 6 });

// chromaticAberration: red/blue channels split apart and snap back, in a decaying burst
await effects.chromaticAberrationEffect("liam", { strength: 8, axis: "horizontal" });

// shockwave: a single ripple distortion travels outward from `origin` (normalized to the element's
// bounds) - by default until it has fully left the element
await effects.shockwaveEffect("liam", { origin: { x: 0.5, y: 0.5 } });

// radialBlur: a decaying burst of zoom-blur radiating from `origin` (normalized to the element's bounds)
await effects.radialBlurEffect("liam", { strength: 0.3, bursts: 1 });

// blurPulse: a repeated, decaying blur bump (distinct from blurIn/blurOut, which are one-shot
// reveal/conceal transitions, not a repeated pulse)
await effects.blurPulseEffect("liam", { strength: 8, pulses: 3 });

// vignettePulse: the edges darken and recover, in a repeated, decaying pulse - it darkens the corners
// of the element's own rectangle, so use it on backgrounds/full-frame images, not on characters
await effects.vignettePulseEffect("background", { strength: 1, pulses: 1, radius: 0.5 });

// desaturate: color drains out and recovers - a single dip, not a repeated pulse
await effects.desaturateEffect("liam", { amount: 0, holdDuration: 0.2 });

// glowPulse: a repeated, decaying outward glow
await effects.glowPulseEffect("liam", { strength: 4, pulses: 3, color: 0xffee00 });
这 8 种效果均按组件生效(使用与此处其他功能相同的
alias
模式)——目前尚不支持全屏或全局滤镜效果。畸变效果(glitch、chromatic aberration、shockwave、radial blur)会移动像素,因此只有在包含细节或边缘的图像上才会清晰可见;在纯色的单一元素上,看起来会像什么都没发生。可以用相同方式构建自定义效果:创建任意
filters.*
类,自行将其附加到
component.filters
,再使用
filters.animate
驱动它(请参阅上一节的低层级示例)。
AdjustmentFilter
/
HslAdjustmentFilter
(色彩分级)、
CRTFilter
/
OldFilmFilter
(复古效果)和
BloomFilter
/
AdvancedBloomFilter
(辉光)都是不错的起点,但未包含在上述效果中。
canvas.animate(componentOrAlias, keyframes, options, priority)
是所有转场辅助函数所基于的原语(采用
motion
的
animate
语义:motion.dev/docs/animate;关键帧是目标值数组,
options
支持
duration
、
ease
、
repeat
等)。Pixi'VN 会跟踪动画状态,以便对其进行保存和恢复——因此应优先使用
canvas.animate
(或转场辅助函数),而不是通过自定义
requestAnimationFrame
循环直接驱动 PixiJS。对于不属于可保存场景图的 UI(例如 PixiJS UI 覆盖层),文档建议直接导入
@drincs/pixi-vn/motion
中的原始
animate
——这样可以跳过保存状态 bookkeeping,开销也更低。

Frame tickers

帧 Ticker

canvas.animate
and the transition helpers above already use tickers internally — most tasks never need to touch the ticker API directly. For a genuinely continuous/looping custom effect (no fixed duration) or manual pause/resume/completion control over a running animation, see
tickers.md
in this same skill folder (registering a
Ticker
,
tickers.addSequence
,
tickers.pause
/
resume
/
remove
,
tickers.completeOnStepEnd
). Docs: pixi-vn.com/start/canvas-tickers.
canvas.animate
和上述转场辅助函数已在内部使用 ticker——大多数任务都不需要直接操作 ticker API。如需真正持续或循环的自定义效果(没有固定时长),或者需要手动暂停、恢复及完成正在运行的动画,请参阅同一 skill 文件夹中的
tickers.md
(注册
Ticker
、
tickers.addSequence
、
tickers.pause
/
resume
/
remove
、
tickers.completeOnStepEnd
)。文档:pixi-vn.com/start/canvas-tickers。

External rendering plugins

外部渲染插件

These optional packages are installed separately from
@drincs/pixi-vn
; their component classes must be imported from the plugin, not the engine. Check the installed package's peer dependencies and types before adapting examples to a project's PixiJS/Pixi'VN versions.
以下可选包需要与
@drincs/pixi-vn
分开安装;它们的组件类必须从插件中导入,而不能从引擎中导入。在调整示例以适配项目的 PixiJS/Pixi'VN 版本之前,请检查已安装包的 peer dependencies 和类型定义。

Live2D:
@drincs/pixi-vn-live2d

Live2D:
@drincs/pixi-vn-live2d

Use this wrapper around
untitled-pixi-live2d-engine
for Live2D models. Register its render plugin before
Game.init()
(or
canvas.init()
):
ts
import { extensions } from "pixi.js";
import { Live2DPlugin } from "@drincs/pixi-vn-live2d/core";

extensions.add(Live2DPlugin);
// Initialize the game/canvas after registering the plugin.
Once the canvas is initialized, register the model asset and await model readiness before controlling it:
ts
import { Assets, canvas } from "@drincs/pixi-vn";
import { Live2D } from "@drincs/pixi-vn-live2d";

Assets.add({ alias: "hero-model", src: "/models/hero/model3.json" });
const model = new Live2D({ source: "hero-model" });
await model.ready;
model.motion("Idle"); // Use a motion group present in this model.
canvas.add("hero", model);
Consult the plugin README for setup and its API reference for model options. Do not substitute
ImageSprite.load()
for
Live2D.ready
.
使用这个基于
untitled-pixi-live2d-engine
的包装器来渲染 Live2D 模型。必须在
Game.init()
(或
canvas.init()
)之前注册渲染插件:
ts
import { extensions } from "pixi.js";
import { Live2DPlugin } from "@drincs/pixi-vn-live2d/core";

extensions.add(Live2DPlugin);
// Initialize the game/canvas after registering the plugin.
Canvas 初始化后,应注册模型资源,并在控制模型前等待模型就绪:
ts
import { Assets, canvas } from "@drincs/pixi-vn";
import { Live2D } from "@drincs/pixi-vn-live2d";

Assets.add({ alias: "hero-model", src: "/models/hero/model3.json" });
const model = new Live2D({ source: "hero-model" });
await model.ready;
model.motion("Idle"); // Use a motion group present in this model.
canvas.add("hero", model);
有关设置和模型选项,请参阅插件 README及其 API 参考。不要使用
ImageSprite.load()
代替
Live2D.ready
。

Spine:
@drincs/pixi-vn-spine

Spine:
@drincs/pixi-vn-spine

Use this wrapper around
@esotericsoftware/spine-pixi-v8
for skeletal animation. Add
import "@drincs/pixi-vn-spine";
to the app entry point: importing the package registers the serializable component. A lazy scene import alone is insufficient when a save is restored before that scene has ever loaded.
Register the skeleton and atlas in the asset manifest, then load both before construction:
ts
import { Assets, canvas } from "@drincs/pixi-vn";
import { Spine } from "@drincs/pixi-vn-spine";

await Assets.load(["hero-skeleton", "hero-atlas"]);
const model = new Spine({ skeleton: "hero-skeleton", atlas: "hero-atlas" });
model.setAnimation(0, "idle", true); // Animation names come from the skeleton.
canvas.add("hero", model);
See the Spine guide for skins and tracks and the package README for save-registration details. Keep the Pixi'VN wrapper when adding saved scene elements.
使用这个基于
@esotericsoftware/spine-pixi-v8
的包装器进行骨骼动画。将
import "@drincs/pixi-vn-spine";
添加到应用入口点:导入该包会注册可序列化组件。如果在某个场景从未加载过之前恢复了存档,仅在场景中执行延迟导入并不足以完成注册。
在资源清单中注册骨架和图集,然后在构建前加载二者:
ts
import { Assets, canvas } from "@drincs/pixi-vn";
import { Spine } from "@drincs/pixi-vn-spine";

await Assets.load(["hero-skeleton", "hero-atlas"]);
const model = new Spine({ skeleton: "hero-skeleton", atlas: "hero-atlas" });
model.setAnimation(0, "idle", true); // Animation names come from the skeleton.
canvas.add("hero", model);
有关皮肤和轨道,请参阅 Spine 指南;有关保存注册详情,请参阅包 README。添加可保存的场景元素时,请保留 Pixi'VN 包装器。

UI layers

UI 层

Persistent UI chrome (HUD, menus) does not live on
gameLayer
— it lives on a separate, non-save-able layer built with
canvas.layers.add
/
get
/
remove
(PixiJS-only UI) or
canvas.htmlLayers.add
/
get
/
remove
(mounting a DOM-based UI framework like React or Vue). That whole API, plus the official template's layer-naming conventions,
extractImage()
for save-file thumbnails, and how to build a UI purely out of PixiJS components, is covered by
pixi-vn-ui
— reach for that skill instead whenever the task is about UI rather than the game scene.
持久化 UI 框架(HUD、菜单)不位于
gameLayer
中——它位于一个独立的、不可保存的层中,可通过
canvas.layers.add
/
get
/
remove
构建纯 PixiJS UI,或通过
canvas.htmlLayers.add
/
get
/
remove
挂载基于 DOM 的 UI 框架(如 React 或 Vue)。整个 API、官方模板的图层命名约定、用于生成存档缩略图的
extractImage()
,以及如何完全使用 PixiJS 组件构建 UI,均由
pixi-vn-ui
覆盖——只要任务与 UI 而非游戏场景有关,就应使用该 skill。

Gotchas

注意事项

  • addImage
    /
    addVideo
    /
    addImageCointainer
    only construct and register the element — the texture is not loaded/visible until you
    await element.load()
    .
    The
    show*
    variants (
    showImage
    ,
    showVideo
    ,
    showImageContainer
    , and the transition helpers) call
    load()
    for you; prefer them unless you need to prepare an element off-screen first.
  • getTexture
    /
    load()
    throw a
    PixiError
    (
    unregistered_asset
    )
    if the alias can't be resolved from the PixiJS
    Assets
    cache/manifest. See
    pixi-vn-assets
    for how to register an alias (local assets via AssetPack, or online assets in
    src/assets/index.ts
    ) before referencing it here.
  • Adding an element with an alias that already exists replaces it and — per the "heredity factor" — copies over properties/
    zIndex
    /tickers from the old one by default (
    canvas.add(alias, el, { ignoreOldStyle: true })
    to opt out) — this is what lets
    showImage("bg", newUrl)
    "just work" as a same-alias swap.
  • canvas.remove(alias)
    also removes tickers
    bound only to that alias; pass
    { ignoreTickers: true }
    if you intend to reattach them elsewhere first.
  • Event listeners use plain PixiJS
    .on(...)
    , not a Pixi'VN-specific method
    — a stray JSDoc comment in the
    Sprite
    source suggests
    sprite.onEvent(...)
    , but that method doesn't exist; the official docs confirm
    .on(...)
    is correct. The Pixi'VN-specific part is only the
    @eventDecorator()
    requirement for save/load:
    sprite.on("pointerdown", Events.handler)
    only serializes correctly if
    Events.handler
    is a static method decorated with
    @eventDecorator()
    (from
    @drincs/pixi-vn
    ); a plain inline arrow function will log a warning and not be restored after loading a save.
  • Positioning has multiple parallel systems (docs: canvas-position) — pixel (
    x
    /
    y
    /
    position
    ), anchor/
    pivot
    (where on the element itself the position point sits),
    align
    /
    xAlign
    /
    yAlign
    (Ren'Py-style percentage-of-canvas positioning, e.g.
    0.5
    = centered; per source JSDoc,
    pivot
    does not affect
    align
    ), and
    percentagePosition
    /
    percentageX
    /
    percentageY
    (also percentage-of-parent, but
    pivot
    does affect it
    , unlike
    align
    ). Setting
    x
    /
    align
    /
    percentagePosition
    on the same axis clears the others; check
    element.positionType
    /
    element.positionInfo
    if you need to know which mode is active.
  • CANVAS_APP_GAME_LAYER_ALIAS
    (
    "__game_layer__"
    ) is reserved
    —
    canvas.add
    ,
    remove
    , and
    addLayer
    will refuse to use that alias.
  • canvas.pause()
    /
    canvas.resume()
    stop/restart rendering and tickers for the whole game layer (e.g. when opening a menu) — remember to call
    resume()
    or elements will appear frozen.
  • addImage
    /
    addVideo
    /
    addImageCointainer
    只负责创建和注册元素——在调用
    await element.load()
    之前,纹理尚未加载或显示。
    show*
    变体(
    showImage
    、
    showVideo
    、
    showImageContainer
    及转场辅助函数)会替你调用
    load()
    ;除非需要先在屏幕外准备元素,否则应优先使用它们。
  • 如果 alias 无法从 PixiJS
    Assets
    缓存或清单中解析,
    getTexture
    /
    load()
    会抛出
    PixiError
    (
    unregistered_asset
    )。
    请参阅
    pixi-vn-assets
    ,了解如何在使用 alias 前注册它(通过 AssetPack 注册本地资源,或在
    src/assets/index.ts
    中注册在线资源)。
  • 添加一个已存在 alias 的元素会替换原元素,并且根据“heredity factor”,默认会从旧元素复制属性、
    zIndex
    和 ticker(可通过
    canvas.add(alias, el, { ignoreOldStyle: true })
    退出此行为)——正是这一机制让
    showImage("bg", newUrl)
    能够直接完成同 alias 的内容替换。
  • canvas.remove(alias)
    也会移除仅绑定到该 alias 的 ticker
    ;如果打算先将它们重新附加到其他位置,请传入
    { ignoreTickers: true }
    。
  • 事件监听器使用原生 PixiJS 的
    .on(...)
    ,而不是 Pixi'VN 专用方法
    ——
    Sprite
    源码中某个 JSDoc 注释提示应使用
    sprite.onEvent(...)
    ,但该方法并不存在;官方文档确认应使用
    .on(...)
    。Pixi'VN 特有要求仅涉及保存/加载时必须使用
    @eventDecorator()
    :只有当
    Events.handler
    是使用
    @eventDecorator()
    (来自
    @drincs/pixi-vn
    )装饰的静态方法时,
    sprite.on("pointerdown", Events.handler)
    才能被正确序列化;普通的内联箭头函数会产生警告,并且在加载存档后无法恢复。
  • 定位系统有多个并行体系(文档:canvas-position)——像素定位(
    x
    /
    y
    /
    position
    )、锚点/枢轴定位(
    anchor
    /
    pivot
    ,表示元素自身上的哪个位置与定位点对应)、
    align
    /
    xAlign
    /
    yAlign
    (类似 Ren'Py 的按 Canvas 百分比定位,例如
    0.5
    表示居中;根据源码 JSDoc,
    pivot
    不影响
    align
    ),以及
    percentagePosition
    /
    percentageX
    /
    percentageY
    (同样按父元素百分比定位,但与
    align
    不同,
    pivot
    会影响它
    )。在同一个轴上设置
    x
    /
    align
    /
    percentagePosition
    会清除其他定位方式;如果需要确定当前生效的模式,请检查
    element.positionType
    /
    element.positionInfo
    。
  • CANVAS_APP_GAME_LAYER_ALIAS
    (
    "__game_layer__"
    )为保留值
    ——
    canvas.add
    、
    remove
    和
    addLayer
    会拒绝使用该 alias。
  • canvas.pause()
    /
    canvas.resume()
    会停止或重新启动整个游戏层的渲染和 ticker
    (例如打开菜单时)——记得调用
    resume()
    ,否则元素会看起来像是被冻结了。

Related skills

相关 skill

  • pixi-vn-getting-started: project setup,
    Game.init()
    , and how the canvas is wired up at startup.
  • pixi-vn-assets: registering images/video (local or online) and choosing when their bundle loads, before referencing them here by alias.
  • pixi-vn-characters: character/emotion definitions that use canvas images under the hood for portraits.
  • pixi-vn-narration: dialogue/step flow, and how canvas transitions integrate with
    completeOnContinue
    .
  • pixi-vn-storage: how canvas element state (by alias) is saved and restored across game saves.
  • pixi-vn-ui: building/mounting UI layers (HTML or PixiJS) on top of the canvas —
    addLayer
    ,
    addHtmlLayer
    , and building UI screens with PixiJS components.
  • pixi-vn-getting-started:项目设置、
    Game.init()
    ,以及 Canvas 在启动时如何完成连接。
  • pixi-vn-assets:在通过 alias 引用图像或视频前,注册本地或在线资源,以及选择资源包的加载时机。
  • pixi-vn-characters:角色和情绪定义,其底层会使用 Canvas 图像呈现角色立绘。
  • pixi-vn-narration:对话和步骤流程,以及 Canvas 转场如何与
    completeOnContinue
    集成。
  • pixi-vn-storage:如何跨游戏存档保存和恢复 Canvas 元素状态(按 alias 管理)。
  • pixi-vn-ui:在 Canvas 上构建或挂载 UI 层(HTML 或 PixiJS)——
    addLayer
    、
    addHtmlLayer
    ,以及使用 PixiJS 组件构建 UI 屏幕。