Как написать визуальную новеллу на unity

Пожалуй, как и при написании любого другого текста, самое тяжелое — это первые строки, поэтому вместо них пусть будет лицо одного из героев моего проекта «ТОЛЬКО ОДИН».

Изначально этот сюжет я писал в качестве сценария короткометражки для одного начинающего кинопродюсера. Он сразу сказал, что ему нужна история, которая будет от начала и до конца проходить в одной тёмной комнате, в которой стоит стол, несколько стульев и в кадре должно быть не более 4-х действующих лиц, при этом не важно что будет в этой комнате происходить.

Обожаю подобные вызовы, поэтому спустя пару недель мой оригинальный сценарий был готов. А когда я его опубликовал на специальном сайте для сценаристов, то ко мне обратились ещё двое молодых режиссеров ВГИКА, которым нужен был хороший сценарий для курсовой работы.

Итого, за месяц у меня было 3 предложения от трех киноделов снять эту короткометражку и даже отправить её на фестиваль, но для каждого из них нужны были «небольшие» корректировки сюжета. Как вы поняли, корректировки там были совершенно противоположны слову «небольшие». Они все уводили историю совершенно не в те стороны, которые видел я, и полностью меняя финальный посыл в конце истории. А ведь весь упор сюжета был на необычном сюжетном твисте в самом конце.

Я мог просто продать эту историю любому из них и имел бы в своём портфолио снятую короткометражку, но в какой-то момент внутри меня проснулся упертый творец и я понял, что не хочу этого делать.

Поскольку я уже продолжительное время пишу сценария для одного крупного консольного игрового проекта, то решил, что этот сюжет очень хорошо подойдет для небольшой, довольно линейной, визуальной новеллы для мобильных телефонов — ни средств, ни ресурсов, ни собственно желания для того, чтобы делать более крупный проект, у меня не было, и я сразу решил, что это будет не продолжительная, камерная история с небольшой вариативностью вопросов/ответов и одним единственным вариантом концовки, который я, собственно, и защитил от начинающих киноделов.

Разработка

Я сразу решил, что создавать проект буду на движке Unity — выбор был между ним и UE4, где тоже есть готовые темплейты визуальных новелл, но Unity мне просто больше нравится своей простотой.

В качестве основы я взял супер простой, но очень подходящий для моего проекта VNcreator:

Сюжет у меня уже был, но мне требовались герои и бэкграунды. А поскольку рисую я чуть хуже, чем конь танцует на льду, то я решил схитрить: я строил нужные мне декорации сцены прямо в сцене Unity при помощи лоу=поли ассетов, делал скрин и вставлял его вместо бекграунда.

Тоже самое я сделал и с главными действующими героями проекта — я выставлял виртуальную камеру перед ними так, чтобы они выглядели, как обычные персонажи в визуальных новеллах, а потом обрезал их в фотошопе, оставляя только нужный контур.

В итоге, у меня получилось так:

Не могу сказать, что я много играл в визуальные новеллы, скорее даже вообще в них не играл, но перед началом создания проекта с некоторыми бегло ознакомился, и с самого начала я решил, что от всех остальных виз.новелл мой проект будет отличаться тем, что беграунд у меня будет сменяться практически при каждой новой реплике. Обычно в виз.новелла всегда висит одна картинка на заднем фоне, на которой появляются спрайты говорящих героев.

Я же пошел немного дальше — у меня в игре 128 различных бекграунд-фонов, поскольку в сюжете есть мелкие события, которые влияют на восприятие картинки, например, в сюжете герои передают друг другу монетки, которые лежат перед ними на столе, и когда по сюжету у одного из героев уже 2-3 монетки, то выглядело бы не очень, если бы на бекграунд-картинке их до сих пор было бы по одной у каждого. Поэтому я строил в сцене новые позы героев, новые положения объектов и делал новый бекграунд под каждое отдельное событие.

В итоге, весь сюжет можно не спешно пройти за 20-25 минут, большего я не хотел сам. Ведь это не коммерческий проект, а скорее «проба пера».

Далее, наступил момент, когда нужно публиковать игру.

Публикация

Я пересмотрел кучу видео о том, как публиковать игру в Google Play Markete, сделал всё, как положено — заполнил миллион форм, страниц с настройками, выбрал только русскоговорящие страны, поскольку пока не планирую делать перевод на иностранные языки, и выслал версию на проверку.

Ждать мне пришлось долго — 8 суток игра висела с подписью «На рассмотрении…», и только на девятые сутки я понял, что гугл хочет, чтобы я дозаполнил еще полмиллиона каких-то ранее не доступных форм и табличек.

Я всё заполнил и вуаля, игра опубликована:

Как я уже сказал, проходится всего минут за 20-25, поэтому буду рад, если вы опробуете и выскажите своё мнение о сюжете) Особенно, о сюжетном твисте в конце и мыслях, которые после него появляются)

Привет, Хабр!

В данном цикле статей мы расскажем, как группа студентов создавала свой игровой движок для визуальных новелл, используя Unity. Звучит не оптимизировано, но почему бы не попробовать?

Данная статья является вступительной, поэтому кода здесь не будет.

Введение

Мы группа студентов, состоящая из трёх человек: 2 Unity-разработчика и 1 дизайнер. Наше название: Aristocat Games Studio.

У нас было 2 разработчика Unity, 1 дизайнер и куча свободного времени.

