LogoPixi’VN
创建你的第一个项目

制作你的第一部视觉小说

使用 Pixi’VN 创建视觉小说的分步指南,涵盖项目设置、叙事、素材和交互性。

本教程将指导你完成创建第一部视觉小说的过程。

为了测试的目的,在本指南中我们将使用 Pixi’VN 重新创建视觉小说 Breakdown。 Breakdown 是一个短篇故事,具备了视觉小说应有的所有特性。 Breakdown 的创作者 Josh Powlison 已经允许我们出于教育目的使用他的叙事内容❤️。

由于 Pixi’VN 允许你通过选择一种或多种可用的叙事语言来编写自己的叙事内容,因此在每个开发步骤中都会为当前每种可用的语言提供示例。

创建新项目

在开始之前,请确保你已经安装了前置条件

第一步是创建一个新项目。 你可以通过运行以下命令来完成:

npm create pixi-vn@latest

然后选择 Visual Novel 模板。

VS Code

如果你使用 VS Code 打开项目,系统会要求你同意运行一些工作区任务,并安装项目推荐的扩展。 强烈建议你接受这两项。

你可以在这里找到有关模板、任务和扩展的更多信息。

创建角色

现在我们将定义这个故事中的角色。 为此,我们将在 /content/characters.ts 文件中定义我们将要使用的角色。

有关如何创建和使用角色的更多信息,请参阅:Characters

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]);

叙事初稿

Markup

所有模板都支持MarkdownTailwind CSS,因此我们将在叙事中使用它。

