Визуальная новелла, интерлюдия: создание разветвленного сценария силами RenPy

На примере создания детской игры с загадками

В закладки

И вновь приветствую всех! Пока продолжение прошлого поста, посвященное основам построения сюжета, еще в процессе написания, я попробую максимально доступно описать методы ветвления сюжета на примере детской игры с загадками (и, возможно, не самой лучшей графикой - не могу же я весь контент показать до релиза?).

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

В комментариях к прошлой записи несколько раз прозвучал вопрос о том, как реализована параллельность сюжета в новелле Spiritual Cavern, которая уже обзавелась собственной группой на Facebook.

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

Спойлер: создание новеллы с графикой и написанием кода заняло 20 минут.

Графика: персонаж и фон

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

События происходят не посреди пустоты, потому нам потребуется фон:

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

Простейший сюжет

Подготовив материалы, мы можем приступить к составлению сюжета - в нашем примере он будет очень простым:

Суть сюжета очень проста: игрок трижды отвечает на вопросы, в зависимости от его ответа меняется настроение кубика. Кроме того, при каждом правильном ответе мы увеличиваем счетчик "Ответы", от которого в последствии зависит концовка игры: если был дан хоть 1

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

Написание кода игры

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

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

Основными конструкциями в RenPy являются:

  • label имя_метки - место в коде, к которому в последствии можно перейти при помощи команды jump имя_метки
  • scene - команда загрузки фонового изображения из папки images созданного проекта. Важно знать: в случае смены фона требуется заново ввести команду по выводу изображения персонажа.
  • show - отобразить картинку (чаще всего - персонажа) поверх существующего фона.
  • say - фраза, произносимая персонажем. Чаще всего используется в следующих форматах:Вариант первый: "автор" "фраза" - здесь мы явно указываем, кто говорит фразу и как эта фраза звучит.Однако, для удобства пользователя есть возможность упростить свою жизнь, заранее указав имя автора.Для этого перед меткой начала игры применяется следующая конструкция:define anna = Character("Anna:")В дальнейшем достаточно написать anna "фраза", что будет воспринято системой как "Anna:" "фраза"Важно понимать, что нет прямой связи между показываемой картинкой (show) и автором фразы - кроме той, которая формируется у игрока в процессе игры.
  • menu - выбор игрока, в зависимости от которого происходит выполнение соответствующего кода, будь то простая фраза либо переход к конкретной метке.

Итак, что же происходит в первом фрагменте кода?

1) мы выводим на экран сцену "bg 1" из папки images нашего проекта
2) от имени игрока (define char = Character("Я:") перед label start) выводим фразу "Интересно, где же кубик?"
3) плавно (with dissolve) выводим на экран изображение "cube wow"
4) от имени кубика (строка define e = Character("Кубик:") перед label start) выводим фразу "А вот он я!"
5) после обмена фразами мы переходим к новой для нас команде - menu, отвечающей за выбор игрока и реакцию игры на этот выбор.

Ничего сложного, согласитесь? Требуется лишь следовать правилам составления сцены и корректно вводить команды.

У тех, кто был внимателен при прочтении нашего "сценария", может возникнуть вопрос: а как нам определить, хорошо или плохо заканчивается игра? Всё верно, при помощи команды menu мы можемпредоставить выбор игроку, а в данной ситуации требуется противоположное - сделать выбор на основе данных самой программы.
С этой целью мы добавляем в игру типичный для программ элемент -переменную под названием "answers" и устанавливаем её значение равным нулю.

В дальнейшем, при выборе игроком положительного (правильного) ответа, мы увеличиваем значение этой переменной следующим образом:

Таким образом, после трех вопросов её значение находится в диапазоне от 0 (если не было дано ни одного правильного ответа) до 3 (если все ответы были правильными).
Будем добрыми, и поставим простое условие: отгадал хоть одну загадку - добро пожаловать на прогулку. Для этого нам потребуется конструкция if-else, "если-иначе". Ниже - пример её применения в нашей новелле:

Если больше 1 правильного ответа - переходим к метке good ending; иначе - к метке sad_ending
Обратите внимание - каждая из меток заканчивается переходом к метке "конец истории". В противном случае возможен последовательный показ обеих сцен, что будет выглядеть.. довольно странно.

Итоги

Одна из двух возможных концовок.

Итак, мы создали новеллу, в которой присутствует как локальная (не влияющая на концовку), так и глобальная (определяющая финал истории) вариативность.

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

И на Android устройствах:

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

К примеру, в Spiritual Cavern на текущий момент присутствует порядка 1200 строк кода, реализующих первые шесть сцен игры - и, если бы не грамотная организация процесса, я давно потерялся бы в этих дебрях.

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

#инди #опыт #практика #новелла