У нас было 2 разработчика Unity, 1 дизайнер и куча свободного времени.

Всё начинается с идеи. Наша идея была в том, чтобы создать движок для визуальных новелл, причём были бы следующие отличительные черты:

  1. Движок может быть встроен в структуру сайта, чтобы пользователи могли создавать свои игры прямо на сайте. Таким образом, мы должны иметь полный контроль над кодом движка.

  2. Визуальное программирование. Даже простой коддинг, как на RenPy, может отпугивать людей от создания игр. Поэтому создание новеллы на нашем движке должно быть основано на нодах в графе, подобно системе Blueprint в Unreal Engine 4.

Посмотрев на структуру нашей команды, мы подумали: сможем ли мы применить наши знания Unity для данной задачи? Сможем ли мы написать игровой движок, используя при этом другой игровой движок?
Перед началом разработки нужно продумать, что мы хотим в итоге.

Требования к движку

Мы понимаем, что по функционалу очень тяжело догнать уже готовые движки такие как: RenPy, Tyrano Builder, Visual Novel Maker. Однако мы можем выделить базовый функционал движка, чтобы пользователи могли его уже использовать и давать свой отзыв.

Вот мы приходим к определению основного функционала нашего будущего движка:

1. Кроссплатформенность движка

Редактор и готовая игра должны быть кроссплатформенными, чтобы их можно было запускать даже на сайте.

Для решения данной проблемы очень помогает базовый функционал Unity. Если и редактор, и готовая новелла будут отдельными проектами Unity, то их можно запускать везде, где Unity это позволяет.

2. Визуальное программирование с помощью графов

Ради визуального программирования мы вводим 3 понятия: граф, ноды, переходы.

Граф

Тут просто. Граф — это место, где будут хранится ноды и переходы.

Также у каждого графа обязательно должна быть нода точки входа (об этом далее).

Ноды

Ноды — это блоки, которые составляют граф визуальной новеллы. Они должны отвечать за логику игры. Если игрок проходит через ноду, то нода выполняет какое-либо действие.

Обязательно будут такие ноды:

  1. Нода точки входа. Данная нода ничего не делает. Просто показывает программе, откуда нужно начинать проход графа.

  2. Нода персонажа. Должна уметь показывать/скрывать персонажа. Изменять его спрайт на экране.

  3. Нода текста. Позволяет изменять текст в окне снизу.

  4. Нода звука. Позволяет проигрывать различные аудио.

  5. Нода заднего фона. Позволяет выбирать спрайт для заднего фона.

Переходы

Ноды будут соединяться с помощью переходов, которые будут отвечать за условия передвижения игрока от одной ноды к другой. Данные условия перехода могут зависеть от переменных, меняющих значения во время новеллы (об этом позже). Обязательно должно быть 2 типа переходов:

  1. Простой переход. Мгновенный переход без условий. Если на пути игрока встречаются несколько мгновенных переходов, то желательно пройти через них в одном кадре (чтобы игрок не заметил, как моргают спрайты при их добавлении/скрытии).

  2. Клик-переход. Переход срабатывает, если пользователь нажал на экран. В визуальных новеллах это основной способ, чтобы перейти к следующему действию игры.

Пример того, как структурно может выглядеть финальный граф проекта:

Пример простой визуальной новеллы

Пример простой визуальной новеллы

Здесь новелла начинается с того, что нас встречает персонаж Anna на фоне заката (Sunset) и говорит: “Егор. Покажи себя!”. После нажатия игрока мы узнаём, сбежал ли Egor? Если сбежал, то Anna скажет: “Ох, нет Егора :c”. Иначе Egor появляется на экране.

3. Разделение графа на граф проекта и граф сцены

Визуальное программирование выглядит плохо, если проект большой. Никто не хочет тратить драгоценные минуты, чтобы найти в километровом графе нужную ноду.