现在我们可以开始编写视觉小说叙事的"初稿"了。 我们将创建第一个名为 start叙事节点(label,它将是游戏的开始。 之后我们可以编写视觉小说中接下来的对话

这是示例:

file_type_ink
ink/start.ink
=== start ===
james: You're my roommate's replacement, huh?
james: Don't worry, you don't have much to live up to. Just don't use heroin like the last guy, and you' fine!
mc: ...

He thrusts out his hand.

james: James!
mc: ...Peter.

I take his hand and shake.

james: Ooh, Peter! Nice, firm handshake! The last quy always gave me the dead fish. I already think we'r gonna get along fine.
james: Come on in and...
james: ...
james: I know you're both watching, come on out already!

sly: I just wanted to see what the new guy was like. Hey, you, Peter- be nice to our little brother, or you'll have to deal with *us*.
mc: ...
james: Peter, this is Sly. Yes, that is her real name.

I put out my hand.

sly: I'm not shakin' your hand until I decide you're an all-right dude. Sorry, policy.
mc: Fair enough, I'm a pretty scary guy, or so l've been told.
james: The redhead behind her is Stephanie.
// Example of using Tailwind CSS
steph: <span class="inline-block motion-translate-y-loop-25">Hey</span>! Everyone calls me Steph. I'll shake your hand.

// ...
-> DONE

将叙事拆分为多个叙事节点(labels

不建议创建非常长的叙事节点(labels)(即使对于线性视觉小说也是如此),而建议创建多个小的叙事节点(labels),并在需要时使用叙事流程控制功能来"调用"它们。

因此,尽管在我们的案例中故事是线性的,它仍将被分为两个叙事节点(labels),第一个是我们刚刚创建的那个(start),第二个将被称为 second_part

这是示例:

file_type_ink
ink/start.ink
=== start ===
james: You're my roommate's replacement, huh?
james: Don't worry, you don't have much to live up to. Just don't use heroin like the last guy, and you' fine!
mc: ...

He thrusts out his hand.

james: James!
mc: ...Peter.

// ...
-> second_part

=== second_part ===

She enters my room before I'VE even had a chance to. \\n\\n...I could've just come back and gotten the platter later...
She sets it on a desk. I throw my two paper bags down beside the empty bed.

steph: They got you a new mattress, right? That last guy was a druggie, did James tell you that?
sly: *We're* the reason he got expelled!
steph: Sly! If word gets out about that... well, actually, it wouldn't matter, *he's* the one who shot himself up.

I'm fumbling for a new subject.

// ...
-> DONE

选择菜单

现在我们将询问玩家是否想继续视觉小说的第二部分。

To do this, we will set narration.choices to an array of choice options created with newChoiceOption (to jump or call a label) and newCloseChoiceOption (to close the choice menu without navigating anywhere).

你可以在这里找到有关选择菜单的更多信息。

这是示例:

file_type_ink
ink/start.ink
=== start ===
// ...

You want continue to the next part?
* Yes, I want to continue
-> second_part
* No, I want to stop here
-> END

=== second_part ===

// ...
-> DONE

编辑角色信息并将其用作变量

现在我将赋予玩家更改 mc 名字的能力。 为此,我将要求玩家使用 Pixi’VN 的功能填写一个输入框

获取输入值后,你可以使用获得的值来设置角色名称

ink 中,_input_value_ 是一个特殊变量,用于保存玩家输入的值。 你可以在这里阅读更多相关信息。

这是示例:

file_type_ink
ink/start.ink
=== start ===
// ...

He thrusts out his hand.
# request input type string default Peter
What is your name?
# rename mc { _input_value_ }

// ...
-> DONE

现在我们可以在对话中使用角色名称

ink 中,你可以使用方括号(例如 [sly])来利用文本替换机制:默认情况下,模板会检查方括号之间的文本是否与角色的 id 匹配,如果匹配,则用该角色的名称替换它。

在 JS/TS 中,你可以使用带有 ${}模板字面量来实现相同的效果(例如 `${sly.name}`)。

这是示例:

file_type_ink
ink/start.ink
VAR steph_fullname = "Stephanie"

=== start ===
// ...

sly: I just wanted to see what the new guy was like. Hey, you, [mc]- be nice to our little brother, or you'll have to deal with *us*.
mc: ...
james: [mc], this is [sly]. Yes, that is her real name.

I put out my hand.

sly: I'm not shakin' your hand until I decide you're an all-right dude. Sorry, policy.
mc: Fair enough, I'm a pretty scary guy, or so l've been told.
james: The redhead behind her is [steph_fullname].
steph: Hey! Everyone calls me [steph]. I'll shake your hand.

She puts out her hand, and I take it.

mc: Thanks, good to meet you, [steph_fullname].
steph: WOW, that is, like, the most perfect handshake I've ever had! Firm, but also gentle. [sly], you *gotta* shake his hand!

// ...
-> DONE

定义并加载素材

要加载和操作素材(图片、gif、视频……), ,您需要使用 AssetsAssets 是一个功能丰富的类,来自 PixiJS 库,如果你想了解更多信息,请阅读这里

最初的步骤之一是选择在哪里保存你的视觉小说素材:你可以使用在线托管服务,也可以将它们保存在项目本地。 你可以在这里找到有关这两种选项的更多信息。

如果你选择将素材保存在本地,只需将它们放置在 src/assets 中:模板会自动使用 AssetPack 来分析文件,并将它们定义在 src/assets/manifest.gen.json 中。 如果你选择将它们保存在线上,则需要手动编辑 assets/index.ts 来注册它们。

无论哪种情况,每个素材都会被分配一个或多个别名(alias),之后你可以在代码中使用它来引用该素材。

icon.png
manifest.gen.json
index.ts
assets/manifest.gen.json
{
  "bundles": [
    {
      "name": "images",
      "assets": [
        {
          "alias": [
            "images_icon",
            "icon"
          ],
          "src": [
            "images/[email protected]",
            "images/icon-VA-NtA.webp",
            "images/[email protected]",
            "images/icon-E_ZC2g.png"
          ],
          "data": {
            "tags": {}
          }
        }
      ]
    }
  ]
}

添加背景和角色图片

现在也该考虑视觉部分了。 我们将把背景和角色图片添加到视觉小说的画布中。

在我们的案例中,每个角色由 3 个 sprite 组成:身体、眼睛和嘴巴。 我们使用ImageContainer——一个将 Sprite 子元素分组的 Container——将它们组合在一起以构成角色。 你可以在这份文档中找到有关如何添加画布组件的更多信息。

这是示例:

file_type_ink
ink/start.ink
=== start ===
# show image bg bg01-hallway
# show imagecontainer james [m01-body m01-eyes-smile m01-mouth-neutral01] xAlign 0.5 yAlign 1
james: You're my roommate's replacement, huh?
# show imagecontainer james [m01-body m01-eyes-grin m01-mouth-smile01]
james: Don't worry, you don't have much to live up to. Just don't use heroin like the last guy, and you'll be fine!
# show imagecontainer james [m01-body m01-eyes-smile m01-mouth-grin00]
mc: ...

// ...
-> DONE

声音和音乐

要为你的视觉小说添加声音和音乐,你可以使用 sound 工具。

你可以定义声音频道来管理不同类型的声音(例如 BGM、SFX),并控制它们的音量、暂停、恢复等。 建议在游戏开始时定义这些频道,例如在 Game.init 函数的 then 回调中。

在这个示例中,我定义了两个频道:一个用于背景音乐(BGM),一个用于音效(SFX)。 我还将 defaultChannelAlias 设置为 SFX 频道,这样如果我在播放声音时没有指定频道,它将默认在 SFX 频道中播放。 为了定义背景频道,我将 background 属性设置为 true,这样与其他频道不同,声音不会在每个叙事步骤(step)结束时停止,而是会持续播放,直到被暂停或停止。

main.ts
import { Game, sound } from "@drincs/pixi-vn";
import { BGM_CHANNEL_NAME, SFX_CHANNEL_NAME } from "@/constans";

Game.init(body, {
    // ...
}).then(() => {
    sound.channels.add(BGM_CHANNEL_NAME, { background: true });
    sound.channels.add(SFX_CHANNEL_NAME);
    sound.defaultChannelAlias = SFX_CHANNEL_NAME;
});

最后,你可以在叙事节点(labels)中使用 sound.play 函数播放声音和音乐。 你还可以使用频道来管理不同类型的声音(例如 BGM、SFX),并控制它们的音量、暂停、恢复等。

你可以在这里找到有关如何使用它的更多信息。

file_type_ink
ink/start.ink
=== start ===
# show image bg bg01-hallway
# play sound sfx_whoosh delay 0.1
# show imagecontainer james [m01-body m01-eyes-smile m01-mouth-neutral01] xAlign 0.5 yAlign 1 with movein direction right ease circInOut type spring
james: You're my roommate's replacement, huh?
# play sound sfx_whoosh channel bgm loop true
# show imagecontainer james [m01-body m01-eyes-grin m01-mouth-smile01]
james: Don't worry, you don't have much to live up to. Just don't use heroin like the last guy, and you'll be fine!
# show imagecontainer james [m01-body m01-eyes-smile m01-mouth-grin00]
mc: ...

// ...

# pause all sounds
# show imagecontainer steph [fm02-body fm02-eyes-smile fm02-mouth-smile00]
# play sound sfx_whoosh delay 0.1
# remove image james with moveout direction right ease circInOut type spring duration 0.5 delay 0.05
# remove image sly with moveout direction right ease anticipate duration 0.5
# remove image steph with moveout direction left ease easeInOut duration 0.5 delay 0.1

You want continue to the next part?<># continue
* Yes, I want to continue
-> second_part
* No, I want to stop here
-> END

=== second_part ===
# show text bg "(A few minutes later...)" style { fontFamily: "Arial", dropShadow: { alpha: 0.8, angle: 2.1, blur: 4, color: "0x111111", distance: 10, }, fill: "\#ffffff", stroke: { color: "\#004620", width: 12, join: "round" }, fontSize: 60, fontWeight: "lighter" } with fade
# edit text bg align 0.5
# pause

# resume all sounds
# show image bg bg02-dorm align 0 with fade
# play sound sfx_whoosh delay 0.4
// ...
She enters my room before I'VE even had a chance to.

// ...

-> DONE

结语

好了,现在你已经知道如何使用 Pixi’VN 创建视觉小说了。 能力越大,责任越大,所以请明智地使用它,创作一个精彩的故事吧! 🚀

本页内容