LogoPixi’VN
Создай свой первый проект

Создайте свою первую визуальную новеллу

Пошаговое руководство по созданию визуальной новеллы с помощью Pixi’VN, охватывающее настройку проекта, повествование, ресурсы и интерактивность.

Это руководство проведёт вас через процесс создания вашей первой визуальной новеллы.

Для целей тестирования в этом руководстве мы воссоздадим визуальную новеллу Breakdown с помощью Pixi’VN. Breakdown — это короткая история, которая обладает всеми особенностями, какие должна иметь визуальная новелла. Джош Поулисон, создатель Breakdown, дал нам разрешение использовать его повествование в образовательных целях❤️.

Поскольку Pixi’VN даёт вам возможность писать собственное повествование, выбирая один или несколько доступных языков повествования, примеры будут приводиться для каждого доступного на данный момент языка на каждом этапе разработки.

Создание нового проекта

Прежде чем начать, убедитесь, что у вас установлены предварительные требования.

Первый шаг — создать новый проект. Сделать это можно, выполнив:

npm create pixi-vn@latest

и выбрав шаблон Visual Novel.

VS Code

Если вы откроете проект в VS Code, вам будет предложено дать согласие на выполнение некоторых рабочих задач (workspace tasks) и установить рекомендуемые расширения для проекта. Настоятельно рекомендуется согласиться и на то, и на другое.

Больше информации о шаблонах, задачах и расширениях можно найти здесь.

Создание персонажей

Теперь мы определим персонажей этой истории. Для этого мы определим в файле /content/characters.ts персонажей, которых будем использовать.

Больше информации о том, как создавать и использовать персонажей, можно найти здесь: Персонажи

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

Все шаблоны поддерживают Markdown и Tailwind CSS, поэтому мы будем использовать их для нашего повествования.

Теперь мы можем начать писать "первый черновик" повествования визуальной новеллы. Мы создадим первый нарративный узел (label) под названием start, который будет началом игры. После этого мы можем написать диалоги, которые последуют далее в нашей визуальной новелле.

Вот пример:

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, видео...) необходимо использовать Assets. Assets — это класс со множеством возможностей, происходящий из библиотеки 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": {}
          }
        }
      ]
    }
  ]
}

Добавление фона и изображений персонажей

Теперь пора подумать и о визуальной части. Мы добавим изображения фона и персонажей на холст (canvas) визуальной новеллы.

В нашем случае каждый персонаж состоит из 3 спрайтов: тела, глаз и рта. Мы используем ImageContainerContainer, который группирует дочерние элементы Sprite, — чтобы объединить их и собрать персонажа. Больше информации о том, как добавлять компоненты холста, можно найти в этой документации.

Вот пример:

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) и контролировать их громкость, паузу, возобновление и т. д. Рекомендуется определять каналы в начале игры, например, в колбэке then функции Game.init.

В этом примере я определяю два канала: один для фоновой музыки (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. Большая сила — это большая ответственность, поэтому используйте её с умом и создайте отличную историю! 🚀

На этой странице