Пример того, что было бы, если весь проект нужно было поместить на один граф (данное руководство содержит граф игры VA-11 HALL-A, который сгенерировал @CyberShadow:

Steam Community :: Guide :: Complete Script Flowchart: ALL Decisions, Branches, and Outcomes

steamcommunity.com

Для решения этой проблемы было добавлено разделение графов на граф проекта и граф сцены.

Граф проекта

Данный граф может иметь только 2 вида нод:

  1. Нода входной точки. Без неё никуда.

  2. Нода запуска сцены. Нода, которая переводит игрока на граф сцены.

Граф сцены

Здесь происходит основной функционал игры, описанный выше.

Если в графе сцены мы попадаем в ноду, у которой нет выходных переходов, то это означает конец сцены. Конец сцены означает, что мы снова переходим к графу проекта и движемся дальше по нему.

Если в графе проекта у ноды нет выходных переходов, то игра считается завершённой.

4. Хранилище ассетов и их обёрток

Должно быть определенное хранилище ассетов (спрайты, аудио, видео и т.д.), используемых в проекте. Подробнее о способе хранения ассетов мы поговорим позже, однако сейчас будем считать, что все ассеты лежат в абстрактной директории ProjectDirectory таким образом:

Заметим, что у некоторых ассетов есть дополнительные файлы с названием вида {название_ассета}.json. Это сериализованные обёртки вокруг ассетов. Они могут хранить дополнительную информацию о том, как нужно использовать ассет. Например, у спрайтов можно выбрать pivot (начало координат), у аудио можно выбрать стандартную громкость и т.д.

Также мы должны хранить другие абстракции, например, персонажа. Проще сразу задать персонажу все спрайты, имя, цвет и т.д., нежели вручную двигать отдельные элементы на сцене. Таким ассетам обёртки не нужны. В примере выше мы можем заметить персонажей Anna и Egor.

5. Система переменных

Должна быть простая система рантайм переменных примитивных типов: int, float, boolean, string.

Значения этих переменных можно использовать на переходах, как дополнительные условия. В примере выше можно заметить переменную boolean EgorRanAway, которая проверяется на переходе Click Transition.

Значения этих переменных должны редактироваться с помощью соответствующих нод, о которых мы ещё не говорили и сейчас не будем.

Пару примечаний:

  1. Если мы попадаем в ноду, у которой не выполняются условия выходных переходов, то мы считаем это концом сцены. На картинке ниже Some Node 1 будет являться концом сцены или концом проекта (в зависимости от того, в каком графе находятся эти ноды).

После Some Node 1 условие перехода не выполняется

После Some Node 1 условие перехода не выполняется
  1. Если мы попадаем в ноду, у которой выполняются сразу несколько условий переходов, то нужно взять в приоритет ту ноду, которая находится выше в редакторе (будто условия проходят сверху вниз). Также стоит оповестить пользователя о возможной проблеме.
    Возможно стоит ввести более явную систему приоритетов, но пока выбранный вариант не нагружает UI, хоть и является не совсем интуитивно понятным.

Произойдёт переход к Some Node 2, а не к Some Node 3

Произойдёт переход к Some Node 2, а не к Some Node 3

6. Локализация

Мы должны заранее подготовиться к тому, что везде в движке вместо текста должны быть ключи. В зависимости от выбранной локализации, данные ключи преобразуются в текст на родном языке. У нас в задачах стоит как можно меньше показывать данные ключи создателю визуальной новеллы, и точно не показывать конечному пользователю.

Сами словари должны хранится в ресурсах игры.

Базовое строение проекта

Когда мы определились с главным функционалом движка, мы можем начинать продумывать архитектуру движка.

Было решено сделать 2 программы: редактор и интерпретатор игры.

Редактор будет иметь всю систему создания новеллы. Из него можно будет экспортировать сериализованный json файл игры, который можно будет позже десериализовывать в интерпретаторе игры. Также нужно понимать, что вместе с сериализованным файлом игры как-то должны поставляться все требуемые ассеты.

Также мы ранее сказали, что должна быть возможность запускать движок (как и редактор, так и интерпретатор) на сайте. Для этого придётся разделить движок на 2 версии: автономная (офлайновая) и web. Самая большая разница между этими двумя версиями — это способ поставки ассетов.

Автономная версия

Автономная версия представляется двумя исполняемыми файлами (редактор и интерпретатор), расположенными на устройстве пользователя.

Здесь у нас есть доступ к файловой системе, поэтому мы можем легко использовать уже знакомую структуру расположения ассетов:

Если пользователь работает с редактором, то он может указать путь к исходникам проекта (путь к ProjectDirectory). Эта директория может быть где угодно на диске (если позволяют права конечно).

Если пользователь уже играет в игру через интерпретатор, то нужно позаботиться о том, чтобы файлы выше лежали там, где интерпретатор посмотрит в первую очередь. Пусть это будет переменная Unity Application.dataPath. Т.е. рядом с исполняемым файлом интерпретатора.

Web версия

Боже, если бы я что-то знал про web…
С Web версией ситуация немного другая. Здесь у нас нет прямого доступа к диску. Однако есть indexedDB, которая может напоминать файловую систему. Но и тут стоит понимать, что IndexedDB имеет ограничение по памяти, поэтому просто загрузить туда все ассеты кажется плохой идеей.

Решением будет хранить ассеты на стороне сервера. Если в игре понадобился определённый ассет, то нужно сделать запрос на сервер и подождать, пока ассет в ответе не дойдёт до нас. Этот ассет сохранится на куче, предоставленной Unity (а именно в объекте WebAssembly.Memory). Здесь тоже нужно быть аккуратным, т.к. куча в Web версии имеет строгий лимит. Подробнее о памяти в Unity WebGL можно почитать здесь.

Чтобы не делать +100500 запросов на каждый отдельный ассет, то можно в начале любой сцены составлять список ассетов, которые нам понадобятся и делать 1 запрос. Или в запросе мы можем передавать только имя сцены, а сервер сам найдёт нужные ресурсы. После каждой сцены нужно очищать ассеты, которые нам больше не понадобятся.

Примерный вид взаимодействия web-интерпретатора с сервером.

Примерный вид взаимодействия web-интерпретатора с сервером.

Если мы говорим о редакторе, то пользователю должна быть видна библиотека ссылок ассетов, расположенных на сервере. Т.е. пользователь видит какие ассеты доступны, но не хранит их у себя на устройстве.

Загрузка новых ассетов производится POST-запросом на хранилище ресурсов новеллы.

Если пользователь хочет загрузить новую версию новеллы, то он сериализует её на своей стороне, а потом отправляет готовый json-файл игры на сервер.

Примерный вид взаимодействия web-редактора с сервером.

Примерный вид взаимодействия web-редактора с сервером.

Создание класса новеллы

Пора создавать абстрактную часть проекта.

Было решено, что самое просто — это использовать один и тот же класс новеллы и для редактора, и для интерпретатора.

Я не смог в UML. Приблизительно такая структура должна описывать всю новеллу (стрелки означают не наследование, а наличие поля в классе):

Теперь подробнее о классах:

  1. Novel. Собственно главный класс нашей новеллы. Этот класс мы должны сериализовывать и десериализовывать.

  2. NovelConfiguration. Здесь будет хранится информация о всей новелле. Например, название новеллы и её версия.

  3. UISettings. Данный класс должен содержать какие-нибудь стили UI новеллы. На данном этапе разработки о кастомизации UI мы ещё не думали, но пусть класс будет.

  4. NovelGraph. Это и есть граф проекта.

  5. NovelGlobalData. Содержит ConstantData и RuntimeData:

    1. ConstantData. Тут лежат все сцены проекта (вместе с их графами), персонажи, другие обёртки вокруг ресурсов (они содержат относительный путь к ресурсам).

    2. RuntimeData. Здесь будет хранится словарь runtime переменных с их default значениями.

Маленькая оптимизация релизной сборки

Мы сказали ранее, что будем использовать класс Novel и для редактора, и для интерпретатора. Однако здесь мы можем заговорить о маленькой оптимизации сборки для интерпретатора.

Для редактора важно сохранять каждый нод, расположенный в редакторе. Рассмотрим на примере:

Здесь мы можем заметить, что ноды 4 и 5 не соединены с Entry Node. Значит, их бесполезно сохранять для релизной сборки. Собственно так и делаем, это может спасти нам пару байт:

И ещё одна оптимизация. Для каждой ноды мы сохраняем её позицию в редакторе. Для релизной сборки этого не нужно делать. Вот ещё пару байтов у нас в кармане.

Базовое строение проекта в Unity

Хоть мы и будем создавать 2 разных исполняемых файла, легче всего вести разработку в одном проекте Unity. Собственно в проекте Unity у нас будут 2 сцены: сцена редактора и сцена интерпретатора игры.

Если мы хотим скомпилировать редактор, то в настройках билда Unity включаем 2 сцены. У нас должна быть возможность играть в игру во время того, как мы её редактируем.

Сцены редактора и интерпретатора включены

Сцены редактора и интерпретатора включены

Если мы хотим скомпилировать интерпретатор, то достаточно в настройках билда выбрать лишь сцену интерпретатора. Редактор в данном случае нам не нужен.

Включена только сцена сцены

Включена только сцена сцены

Заключение

Теперь у нас есть проект Unity, а также базовые классы, с которыми можно начинать разработку. Последующие этапы разработки будут отталкиваться от информации, предоставленной здесь.

На момент написания статьи движок находится на этапе разработки автономной версии. В следующей статье планируется расписать взаимодействие менеджеров в проекте.

Если есть какие-то вопросы или предложения, то пишите в комментариях. Фидбек очень важен.

Ever tried to develop a game with a deep, branching story and choice based narrative? Ever felt like managing those branches and choices can become overwhelming very quickly? Well, so did I. That’s why I created this tutorial series, to show you some great tools and techniques I found when working on my own game. By the end of it, you should have a solid skeleton of your visual novel and will be able to focus on what matters the most — the story.

Ink

If you like narrative games you might have heard of Inkle Studios. They released Sorcery, 80 days and, most recently, Pendragon. You haven’t? I recommend you check them out.

What’s interesting is that their games are developed with ink, a scripting language those guys made to help them develop text games more easily. The best part is, it’s open source and free to use!

Ink allows you to write stories with branching narrative, choices, and dialogues very easily. It comes with Inky — an editor, where you can write stories using Ink, debug them and run in the same window to test your choices etc. There is also Inklewriter, which offers a simple interface to let you create those stories without needing to get deep into the scripting magic. To be honest, I haven’t used it. If your only goal is to write and share some interactive fiction, you might want to give it a go. However, if you’re planning to build an actual story driven game with Unity, read on.

Working with Ink

Inkle offers an extremely comprehensive documentation with examples of how to use Ink and all of its features. There is also an example game where they show those features in action. However, if you’re like me, you may find it a little bit daunting to go through all that text and try to make sense of it. That’s why I prepared this small example to show you the basics. Truth to be told, when making my own game, these basics covered 90% of what I needed.

If you want to follow along, download Inky, and let’s get started!

Knots

In Ink, the story is divided into knots. Think of them as scenes within your story. Each scene can have a dialogue, a paragraph of text, some choices, etc. It’s up to you how you want to structure your story. What I found works best is to break into another knot when we get to a choice, a scene change, or when there is a context change (like the player needing to perform an action). How do knots look, you ask? Very good question, you mark them like so:

=== knot_title ===
Here goes knot content

Simple as that!

Now, I said that a story is built from multiple knots so you will need a way to move between them. You do it like so:

-> knot_title

This is also used to tell ink, which knot is the entry point for your story, so make sure you add this line at the top of your script (with the correct title). This is very important, as ink wouldn’t know where to start otherwise!

Now, let’s see an example script. Please feel free to paste the following into your Inky to see it in action.

-> start_knot // this tells ink where to start the story

=== start_knot ===
Hello from the start knot!
Now we'll go to knot 2!
-> knot_2

=== knot_2 ===
Hello from knot 2!
-> END // this marks the end of the story

Enter fullscreen mode

Exit fullscreen mode

You’ll see knot_2 ends with -> END line. This is to mark where the story ends. You want to make sure all of your knots have an ending, whether it’s moving into the next knot or ending the story (Inky will remind you of that too).

Okay, so let’s get to that exciting bit now, the choices. In your game, you’re very likely to want to give a player a decision to make. Depending on what they choose, we want to branch off into a different knot. How is that done then?

*** Choice 1
[Continuation for choice 1]

*** Choice 2
[Continuation for choice 2]

Each choice is marked with *** and what follows will only be visible after the player chooses that option. Let’s add that into our example:

-> start_knot // this tells ink where to start the story

=== start_knot ===
Hello from the start knot!
Now we'll go to knot 2!

-> knot_2

=== knot_2 ===
Hello from knot 2!
Time for a personality test.
Red pill or blue pill?
*** Red pill
My god, how brave!
-> END // this marks the end of the story
*** Blue pill
Bold move, my friend
-> END // this marks the end of the story

Enter fullscreen mode

Exit fullscreen mode

Now, you see each choice has its own continuation and we’ve performed our first branching! You can test your story in Inky, on the right hand side section of the window.

You can imagine it can get quite difficult to read and (and write) if you add a lot of text for each choice. This is why, as mentioned before, I like to split into further knots after each decision to make it easier to handle. Let’s change our example:

-> start_knot // this tells ink where to start the story

=== start_knot ===
Hello from the start knot!
Now we'll go to knot 2!
-> knot_2

=== knot_2 ===
Hello from knot 2!
Time for a personality test.
Red pill or blue pill?
*** Red pill
-> red_pill
*** Blue pill
-> blue_pill

=== red_pill ===
My god, how brave!
-> END

=== blue_pill
Bold move, my friend
-> END

Enter fullscreen mode

Exit fullscreen mode

Now you see that each option has its own knot. From the player’s point of view, nothing has changed. However, doing things this way will prove very useful when localizing your game (you never know when you might need that!) and even just to make it more readable and easy to work with.

Choices matter

We’ve learned how to branch our story based on different choices, but in many cases, you’ll still want to share some same parts of the story afterward. Otherwise, it could get out of hand very quickly. If you want to do that, you just create a new knot and point into it from your choice specific knots.

-> start_knot

=== start_knot ===
Hello from the start knot!
Now we'll go to knot 2!
-> knot_2

=== knot_2 ===
Hello from knot 2!
Time for a personality test.
Red pill or blue pill?
*** Red pill
-> red_pill
*** Blue pill
-> blue_pill

=== red_pill ===
My god, how brave!
-> continue_conversation

=== blue_pill
Bold move, my friend
-> continue_conversation

=== continue_conversation
Alright. You have answered my question.
-> END

Enter fullscreen mode

Exit fullscreen mode

Simple as that! These choices are meant to matter though. For example, what if we want to make a comment based on what the player has done? The most simple way is to check if we’ve visited a specific knot or not (hehe!). This is how to make that check:

{ knot_name: [further story content] }

Do you want to check if we’ve NOT visited a knot?

{ not knot_name: [further story content] }

By using this pattern, you can check for many different conditions and define the flow of the story accordingly. Let’s see it in action:

-> start_knot

=== start_knot ===
Hello from the start knot!
Now we'll go to knot 2!
-> knot_2

=== knot_2 ===
Hello from knot 2!
Time for a personality test.
Red pill or blue pill?
*** Red pill
-> red_pill
*** Blue pill
-> blue_pill

=== red_pill ===
My god, how brave!
-> continue_conversation

=== blue_pill
Bold move, my friend
-> continue_conversation

=== continue_conversation
Alright. You have answered my question.
{ red_pill:
You chose the red pill. But I'm still not sure I can trust you
}
{ not red_pill: -> no_red_pill_comment}
->END

=== no_red_pill_comment ===
You didn't choose the red pill. I'm not sure I can trust you.
-> END

Enter fullscreen mode

Exit fullscreen mode

You’ll notice I’ve used both approaches of typing the text directly after the check and of pointing to another knot. It’s up to you how you want to structure your file and what’s more convenient for you. Just bear in mind using knots can be a huge help later on.

Variables and conditions

If you’re familiar with programming, you might have thought the way to check if we’ve seen a knot is similar to ‘if-statements’. That’s exactly what it is and it can be used to check various different conditions! Before we do that though, let’s talk about variables.

In your story, you might want to keep track of some things like player name, health points, relationship levels between people, experience points, etc. You can do that in ink by using variables. Some example ones can look like so:

VAR PlayerName = “John”

VAR ChoseRedPill = false

VAR HealthPoints = 50

As you can see, we can have various types of variables. They can be numbers, text, or booleans (true/false values). After defining a variable, you can access it anywhere in your story, whether it’s to display it, update it or use it to make another decision. Let’s add those variables into our game.

// Global variables
VAR PlayerName = "John"
VAR ChoseRedPill = false
VAR HealthPoints = 50

-> start_knot

=== start_knot ===
Hello, {PlayerName}! This is the starting knot! // display variable value
Now, we'll go to knot 2!
-> knot_2

=== knot_2 ===
Hello from knot 2!
Time for a personality test.
Red pill or blue pill?
*** Red pill
~ChoseRedPill = true // update variable value
-> red_pill
*** Blue pill
~HealthPoints -= 20 // update variable value
-> blue_pill

=== red_pill ===
My god, how brave!
-> continue_conversation

=== blue_pill
Bold move, my friend
-> continue_conversation

=== continue_conversation
{ HealthPoints < 50: You seem quite weak. I wonder why...}
Alright. You have answered my question.
{ ChoseRedPill:
You chose the red pill. But I'm still not sure I can trust you
}
{not red_pill: -> no_red_pill_comment}
->END

=== no_red_pill_comment ===
You didn't choose the red pill. I'm not sure I can trust you.
-> END

Enter fullscreen mode

Exit fullscreen mode

As you can see, to display a variable within the text, use ~VariableName

To update a variable, use ~VariableName = [new value]. In case of numbers you can use += or -= to increase or decrease the value by a certain amount (e.g. ~HealthPoints -= 30).

To check for a condition using variables, follow the same pattern as before, { condition : result }. For example:

{ ChoseRedPill:
You chose the red pill. But I'm still not sure I can trust you
}

Enter fullscreen mode

Exit fullscreen mode

Or

{ HealthPoints < 50: You seem quite weak. I wonder why...}

Enter fullscreen mode

Exit fullscreen mode

This will make the line appear only when HealthPoints value is lower than 50.

Wrapping up

That’s it for the basics! There is a lot more to cover in regards to Ink, but I’m confident you can start writing great interactive stories or prototypes of your games with just the methods we’ve covered here. However, if you’re interested in learning more Ink magic, check out the official documentation. It’s amazing how much you can do with it!

Next time, we’ll see how to connect Ink to our Unity project.

Happy coding!

More in this series

Making a Visual Novel with Unity 1/5 — Introduction to ink
Making a Visual Novel with Unity 2/5 — Integration with ink (Coming out 1st Dec)
Making a Visual Novel with Unity 3/5 — Characters and emotions (Coming out 8th Dec)
Making a Visual Novel with Unity 4/5 — State handling (Coming out 15th Dec)
Making a Visual Novel with Unity 5/5 — Localisation (Coming out 22nd Dec)

# Начало работы

# Необходимые компоненты

Naninovel – это расширение для игрового движка Unity, поэтому настоятельно рекомендуется изучить основы использования движка перед началом работы с Naninovel.

Указанные ниже главы руководства являются важнейшими:

  • Установка Unity
  • Создание проектов
  • Интерфейс редактора
  • Использование Asset Store
  • Публикация сборок

В случае, если вы не собираетесь создавать какой-либо пользовательский геймплей за пределами Naninovel, можете не углубляться в изучение информации о сценах, так как Naninovel позаботится об этом.

# Основные концепты

Прежде чем использовать Naninovel, давайте пробежимся по некоторым основным концептам.

Важнейшим из них, с которым вы постоянно будете сталкиваться в остальной части руководства, является актор. Актор – это объект, описываемый идентификатором (ID), внешностью, положением в пространстве (в сцене) и некоторыми другими параметрами.

Актор – абстрактный объект, который не может существовать непосредственно; вместо этого используются его специализированные типы с различными дополнительными параметрами:

Тип актора Дополнительные параметры Описание
Персонаж Направление взгляда Представляет персонажа в сцене.
Фон Отсутствуют Представляет фоновое изображение сцены; по умолчанию размещается за акторами персонажей.
Текстовый принтер Текст, ID персонажа, Процесс отображения Постепенно проявляет (печатает) текстовое сообщение в течение некоторого времени.
Обработчик выбора Варианты выбора Позволяет игроку выбрать один из доступных вариантов.

Рассмотрим типичную сцену визуальной новеллы – с персонажем, изображенным поверх фона. В Naninovel она будет представлена следующим образом:

Теперь, предположим, вы хотите, чтобы персонаж «Kohaku» выглядел счастливым. У вас есть несколько текстур (изображений) этого персонажа, изображающих различные эмоции. В Naninovel такие текстуры называются «внешностью» актора. Чтобы получить желаемый результат, нужно изменить внешность актора персонажа. Точно так же для того, чтобы «MainBackground» отображал другой фон, необходимо изменить внешность актора фона.

Акторы и их параметры управляются (направляются) с помощью команд, указанных в скриптах Naninovel.

Ещё одним широко используемым концептом является пользовательский интерфейс (UI). UI используется игроком для взаимодействия с акторами и остальной игрой. Сюда входят различные меню (главное меню, сохранение-загрузка, настройки и т. д.) и панели управления (режим авточтения, пропуск текста и т. д.). Элементы пользовательского интерфейса по умолчанию располагаются поверх акторов.

Текстовые принтеры и обработчики выбора рассматриваются и как акторы, и как элементы пользовательского интерфейса, то есть они имеют качества акторов и могут управляться с помощью скриптов Naninovel, и в то же время используются игроками для взаимодействия с игрой.

Если вы знакомы с программированием, взгляните на архитектуру движка, чтобы понять, как он спроектирован с точки зрения программного обеспечения.

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

Итак, держа основные концепты в уме, начнем первоначальную настройку. Первое, что вам понадобится – проект Unity. Обратитесь к руководству Unity, описывающему создание проекта.

При создании проекта вы, вероятно, захотите использовать шаблон 2D Template, чтобы установить редактор в режим 2D проекта, благодаря чему изображения будут импортироваться по умолчанию как спрайты, и вам не придется вручную изменять настройки импорта. Вы можете изменить режим работы редактора позже, используя настройки проекта.

Когда вы создадите новый проект, Unity автоматически добавит пример сцены с двумя игровыми объектами в ней: «Main Camera» и «Directional Light». Naninovel полностью независим от сцены, поэтому вы можете удалить эти объекты из сцены, чтобы они не создавали ненужной производительной нагрузки. Вы также можете удалить саму сцену, хотя для корректной работы некоторых функций редактора рекомендуется иметь в проекте хотя бы одну сцену.

Загрузите пакет Naninovel с помощью окна Asset Store и дождитесь начальной компиляции скриптов и импорта ассетов. Вы можете свободно перемещать папку ‘Naninovel’ в любое место в директории ассетов вашего проекта, если хотите.

В процессе использования Naninovel в папке ‘Assets/NaninovelData’ будет автоматически сгенерирован ряд ассетов (конфигурация, настройки, сохранения и т.д.). В отличие от папки пакета Naninovel, не стоит вручную перемещать папку данных (она будет автоматически перегенерирована). Если вы хотите изменить её расположение, измените свойство Generated Data Path в меню конфигурации движка.

# Добавление скрипта Naninovel

Воспользуйтесь контекстным меню ассетов Create -> Naninovel -> Naninovel Script для создания ассета скрипта Naninovel.

Создание скрипта Naninovel

ПРИМЕЧАНИЕ

Вы можете создавать и хранить скрипты Naninovel (а также все другие ресурсы Naninovel) в любой папке проекта и организовывать их по своему усмотрению, как и задавать им имена. Приведенная выше иллюстрация является лишь примером.

Скрипты Naninovel – это текстовые документы (с расширением .nani), где вы контролируете происходящее в сцене. Вы можете открывать их и редактировать с помощью любого текстового редактора на ваш выбор, например Notepad, TextEdit или VS Code.

Открытие скрипта Naninovel

Вы также можете использовать визуальный редактор скриптов для редактирования скриптов Naninovel. Выберите созданный скрипт, и вы увидите, что визуальный редактор автоматически откроется в окне инспектора.

Чтобы добавить новую строку в скрипт, щелкните правой кнопкой мыши в месте, куда вы хотите вставить строку, либо нажмите Ctrl+Space (вы можете изменить стандартные комбинации клавиш в меню конфигурации ввода) и выберите нужную строку или тип команды. Чтобы изменить порядок строк, перетащите их, используя их номерные метки. Чтобы удалить строку, нажмите на неё правой кнопкой мыши и выберите «Удалить».

После изменения скрипта с помощью визуального редактора вы увидите звездочку (*) над именем сценария в шапке инспектора. Это означает, что ассет изменён и должен быть сохранен; нажмите Ctrl+S, чтобы сохранить внесённые изменения. Если вы попытаетесь выбрать другой ассет, не сохранив изменения в скрипте, появится диалоговое окно, позволяющее либо сохранить, либо отменить изменения.

Визуальный редактор автоматически синхронизирует редактируемый скрипт при его обновлении извне, так что вы можете легко работать со сценариями как в текстовых, так и в визуальных редакторах.

ПРИМЕЧАНИЕ

В остальной части этого руководства мы будем использовать текстовый редактор, но вы можете повторить все те же шаги в визуальном редакторе, если хотите.

Для того, чтобы ассет Naninovel (например, созданный вами скрипт) стал «видимым» для движка, он должен быть назначен ресурсом проекта. При создании с помощью меню создания ассетов скрипты назначаются автоматически. Чтобы назначить (или отредактировать/удалить) файл скрипта вручную, используйте окно скриптовых ресурсов, доступное в меню редактора Naninovel -> Resources -> Scripts. Чтобы добавить скрипт, нажмите кнопку + (знак плюс) в списке, чтобы добавить новую строку, и перетащите файл скрипта в неё. Кроме того, можно перетащить несколько выделенных ассетов или даже целые папки в список, чтобы добавить их группой.

Добавление скрипта Naninovel

Откройте созданный скрипт в текстовом редакторе и добавьте следующий текст:

Первая строка выведет текст «Hello World!» при запуске игры, а вторая требуется для корректной остановки выполнения скрипта.

Войдите в режим воспроизведения и начните новую игру, чтобы увидеть результат.

ПРИМЕЧАНИЕ

Все доступные встроенные команды скрипта, поддерживаемые параметры и примеры использования приведены в справочнике по API. Кроме того, можно добавить пользовательские команды; дополнительную информацию смотрите в руководстве.

Если кнопка «НОВАЯ ИГРА» в главном меню неактивна, убедитесь, что свойство Start Game Script в настройках скриптов (Naninovel -> Configuration -> Scripts) равно имени созданного скрипта. Свойство заполняется автоматически при создании первого скрипта через меню создания ассетов, но может не сработать, если вы скопируете в проект уже существующий скрипт.

# Добавление персонажа

Персонажи в Naninovel могут быть основаны на обычных и нарезанных спрайтах, анимированных моделях Live2D и 3D-моделях; вы также можете добавить свои собственные варианты реализации. В этом туториале мы будем использовать спрайты.

Каждый персонаж представлен ID и набором вариантов внешности. Чтобы добавить персонажа, используйте графический интерфейс менеджера персонажей, доступный через меню Naninovel -> Resources -> Characters, добавьте новую строку актора персонажа, указав ее ID, затем дважды щелкните по ней (или нажмите кнопку в конце строки) и добавьте все варианты спрайта в список Resources. Как и в случае со скриптами Naninovel, вы можете перетащить в список сразу несколько ассетов и папок.

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

Предположим, что ID созданного персонажа – «Kohaku». Отредактируйте скрипт Naninovel, чтобы показать добавленного персонажа:

Запустите игру, и вы увидите спрайт персонажа в центре экрана. Если вы не укажете внешность, то по умолчанию будет выбрана либо та, имя которой равно ID персонажа, либо внешность с названием «Default». Чтобы выбрать определенную внешность, добавьте её название после ID персонажа, разделив их точкой, как здесь:

Так как персонажу «Kohaku» была выбрана внешность под названием «Happy», соответствующий спрайт будет отображен вместо стандартного.

Теперь вы можете связать выводимый текст с именем персонажа, добавив его ID, а затем двоеточие перед текстом:

Кроме того, можно объединить объявление внешности персонажа с выводом текста, чтобы сократить код:

Чтобы скрыть (удалить из сцены) персонажа (или любой другой актор, например, фон, текстовый принтер и т. д.), используйте команду @hide, за которой следует ID актора:

# Добавление фона

Подобно персонажам, фон в Naninovel может быть представлен несколькими способами: спрайт, простой объект, видео и сцена; также возможны пользовательские реализации.

Вы можете создать несколько независимых акторов фона, хотя в типичной визуальной новелле обычно используется только один, меняется лишь его внешность. Чтобы упростить процедуру, актор MainBackground по умолчанию добавляется в список акторов фона, и вам не нужно указывать его ID каждый раз, чтобы изменить его внешность в скриптах Naninovel.

Добавьте фоновые спрайты через меню Naninovel -> Resources -> Backgrounds. Строка MainBackground откроется автоматически, но вы все равно можете вернуться в список акторов и создать другие, если потребуется.

Добавление фона

Предположим, что добавленная внешность спрайта фона называется «City». Чтобы отобразить фон, используйте команду @back, за которой следует название внешности фона:

При переключении между фонами по умолчанию будет использоваться кроссфейд (эффект перехода). Чтобы сменить эффект, укажите тип перехода после внешности:

Это позволит перейти от фона «City» к фону «School», используя эффект перехода «RadialBlur».

Чтобы обратиться к фону, отличному от основного (например, если вы хотите создать несколько фонов поверх друг друга), укажите ID актора. Например, если фоновый актор с ID «Flower» существует отдельно от основного, следующие команды изменят его внешность на «Bloomed», а затем на «Withered»:

# Добавление музыки и звуковых эффектов

Чтобы добавить ассет BGM (фоновая музыка) или SFX (звуковой эффект), используйте меню редактора Naninovel -> Resources -> Audio. Вы можете использовать любые аудиоформаты, поддерживаемые Unity.

Управление аудио

Допустим, вы добавили BGM трек под названием «ThePromenade». Для воспроизведения этого трека в качестве фоновой музыки используйте команду @bgm, за которой следует название трека:

Эффект кроссфейда будет автоматически применен при смене музыкального трека. Музыка по умолчанию будет зациклена, но вы можете изменить это, как и громкость, и длительность затухания с помощью параметров команды.

Напротив, звуковые эффекты по умолчанию не будут зациклены. Предположим, вы добавили SFX с названием «Explosion» – используйте команду @sfx, чтобы воспроизвести его:

# Видеогайд

Если вы предпочитаете видеогайды, то здесь вы можете найти таковой, иллюстрирующий вышеприведенные инструкции.

# Демо-проект

Полные исходники демо-проекта (того же, что демонстрировался в магазине) доступны на GitHub здесь: github.com/Naninovel/Demo.

Вы можете клонировать репозиторий с помощью клиента Git или скачать его в виде zip-архива. Обратите внимание на то, что ресурсы, распространяемые вместе с демо-проектом, могут быть предметами пользовательских лицензий и предоставляться исключительно в учебных целях.

ВНИМАНИЕ

Пакет Naninovel не распространяется вместе с проектом, а значит, при первом его открытии могут появиться ошибки компиляции; импортируйте Naninovel из Asset Store, чтобы решить эту проблему.

UVNF_Logo

UVNF — Unity Visual Novel Framework

UVNF is a free Unity framework for creating Visual Novels in Unity.
This repository provides a set of examples, tools, workflows and other resources to help you get started on your project.

Several ways of writing your own visual novels without any programming. Using a writer-friendly node based dialogue editor and a more in depth story editor, you can view your story visually without having to dive into a pogramming language or complex settings.


Installation

Clone/Download the repository and use Unity Hub to add the Project folder to your list of existing projects. You can then open the project and it will lead you to a list of settings to customize your framework experience (settings can be changed later on).

Future Plans

  • Creating more story elements
  • Create transitions

Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

Contributers

Velorexe (Project lead/Front-Backend Developer)

Jocelyn (Art/Character Designer)

  • Instagram

Credits

Siccity

  • Xnode

License

MIT

The art that is contributed by artist Jocelyn Hatch is not for commercial use and should be removed from your project when published commercially. When the art is found used by products in a case mentioned above, we kindly ask you either take down the product or remove the content that’s marked as non-commercial use. The commisioned art is only used for creating example scenes, trailers and/or tutorial videos create by Velorexe or other users.

Понравилась статья? Поделить с друзьями:
  • Как написать визитную карточку города
  • Как написать визитку школы
  • Как написать визажисту про макияж свадебный
  • Как написать визажисту про макияж правильно
  • Как написать виджет для сайта