Создание вашего первого визуального романа
Пошаговое руководство по созданию визуального романа с использованием Pixi’VN, включая настройку проекта, нарратив, ассеты и интерактивность.
Этот туториал проведёт вас через процесс создания вашего первого визуального романа.
Что такое визуальный роман? Визуальный роман (VN) — это форма цифровой интерактивной художественной литературы. Визуальные романы часто ассоциируются с видеоиграми, однако не всегда классифицируются именно как игры. Они объединяют текстовый нарратив со статичными или анимированными иллюстрациями и различной степенью интерактивности. Этот формат также иногда называют «novel game», что является транскрипцией японского термина noberu gēmu (ノベルゲーム), который в Японии используется значительно чаще.
В целях тестирования в этом руководстве мы будем воссоздавать визуальный роман Breakdown с использованием Pixi’VN. Breakdown — это короткая история, содержащая все элементы, которыми должен обладать визуальный роман. Джош Поулисон, создатель Breakdown, дал нам разрешение использовать его повествование в образовательных целях ❤️.
Поскольку Pixi’VN позволяет писать собственный нарратив, выбирая один или несколько доступных нарративных языков, на каждом этапе разработки будут приведены примеры для всех языков, доступных в данный момент.
Создание нового проекта
Первым шагом является создание нового проекта. Более подробную информацию о создании проекта на основе шаблона можно найти здесь. В этом руководстве будет использован шаблон «Visual Novel - React».
Visual Novel -> React
После завершения создания проекта крайне важно ознакомиться с файлом README.md, находящимся в корне проекта. В этом файле содержится важная информация о проекте и способах его использования.
В нашем случае для запуска проекта потребуется лишь выполнить указанные команды:
npm install
npm startСоздание персонажей
Теперь мы определим персонажей этой истории. Для этого в файле /values/characters.ts будут описаны персонажи, которые будут использоваться. Более подробную информацию о создании и использовании персонажей можно найти здесь: Characters
Что означает mc? mc — это распространённое сокращение от «Main Character» (главный персонаж). В визуальных романах принято использовать mc в качестве имени главного персонажа.
import { RegisteredCharacters } from "@drincs/pixi-vn";
import Character from "../models/Character";
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
Все шаблоны поддерживают Markdown и Tailwind CSS, поэтому они будут использоваться для нарратива.
Теперь можно приступить к написанию «первого черновика» нарратива визуального романа.
Мы создадим первый label с именем start, который будет являться началом игры. После этого можно писать dialogues, которые будут развиваться в визуальном романе.
Ниже приведён пример:
=== 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.
Ниже приведён пример:
=== 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Меню выбора
Теперь мы предложим игроку решить, хочет ли он продолжить визуальный роман и перейти к следующей части истории.
Для этого будет использовано меню выбора.
Ниже приведён пример:
=== 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.
После этого станет возможным использование имён персонажей внутри диалогов.
Ниже приведён пример:
=== start ===
// ...
He thrusts out his hand.
# request input type string default Peter
What is your name?
# rename mc { _input_value_ }
// ...
-> DONEТеперь мы можем использовать имена персонажей в диалогах.
Ниже приведён пример:
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Использование функции «glue» в dialogues
В визуальных новеллах часто бывает полезно вставлять текст в текущий dialogue. Например, чтобы приостановить разговор и продолжить его в последующем step. Для этого можно использовать функцию glue.
Ниже приведён пример:
=== start ===
// ...
james: Ooh, [mc]! Nice, firm handshake!
<>The last guy always gave me the dead fish.
<>I already think we're gonna get along fine.
james: Come on in and...
// ...
-> DONEОпределение и загрузка ассетов
Для загрузки и управления ассетами (изображениями, GIF, видео и т. д.) необходимо использовать Assets. Assets — это класс с множеством возможностей, предоставляемый библиотекой PixiJS. Подробнее см. здесь.
Одним из первых шагов является выбор места для хранения ассетов визуальной новеллы. В этом случае ассеты будут сохранены в Firebase Storage (хостинг-сервис). Вы можете использовать любой другой хостинг или сохранить ассеты локально в проекте. Подробнее читайте здесь.
Перед использованием ассетов настоятельно рекомендуется инициализировать матрицу ассетов.
По умолчанию, как видно в файле assets/manifest.ts, все шаблоны в onLoadingLabel пытаются загружать в фоновом режиме «bundle assets» с алиасом, равным текущему id label. Поэтому рекомендуется добавлять в manifest «bundle assets» для каждого label с алиасом, совпадающим с id label, и содержащим изображения, используемые в этом label.
Ниже приведён пример:
import { AssetsManifest } from "@drincs/pixi-vn";
import { MAIN_MENU_ROUTE } from "../constans";
/**
* Manifest for the assets used in the game.
* You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets
*/
const manifest: AssetsManifest = {
bundles: [
// screens
{
name: MAIN_MENU_ROUTE,
assets: [
{
alias: "background_main_menu",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/main-menu.png",
},
],
},
// labels
{
name: "start",
assets: [
{
alias: "bg01-hallway",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/bg01-hallway.webp",
},
],
},
{
name: "second_part",
assets: [
{
alias: "bg02-dorm",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/bg02-dorm.webp",
},
],
},
// characters
{
name: "fm01",
assets: [
{
alias: "fm01-body",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm01/fm01-body.webp",
},
{
alias: "fm01-eyes-grin",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm01/fm01-eyes-grin.webp",
},
{
alias: "fm01-eyes-smile",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm01/fm01-eyes-smile.webp",
},
{
alias: "fm01-eyes-soft",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm01/fm01-eyes-soft.webp",
},
{
alias: "fm01-eyes-upset",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm01/fm01-eyes-upset.webp",
},
{
alias: "fm01-eyes-wow",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm01/fm01-eyes-wow.webp",
},
{
alias: "fm01-mouth-grin00",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm01/fm01-mouth-grin00.webp",
},
{
alias: "fm01-mouth-serious00",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm01/fm01-mouth-serious00.webp",
},
{
alias: "fm01-mouth-serious01",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm01/fm01-mouth-serious01.webp",
},
{
alias: "fm01-mouth-smile00",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm01/fm01-mouth-smile00.webp",
},
{
alias: "fm01-mouth-smile01",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm01/fm01-mouth-smile01.webp",
},
{
alias: "fm01-mouth-soft00",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm01/fm01-mouth-soft00.webp",
},
{
alias: "fm01-mouth-soft01",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm01/fm01-mouth-soft01.webp",
},
{
alias: "fm01-mouth-upset00",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm01/fm01-mouth-upset00.webp",
},
{
alias: "fm01-mouth-upset01",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm01/fm01-mouth-upset01.webp",
},
{
alias: "fm01-mouth-wow01",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm01/fm01-mouth-wow01.webp",
},
],
},
{
name: "fm02",
assets: [
{
alias: "fm02-body",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm02/fm02-body.webp",
},
{
alias: "fm02-eyes-bawl",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm02/fm02-eyes-bawl.webp",
},
{
alias: "fm02-eyes-joy",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm02/fm02-eyes-joy.webp",
},
{
alias: "fm02-eyes-nervous",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm02/fm02-eyes-nervous.webp",
},
{
alias: "fm02-eyes-smile",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm02/fm02-eyes-smile.webp",
},
{
alias: "fm02-eyes-upset",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm02/fm02-eyes-upset.webp",
},
{
alias: "fm02-eyes-wow",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm02/fm02-eyes-wow.webp",
},
{
alias: "fm02-mouth-cry01",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm02/fm02-mouth-cry01.webp",
},
{
alias: "fm02-mouth-nervous00",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm02/fm02-mouth-nervous00.webp",
},
{
alias: "fm02-mouth-nervous01",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm02/fm02-mouth-nervous01.webp",
},
{
alias: "fm02-mouth-smile00",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm02/fm02-mouth-smile00.webp",
},
{
alias: "fm02-mouth-smile01",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm02/fm02-mouth-smile01.webp",
},
{
alias: "fm02-mouth-upset00",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm02/fm02-mouth-upset00.webp",
},
{
alias: "fm02-mouth-upset01",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm02/fm02-mouth-upset01.webp",
},
{
alias: "fm02-mouth-wow01",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm02/fm02-mouth-wow01.webp",
},
],
},
{
name: "m01",
assets: [
{
alias: "m01-body",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-body.webp",
},
{
alias: "m01-eyes-annoy",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-eyes-annoy.webp",
},
{
alias: "m01-eyes-concern",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-eyes-concern.webp",
},
{
alias: "m01-eyes-cry",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-eyes-cry.webp",
},
{
alias: "m01-eyes-grin",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-eyes-grin.webp",
},
{
alias: "m01-eyes-smile",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-eyes-smile.webp",
},
{
alias: "m01-eyes-wow",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-eyes-wow.webp",
},
{
alias: "m01-mouth-annoy00",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-mouth-annoy00.webp",
},
{
alias: "m01-mouth-annoy01",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-mouth-annoy01.webp",
},
{
alias: "m01-mouth-concern00",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-mouth-concern00.webp",
},
{
alias: "m01-mouth-concern01",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-mouth-concern01.webp",
},
{
alias: "m01-mouth-cry00",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-mouth-cry00.webp",
},
{
alias: "m01-mouth-cry01",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-mouth-cry01.webp",
},
{
alias: "m01-mouth-grin00",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-mouth-grin00.webp",
},
{
alias: "m01-mouth-neutral00",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-mouth-neutral00.webp",
},
{
alias: "m01-mouth-neutral01",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-mouth-neutral01.webp",
},
{
alias: "m01-mouth-smile00",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-mouth-smile00.webp",
},
{
alias: "m01-mouth-smile01",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-mouth-smile01.webp",
},
{
alias: "m01-mouth-wow01",
src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-mouth-wow01.webp",
},
],
},
],
};
export default manifest;Добавление фоновых изображений и изображений персонажей
Теперь пришло время подумать и о визуальной части. Мы добавим фоновые изображения и спрайты персонажей на canvas визуальной новеллы.
Что такое спрайт? В компьютерной графике спрайт — это двумерное растровое изображение, встроенное в более крупную сцену, чаще всего используемое в 2D-видеоиграх.
В нашем случае спрайты персонажей состоят из 3 изображений: тела, глаз и рта. Затем мы используем ImageContainer для сборки персонажа. Дополнительную информацию о добавлении canvas-компонентов можно найти в этой документации.
Ниже приведён пример:
=== 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Умная загрузка ассетов
В нашем случае изображения игры сохранены на хостинг-сервисе (Firebase). По этой причине загрузка ассетов не является мгновенной.
Чтобы игрок не ощущал слишком много загрузок, следует группировать их по определённым фазам игры. В этом случае наиболее часто используемые изображения загружаются в начале label.
Подробнее о управлении загрузками можно узнать здесь.
Ниже приведён пример:
=== start ===
# lazyload bundle m01 fm01 fm02
# show image bg bg01-hallway
# show imagecontainer james [m01-body m01-eyes-smile m01-mouth-neutral01] xAlign 0.5 yAlign 1 with movein direction right speed 300
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Использование переходов
Чтобы сделать визуальную новеллу более динамичной, можно использовать переходы при отображении изображений. Подробнее об использовании переходов см. здесь.
Ниже приведён пример:
=== start ===
// ...
# show image bg bg01-hallway
# show imagecontainer james [m01-body m01-eyes-smile m01-mouth-neutral01] xAlign 0.5 yAlign 1 with movein direction right speed 300
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Создание анимации
Чтобы сделать визуальную новеллу более динамичной, можно использовать анимации. Подробнее о работе с анимациями см. здесь.
Рекомендуется использовать TypeScript, если необходимо настраивать множество свойств, так как это обеспечивает больший контроль, больше функциональности и обратную связь типов.
В этом примере анимация убирает steph со сцены и повторно добавляет её на следующем step. Также она отражается по оси X, чтобы персонаж был повернут в правильную сторону.
Для удаления и добавления steph используются функции moveOut и moveIn. Для эффекта зеркального отражения применяется функция canvas.animate.
Сначала используется функция canvas.animate для создания анимации, которая изменяет свойство scaleX от -1 (зеркально) до 1, с параметром autoplay: false, чтобы анимация не запускалась сразу.
Затем используется функция moveIn для перемещения steph на сцену с передачей tickerId анимации, чтобы она могла быть продолжена после завершения перехода.
Опция forceCompleteBeforeNext: true используется для гарантии завершения анимации перед выполнением следующего step. Для moveIn, как для перехода, forceCompleteBeforeNext по умолчанию равно true.
Поскольку эта анимация реализована на TypeScript, для неё был создан отдельный label, чтобы её можно было вызывать и из других языков.
import { canvas, ImageContainer, moveIn, newLabel } from "@drincs/pixi-vn";
export const animation01 = newLabel("animation_01", [
async () => {
let tickerId = canvas.animate<ImageContainer>(
"steph",
{
scaleX: 1,
},
{ autoplay: false, forceCompleteBeforeNext: true }
);
await moveIn(
"steph",
{
value: ["fm02-body", "fm02-eyes-joy", "fm02-mouth-smile01"],
options: { xAlign: 0.8, yAlign: 1, scale: { y: 1, x: -1 }, anchor: 0.5 },
},
{ direction: "right", ease: "easeInOut", tickerIdToResume: tickerId }
);
},
]);ink
Как объясняется здесь, из ink можно вызывать labels, написанные на JS/TS, и наоборот.
Теперь вы можете вызывать этот label (animation_01) из основного label (start).
=== start ===
// ...
# show imagecontainer james [m01-body m01-eyes-grin m01-mouth-grin00]
# show imagecontainer sly [fm01-body fm01-eyes-smile fm01-mouth-smile00]
# show imagecontainer steph [fm02-body fm02-eyes-upset fm02-mouth-nervous00]
# remove image steph with moveout direction left speed 300
[steph_fullname] goes through the opposite door,
# call animation_01
<> and returns with a HUGE tinfoil-covered platter.
// ...
-> DONEЗаключение
Теперь вы знаете, как создать визуальную новеллу с помощью Pixi’VN. С большой силой приходит большая ответственность — используйте её с умом и создайте отличную историю! 🚀
Ниже приведён интерактивный пример с минимальным UI (HTML). Прокрутив страницу вниз, вы увидите тот же результат с использованием полноценного UI (шаблон React).