{ "author_name": "Богдан Бортаков", "author_type": "self", "tags": ["\u0438\u043d\u0434\u0438","\u043e\u043f\u044b\u0442","\u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0430","\u043d\u043e\u0432\u0435\u043b\u043b\u0430"], "comments": 12, "likes": 29, "favorites": 1, "is_advertisement": false, "subsite_label": "indie", "id": 13888, "is_wide": false }
{ "id": 13888, "author_id": 39163, "diff_limit": 1000, "urls": {"diff":"\/comments\/13888\/get","add":"\/comments\/13888\/add","edit":"\/comments\/edit","remove":"\/admin\/comments\/remove","pin":"\/admin\/comments\/pin","get4edit":"\/comments\/get4edit","complain":"\/comments\/complain","load_more":"\/comments\/loading\/13888"}, "attach_limit": 2, "max_comment_text_length": 5000 }

12 комментариев 12 комм.

Популярные

По порядку

Написать комментарий...

Александр Тим

1

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

Ответить

Богдан Бортаков

Александр
1

Я веду таблицу с сюжетными ветками для каждого "сценария" - сцены от начала и до концовки: название метки в RenPy, из какой метки происходит переход, суть метки и название файла с фоном (опционально, но мне так удобнее).
В коде оставляю комментарии, чтобы открыв код после выходных не потеряться в бездне сцен.
Ну и правило: код после каждой метки выполняет переход к другой/другим меткам.
В итоге получается довольно удобная система)

Ответить

Александр Тим

Богдан
0

Спасибо, нужно нечто подобное реализовать под свой проект.

Ответить

Αнатолий Εрмаков

1

Спасибо за статью! Сел и за полтора часа собрал простенькую демку с музыкой, переходами по меткам и меню. Сяду изучать вплотную и творить!

Ответить

Богдан Бортаков

Αнатолий
0

Рад, что статья оказалась полезной) Удачи в Вашем начинании!

Ответить

Nick Ilnicky

0

Сначала перед тем как пилить новеллу на renpy, скиньте людям хотя-бы базовую обучалко по питону

Ответить

Богдан Бортаков

Nick
1

RenPy настолько профессионально скрывает аспекты создания кода, что для создания простых новелл достаточно прочитать Quick Start Guide на их сайте: https://www.renpy.org/doc/html/quickstart.html
Кроме того, обучающих материалов конкретно по основам создания новелл в RenPy более чем достаточно. Зачем плодить сущности?
Это только моё ИМХО, прошу заметить.

Ответить

Комментарий удален

Комментарий удален

Комментарий удален

Комментарий удален

Витя Буторин

0

Хм... Есть мини-новелла "Вопрос" и, собственно, обучающая новелла в самой RenPy. Я не говорю, что эта статья плохая, но лучше бы сразу перейти к упомянутым новеллам и документации программы. Они более полные и содержат больше контента, причем, хорошо структурированного.

Ответить

Богдан Бортаков

Витя
0

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

Ответить

Комментарий удален

0

Прямой эфир

Подписаться на push-уведомления
[ { "id": 1, "label": "100%×150_Branding_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox_method": "createAdaptive", "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "ezfl" } } }, { "id": 2, "label": "1200х400", "provider": "adfox", "adaptive": [ "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "ezfn" } } }, { "id": 3, "label": "240х200 _ТГБ_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fizc" } } }, { "id": 4, "label": "240х200_mobile", "provider": "adfox", "adaptive": [ "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "flbq" } } }, { "id": 5, "label": "300x500_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "ezfk" } } }, { "id": 6, "label": "1180х250_Interpool_баннер над комментариями_Desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "h", "ps": "clmf", "p2": "ffyh" } } }, { "id": 7, "label": "Article Footer 100%_desktop_mobile", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fjxb" } } }, { "id": 8, "label": "Fullscreen Desktop", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fjoh" } } }, { "id": 9, "label": "Fullscreen Mobile", "provider": "adfox", "adaptive": [ "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fjog" } } }, { "id": 10, "label": "Native Partner Desktop", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fmyb" } } }, { "id": 11, "label": "Native Partner Mobile", "provider": "adfox", "adaptive": [ "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fmyc" } } }, { "id": 12, "label": "Кнопка в шапке", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fdhx" } } }, { "id": 13, "label": "DM InPage Video PartnerCode", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox_method": "createAdaptive", "adfox": { "ownerId": 228129, "params": { "pp": "h", "ps": "clmf", "p2": "flvn" } } }, { "id": 14, "label": "Yandex context video banner", "provider": "yandex", "yandex": { "block_id": "VI-250597-0", "render_to": "inpage_VI-250597-0-1134314964", "adfox_url": "//ads.adfox.ru/228129/getCode?pp=h&ps=clmf&p2=fpjw&puid1=&puid2=&puid3=&puid4=&puid8=&puid9=&puid10=&puid21=&puid22=&puid31=&puid32=&puid33=&fmt=1&dl={REFERER}&pr=" } }, { "id": 15, "label": "Плашка на главной", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "p1": "byudo", "p2": "ftjf" } } }, { "id": 17, "label": "Stratum Desktop", "provider": "adfox", "adaptive": [ "desktop" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fzvb" } } }, { "id": 18, "label": "Stratum Mobile", "provider": "adfox", "adaptive": [ "tablet", "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fzvc" } } } ]