Глава 7. Три мини игры

На основе рассмотренной в первой главе повести мы создадим визуальную новеллу на Ren’Py, Twine и TyranoBuilder под названием "Август возвращается". В процессе создания предпочтение будет предоставлено больше Ren’Py.

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

Составляем план

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

Сначала разложим повесть "Август возвращается" на блоки в виде таблиц. Так же вы можете воспользоваться другим методом визуализации для этих целей. Главное – не пропускать данный этап, во избежание негативных последствий при создании игры.

Состав персонажей

Начнем с персонажей (см. Таблицу 7-1). Если забыли про них, то вернитесь к первой главе книги.

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

Таблица 7-1. Главные герои

ГеройРоль/АрхетипПол и ВозрастВнешний вид
Рон Легион-СмертиГлавный герой (т. е. игрок)Мужчина, около 30 летСреднего телосложения и роста, на вид физически подтянутый
Мервин ПопплуэллИскательМужчина, лет за 30Худой, среднего роста, небрежно одетый
Ройстон Медовая-БабочкаМудрецМужчина, 50 летКрепкое телосложение, имеет обычный вид
КлэрПравительЖенщина, около 20 летСпортивное телосложение, среднего роста, модно одетая
Человек в комбинезонеБунтарьМужчина, без возрастаСпортивное телосложение, высокий, лысый с причудливым видом
РейнЛюбовникЖенщина, около 30 летСреднее телосложение, низкий рост, изящный вид

Локации

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

Примечание:

Игра, создаваемая в этой книге, не соответствует вышеуказанному правилу.

По сравнению с каталогизацией персонажей, определиться с локациями событий в игре сложнее. Слишком малое количество портят атмосферу, а слишком много сбивают с толку игрока. Лучше всего начать с малого, определить основные элементы игры, после чего разделить их на более мелкие единицы (например, комнаты) (см. Таблицу 7-2).

Для построения истории мы воспользуемся такими понятиями как:

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

Таблица 7-2. Основные сеттинги и места повести "Август возвращается"

СеттингМестоПример описание комнаты
ОфисРабочее место РонаВы бездельничаете за рабочим столом, на котором среди стопок бумаг стоит персональный компьютер. Только наклейка с изображением женщины-киборга выделяет ваше рабочее место среди всех остальных.
ОфисПомещение, где храниться сотовый телефонПоднявшись на верхний этаж, вы попадаете в заброшенную часть офиса со старой мебелью.
ГородПарк, АркаНочь, одинокая арка городского парка, статуи освещённые тусклым светом, только усиливают волнения и страх вызванный недавними событиями.
Офис (Пожар)Рабочий стол РонаАд. Рабочее место Рона окутано огнём. Защищаясь портфелем, он ныряет в него.
Город (Жилой район)Квартира РонаКвартира главного героя становится не такой безопасной, когда из-за двери доносятся странные звуки.
Город (Железнодорожный вокзал)Железнодорожный вокзалВы испуганный, лихорадочно просматриваете расписание движения поездов в поиске ближайшего, который готов к отправке.
ПоездПоезд на северСидя в мягком сиденье поезда, женщина с черными волосами напротив вас сняла наушники, улыбнулась и представилась.
Конечная станцияКонечная станцияПытаясь слиться с толпой, вас ловят за руку.
ОстровОстровВы высаживаетесь на острове, для вас это чужой мир. Холод проникает до дрожи костей. Корабль покидает причал, и вы остаетесь один на холодном северном ветре.
ОстровБункерНезаметное снаружи строение на удивление оказалось просторным, комфортным, с отличной мебелью. Это напоминало больше отель нежели бункер.
ОкеанСудноПо правому борту без опознавательных знаков появляется второе судно. Вы начинаете паниковать.
СеверАэропортВы оказываетесь в международном аэропорту, заполненным огромным количеством людей. Возможность раствориться в толпе успокаивает вас.
ГородАэропортВас окружает беззаботная жизнь. Туристы, тянут свой багаж, но вы внутренне готовитесь к битве всей своей жизни.
ГородРезиденция президентаНет времени любоваться городом. По вам открыли огонь, остается только бежать.
Новый офисНовое рабочее место РонаПрежде чем приступить к новым обязанностям, он с тяжестью в душе возвращает старый сотовый телефон в шкаф.

Теперь разделим вышеупомянутые места на более мелкие эпизоды, а именно на комнаты. Давайте сперва это сделаем для сеттинга "Офис" (см. Таблицу 7-3).

Таблица 7-3. Сеттинг "Офис" в повести "Август возвращается"

СеттингМестоКомнатаСвязанные комнаты
ОфисРабочее место РонаРабочий стол РонаЗона с питьевым фонтанчиком, Рабочий стол Ройстона
Зона с питьевым фонтанчикомРабочий стол Рона, Соседний рабочий стол
Соседний рабочий столЗона с питьевым фонтанчиком
Верхний этаж (т. е. место хранение телефона)Рабочий стол РойстонаРабочий стол Рона, Шкаф для документов
Шкаф для документовРабочий стол Ройстона

Сеттинг I. Офис

Как вы, помните, наше приключение начинается с того, что Рон получает загадочное письмо от умершего коллеги, некоего Мервина Попплуэлла.

Замечания по обстановки в Офисе:

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

Сеттинг II. Город

Выполняя инструкцию из загадочного письма, мы идем по улицам города в направлении арки парка (см. Таблицу 7-4).

Таблица 7-4. Сеттинг "Город" в повести "Август возвращается"

СеттингМестоКомнатаСвязанные комнаты
ГородПаркПаркАрка
АркаАркаПарк
УлицаДорога мимо офисаАрка
Увиденный пожарНет

Замечание к дизайну Города:

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

Сеттинг III. Офис (Пожар)

Это один из опасных моментов в приключениях нашего главного героя. Рон решился вернуться в свой офис, но он оказался охвачен огнем (см. Таблицу 7-5). Не имея средств защиты, кроме портфеля, он бросается в жерло пожара.

Таблица 7-5. Сеттинг "Офис (Пожар)" в повести "Август возвращается"

СеттингМестоКомнатаСвязанные комнаты
ГородВход в офис со двораПереулокПожарная лестница
Офис (Пожар)Пожарная лестницаПомещение офиса
Помещение офисаРабочий стол Рона
Рабочий стол РонаСоседний стол А
Соседний стол АРабочий стол Рона, Соседний стол Б
Соседний стол БСоседний стол А

Замечания по обстановке в сеттинге Офис (Пожар):

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

Сеттинг IV. Город (Жилой район)

В этой точке сюжета Рон направляется домой. Но и здесь что-то происходит непонятное. Он слышит, странные звуки из своей квартиры. Рон чувствует интуицией, что если туда войдет, то уже никогда из неё не выйдет (см. Таблицу 7-6).

Таблица 7-6. Сеттинг "Город (Жилой район)" в повести "Август возвращается"

СеттингМестоКомнатаСвязанные комнаты
Город (Жилой район)Квартира РонаУлицаНет
КоридорУлица
Дверь квартира РонаКоридор, Квартира Рона
Квартира РонаНет

Замечание к дизайну Город (Жилой район):

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

Сеттинг V. Город (Железнодорожный вокзал)

Теперь, находиться в городе стало опасно для жизни Рона, его нужно немедленно покинуть, из-за чего он как можно скорей пытается добраться до вокзала. Где Рон садится на первый поезд, идущий на север (см. Таблицу 7-7).

Таблица 7-7. Сеттинг "Город (Железнодорожный вокзал)" в повести "Август возвращается"

СеттингМестоКомнатаСвязанные комнаты
Город (Железнодорожный вокзал)Железнодорожный вокзалСтатуяПлатформа А
Платформа АСтатуя, Платформа Б
Платформа БПоезд
ПоездНет

Замечание к дизайну Город (Железнодорожный вокзал):

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

Сеттинг VI. Поезд

Зайдя в поезд, Рон немного успокаивается. Однако это спокойствие длится недолго, его продолжают преследовать (см. Таблицу 7-8). В результате он запирается в туалете, прячась от преследователей. В этот момент ему позвонили на старый мобильный телефон, и сказали покинуть поезд на ближайшей станции.

Таблица 7-8. Сеттинг "Поезд" в повести "Август возвращается"

СеттингМестоКомнатаСвязанные комнаты
ПоездПоезд на северДвери поездаНет
Первый вагонВторой вагон, Двери поезда
Второй вагонПервый вагон
Туалет в поездеВторой вагон, Третий вагон
Третий вагонНет

Замечание к дизайну сеттинга Поезд:

  • Обстановка в плане драматичности остаётся прежней и усиливается к завершению данного сеттинга.
  • Атмосфера – тихий шум движущегося поезда.

Сеттинг VII. Конечная станция

Рон, не зная, что делать, метается по платформе, пока его не хватает кто-то за руку. Рона начинает трясти, но это оказался коллега из отдела технической поддержки Ройстон Медовая-Бабочка. После встречи они оказываются в парке на скамейке возле станции, где Рону сообщают о деталях происходящего (см. Таблицу 7-9).

Таблица 7-9. Сеттинг "Конечная станция" в повести "Август возвращается"

СеттингМестоКомнатаСвязанные комнаты
Конечная станцияКонечная станцияПлатформа АПлатформа Б
Платформа БПлатформа А, Дорога в парк
Дорога в паркСкамейке в парке
Скамейке в парке

Замечание к дизайну сеттинга Конечная станция:

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

Сеттинг VIII. Остров

Когда Ройстон покинул Рона, ему снова позвонили на телефон. Рейн сообщает ему, что нужно избавиться от телефона, и используя поезд, паром, рыбацкую лодку, прибыть в течении трёх дней на самый дальний остров на севере (см. Таблицу 7-10).

На самом острове Рона встречает погибший Мервин Попплуэлл, и скрывшись в бункере из четырех комнат, за чашкой чая Рону рассказывают, что нужно закончить работу над системой безопасности «Август».

Поскольку команда не может защититься, на помощь, используя вертолет, прилетает Ройстон Медовая-Бабочка.

Таблица 7-10. Сеттинг "Остров" в повести "Август возвращается"

СеттингМестоКомнатаСвязанные комнаты
ОстровОстровБерегПуть к бункеру А
Путь к бункеру АПуть к бункеру Б
Путь к бункеру БВход в бункер
Вход в бункерПервая комната в бункере
БункерПервая комната в бункереВторая комната в бункере, Третья комната в бункере, Четвертая комната в бункере
Вторая комната в бункереПервая комната в бункере, Третья комната в бункере, Четвертая комната в бункере
Третья комната в бункереПервая комната в бункере, Вторая комната в бункере, Четвертая комната в бункере
Четвертая комната в бункереПервая комната в бункере, Вторая комната в бункере, Третья комната в бункере

Замечание к дизайну сеттинга Остров:

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

Сеттинг IX. Океан

Спустя несколько дней к Рону прибывает Рейн и предлагает покинуть бункер, так как в резиденции президента произошло чрезвычайное происшествие. Рон принимает предложение. После чего они покидают остров, погрузившись на судно на воздушной подушке. Хоть это и короткая сцена, но из-за эпизода с нападением на корабль, очень напряженная (см. Таблицу 7-11).

Таблица 7-11. Сеттинг "Остров" в повести "Август возвращается"

СеттингМестоКомнатаСвязанные комнаты
ОкеанСудноПалубаРубка
РубкаПалуба
Спасательный вертолётРубкаПалуба
ПалубаРубка

Замечание к дизайну сеттинга Океан:

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

Сеттинг X. Север

Рон и его друзья покинули остров и высадились на побережье материка, где их уже ждала машина, чтобы отвезти в аэропорт. Используя частный самолёт, они вылетают в столицу. Во время полёта они узнают про ужасные события в резиденции президента (см. Таблицу 7-12).

Таблица 7-12. Сеттинг "Север" в повести "Август возвращается"

СеттингМестоКомнатаСвязанные комнаты
СеверПобережьеПобережьеАвтомобиль в аэропорт
АэропортАвтомобиль в аэропортЗона аэропорта А
Зона аэропорта АЗона аэропорта Б, Автомобиль в аэропорт
Зона аэропорта БСамолет в столицу
Самолет в столицуНет

Замечания к дизайну сеттинга Север:

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

Сеттинг XI. Город

Рон и его друзья, прибыв в столицу, немедленно направляются в резиденцию президента (см. Таблицу 7-13). Где Рон встречает Клер и человека в комбинезоне. Им сообщают, что премьер-министр похищен, и чтобы его освободить Рон должен передать исходный код системы безопасности «Августа».

Рон соглашается с выдвинутыми условиями, но ухитряется обмануть врагов, подсунув им исправную систему безопасности, а спецназ в свою очередь обезвреживает всех сотрудников «Фракции».

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

Таблица 7-13. Сеттинг "Город" в повести "Август возвращается"

СеттингМестоКомнатаСвязанные комнаты
ГородАэропортПрибытиеМашине в резиденцию президента
Машине в резиденцию президентаЛестница
Резиденция президентаЛестницаПарадный вход
Парадный входЗал, Лестница
ЗалПарадный вход
Дом РонаПриемнаяСпальня
СпальняПриемная, Балкон
БалконСпальня

Замечание к дизайну сеттинга Город:

  • Тон письма должен быть сухим, холодным. Он должен показать в действиях главного героя твёрдость и расчетливость. Рон за нескольких недель своих приключений пережил многое, стал мудрее.
  • Что касается звука, находясь в столице — это шум города, а попав в резиденцию президента – это тихая атмосфера. Так же мы должны выделить динамические сцены и сцены, в которых нужно принимать судьбоносные решения.

Сеттинг XII. Новый офис

Рон возвращается на работу. Офис отремонтирован за время его отсутствия, стоят новые рабочие столы и компьютеры (см. Таблицу 7-14).

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

Таблица 7-14. Сеттинг "Новый офис" в повести "Август возвращается"

СеттингМестоКомнатаСвязанные комнаты
Новый офисНовое рабочее место РонаРабочий стол РонаЗона с питьевым фонтанчиком, Рабочий стол Ройстона
Соседний рабочий столНовое рабочее место Рона
Помещение, где храниться сотовый телефонРабочий стол Ройстона (Новое рабочее место Рона)Новое рабочее место Рона, Хранилище документов
Хранилище документовРабочий стол Ройстона

Замечание к дизайну сеттинга Новый офис:

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

Часть I: Начало "Август возвращается" в Ren’Py

Теперь пришло время приступить к написанию кода. "Август возвращается" имеет 61 комнату, приблизительно от 2000 до 2500 слов, и этого достаточно чтобы познакомится с процессом разработки игр в Ren’Py.

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

На Лаунчере Ren’Py нажмите "Добавить новый проект". У вас запросят указать имя проекта на латинице, в нашем случае — это "Avgust Vozvrashaetsya". Затем выбираем разрешение для проекта, по умолчанию это 1920x1080 (Full HD) и нажимаем "Продолжить". На следующем шаге задаём цветовую схему игры. Если возникнет потребность данные параметры можно будет изменить. После нажатия "Продолжить", Ren’Py выполнить процесс создания проекта и вернёт вас на главный экран лаунчера.

В левой части в разделе "Проекты", выберите "Avgust Vozvrashaetsya". Далее в разделе "Редактировать файл" нажмите на файле script.rpy. Он откроется в текстовом редакторе. Это основной скрипт игры. На протяжении данной главы мы в основном будем работать только с ним.

Нужно отметить, что скрипты Ren’Py очень чувствительны к пробелам и синтаксису. Например, некоторые текстовые редакторы выполняют авто форматирование и заменяют обычные кавычки на изогнутые. Это приводит к тому, что Ren’Py не может выполнить скрипт. Желательно отключите авто форматирование в вашем текстовом редакторе или используйте IDE (интегрированную среду разработки).

Настройка персонажей

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

Для цвета в Ren’Py используется шестнадцатеричный формат, как и в HTML. В котором первые две цифры это красный цвет, следующие две – зелёный, а последние две – синий. Диапазон значений состоит от 0 до 9, потом от A до F. Например, красный цвет имеет значение: #FF0000, а черный: #000000.

Откройте файл script.rpy и найдите следующую строку:

define e = Character('Эйлин', color="#c8ffc8")

И вместо неё вставьте следующий код:

define reg = Character("Рон Легион-Смерти", color="#0099BB")
define merv = Character("Мервин Попплуэлл", color="#007799")
define roy = Character("Ройстон Медовая-Бабочка", color="#0044CC")
define rai = Character("Рейн", color="#8888EE")
define cla = Character("Клэр", color="#AA1100")
define man = Character("Человек в комбинезоне", color="#EE1100")

Как видим для каждого персонажа задаётся имя и цвет диалога, а сам персонаж в свою очередь присваивается константе виде сокращенного имени. Для различия главному герою и его команде задаются цвета синих оттенков, а стороне антагониста оттенки красного цвета.

Переходы

Ранее в этой книге мы уже познакомились со стандартными переходами в Ren’Py (например, затухание, растворение, пикселизация), но иногда нужно создать переходы с индивидуальными параметрами. Добавим два таких перехода в скрипт игры. Первый это "slideleft" - слайдер в лево, который выполняет переход в течении двух секунд. Второй "fireflash" - вспышка, который представляет собой переход к ярко-оранжевому цвету и обратно. Он будет использоваться для сцен пожара.

define slideleft = CropMove(2.0, "slideleft")
define fireflash = Fade(0.1, 0.0, 0.5, color="#e40")

Изображения для персонажей

На лаунчере Ren’Py в раздел "Открыть папку" есть ссылка images. После её нажатия откроется каталог с изображениями проекта.

Чтобы назначить изображение персонажу, нужно выполнить следующие правило: имя файла с изображением должно совпадать с именем константы, в которой храниться персонаж. Например, в нашем случае нужно добавить изображения с именами reg.png, merv.png, roy.png и так далее. Так же файл с именем Reg.png соответствует reg.png и будет присвоен персонажу, но файл с именем Reginald.png уже не подойдёт.

Нужно не забывать, что Ren’Py для персонажей использует формат изображения PNG и WEBP, а для фона JPG, PNG и WEBP.

Подготовка ассетов

Хоть Ren’Py на основе имени автоматически подгружает изображения персонажей, то остальные аудиовизуальные ресурсы нужно явно определить в файле скрипта. Как мы увидим позже большая часть ресурсов в создаваемой игре это фоновые изображения в формате jpg, которым присвоены соответствующие имена.

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

image rai serious = "rai_serious.png"

Теперь для вывода персонажа Рейн с серьезным выражением лица нужно выполнить команду rai serious "Да, я серьезно!". На основе данного приёма сделайте своих персонажей более живыми используя как можно больше изображений с различными эмоциями лица.

Помните, вы можете использовать оператор image для определения как основного портрета персонажа (например, _image jimmy = "jimmy.png"), так и для дополнительных (например, image jimmy happy = "jimmyhappy.png"). Однако перед этим вам нужно определить персонажа (например, define jimmy = Character("Jimmy")), что мы и сделали в главе "Настройка персонажей".

Теперь давайте определим ассеты для офиса, которые можно скрывать и показывать при помощи команд hide и show (например, чтобы показать изображение офиса office1.jpg, нужно выполнить команду: show office, а чтобы скрыть выполнить команду: hide office)

image office = "office1.jpg"
image office2 = "office2.jpg"
image email = "email.jpg"
image topfloor = "topfloor.jpg"
image cooler = "cooler.jpg"

Далее назначим изображения для сцен города, парка, станции и т.д.:

image london = "london.jpg"
image london2 = "london2.jpg"
image park = "park1.jpg"
image park2 = "park2.jpg"

image alley = "alley.jpg"
image door = "door.jpg"
image window = "window.jpg"

image station = "station.jpg"

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

image fire1 = "fire1.jpg"
image fire2 = "fire2.jpg"
image fire3 = "fire3.jpg"
image fire4 = "fire4.jpg"

В бой!

Теперь мы приступим к написанию основного сценария. Укажем кто мы и чем занимаемся. Для этого отобразим портрет главного героя в точке с координатами xpos: 0.7 и ypos: 0.2, где значение 0.0 – это левый край, а 1.0 правый. Так же применим ранее определённый эффект скольжения.

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

# Начало игры

label start:

# Определяем переменные

    $ sips = 0
    $ time = 0
    $ items = []
    $ dvd_found = False

При написании скрипта, не забывайте корректно задавать отступы для блоков кода.

Далее вставляем пустую строку и прописываем щелчки мыши:

    play sound [ "<silence 1.5>", "sounds/mouse_clicks.wav" ]

Данная строка найдет в каталоге со звуками файл "sounds/mouse_clicks.wav" и воспроизведёт его с паузой в 1,5 секунды.

    show office1
    with slideleft

Теперь отобразим фон офиса с именем office1 используя ранее определённый переход slideleft. Эти две строки должны иметь одинаковый отступ иначе при запуске игры возникнет ошибка. Чтобы с отступами было меньше проблем, то лучше использовать более продвинутые текстовый редактор, которые позволяет делать отступы при помощи кнопки Tab.

    show reg:
        xalign 0.0
        xpos 0.7
        ypos 0.2
    with easeinleft

    "Рон Легион-Смерти, правительственный эксперт по кибербезопасности, бездельничал за своим рабочим столом."

    "Вместо работы над проектом, он смотрел смешные видеоролики в интернете."

    "Рон находился в депрессии из-за смерти своего лучшего и единственного друга по офису Мервина Попплуэлла."

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

Комментирование кода

Хорошим тоном написания сценария – это его комментирование, особенно когда вы работаете в большой команде. В Ren’Py – это делается с помощью символа хэштега (#).

Например, добавьте данный комментарий перед появлением портрета главного героя с текстом "Рон Легион-Смерти, правительственный…".

# Комната 1: Рабочий стол Рона

Хоть Ren’Py имеет строгие правила к отступам, но они не распространяется на комментарии, которые не являются частью кода.

Ваше первое меню

Теперь мы при помощи меню реализуем взаимодействие пользователя с игрой.

Однако сперва для работы меню нужно определить переменные после метки "start". Чтобы это сделать используйте следующий синтаксис: $ variable_name = 0, где variable_name – имя переменной.

label start:
    $ sips = 0
    $ time = 0
    $ items = []

Как видим, инициализируется три переменных: sips – которая используется для подсчета количества сделанных глотков воды главным героем, переменная time будет подсчитывать количество проведённого времени, а переменная items, которая является пустым массивом, будет хранить в себе инвентарь. Однако Ren’Py не имеет массивов как таковых, вместо этого он использует списки.

Добавим следующий код в конец основного скрипта:

    menu deskaction:
     "Выбери действие"
     "Посетить зону с питьевым фонтанчиком":
         $ time += 1
         stop sound fadeout 1.0
         hide office1
         show cooler
         with dissolve
         jump drinkingfountain
         
     "Осмотреть рабочий стол коллеги":
         $ time += 1
         stop sound fadeout 1.0
         hide office1
         show office2
         with dissolve
         jump neighboringdesk

    menu drinkingfountain:
             "Выбери действие"
             "Вернуться к своему рабочему столу":
                 jump deskaction
                 
             "Выпить":
                 $ sips += 1
                 
                 "Сделано глотков [sips]."
                 jump deskaction

    label neighboringdesk:
           if time>3:
               jump emailreceived

           if time<=2:
               "Коллег ушел рано. Его рабочий стол пуст."
               hide office2
               show office1
               with dissolve
               show reg
               jump deskaction
           else:
               "Соседний рабочий стол убран."
               hide office2
               show office1
               with dissolve
               show reg
               jump deskaction

В данном коде для создания меню используются такие ключевые слова как menu, jump и label. Каждое меню должно иметь метку, в нашем случае это deskaction. А сочетание слов jump (прыжок) и label (метка) позволит выполнит переход по самому меню, и переход в нужную точку сценария. Так же обратите внимание, как используется знак двоеточия в конце меток и пунктов меню.

При выборе пункта меню «Выпить», над переменной выполняется операция +=, а потом она выводится в строке через квадратные скобки. Таким образом мы подсчитываем сколько Рон сделал глотков воды пока находился возле фонтанчика с водой. Каждое такое действие увеличивает количество глотков на единицу, но если нужно сделать 60 глотков, то строка имела бы вид $ sips += 60.

Использование условного оператора

Благодаря условному оператору if, elif и else игру можно сделать более разнообразной и интересной. Для этого замените код от строки menu drinkingfountain: до jump deskaction на следующий:

    menu drinkingfountain:
             "Выбери действие"

             "Вернуться к своему рабочему столу":
                 jump deskaction

             "Выпить":
                 $ sips += 1
                 play sound "sounds/gulp.wav"

                 if sips<3:
                     "Сделано глотков [sips]."
                     show office1
                     with dissolve
                     jump deskaction
                 elif sips==3:
                     "После [sips] глотков, вы утолили жажду."
                     show office1
                     with dissolve
                     jump deskaction
                 else:
                     "Пропало желание пить."
                     show office1
                     with dissolve
                     jump deskaction

Как видим благодаря условному оператору мы проверяем переменную sips и в зависимости от количества глотков сообщаем, что Рон утолил жажду. Условный оператор состоит из самого условия, которое прописывается после слова if, ключевого слова elif (если ещё), который используется для проверки дополнительных условий, и слова else, код после которого будет выполняться если ни одно из условий не выполнено.

Теперь давайте добавим еще условие, на основе переменной time. Для этого внесём изменения в код меню deskaction:

     "Посетить зону с питьевым фонтанчиком":
         $ time += 1
         jump drinkingfountain
         
     "Осмотреть рабочий стол коллеги":
         $ time += 1
         jump neighboringdesk

Далее добавим условный оператор после строки label neighboringdesk:

           if time>3:
               jump emailreceived

Этот код просто проверяет количество выполненных действий в игре, и когда число больше трёх, мы переходим к метке emailreceived.

Теперь сменим декорации. Добавьте следующие строки кода после jump deskaction:

    # Время электронной почты
    label emailreceived:
        scene email
        with dissolve

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

        play sound "sounds/email.wav"

        "Рон вернулся к своему рабочему столу, и замечает, что ему пришло письмо."
        "Оно содержало следующее сообщение: \"Встретимся сегодня в семь вечера в Парке возле Aрки. Никому не говори об этой встречи …\""
        "... и подписано Мервин Попплуэлл."
        "Это какая-то шутка. Мервина же больше нет."
        "Рон решает позвонить Ройстону сотруднику из технической поддержки, чтобы узнать, кто прислал это письмо."
        "После телефонного звонка, выясняется, что письмо настоящее, и Ройстон просит Рона подняться на верхний этаж."

Согласно повествованию, следующее место действия – это верхний этаж. В этой сцене так же используется условный оператор. Будет выполнена проверка переменной sips, чтобы узнать, немного ли выпил игрок воды, и если да, то отобразить шутливое сообщения.

label topfloor:

    scene topfloor
    with dissolve

    # Показываем изображение телефона
    show phone:
        xalign 0.0
        xpos 0.5
        ypos 0.18
    with easeinleft

Кроме изображений фона и персонажей Ren’Py может отобразить любой другой спрайт. Если посмотреть пример предыдущего кода, то сценарий выводит спрайт мобильного телефона используя эффект easeinleft. Далее отображается диалог, а затем телефон исчезает с эффектом easeoutright.

    "Там Рон находите мобильный телефон 80-х годов, и забирает его с собой."

    hide phone
    with easeoutright

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

    play sound "sounds/sound.wav"
    $ items.append("специальный телефон")

    scene london
    with dissolve

   # Если игрок выпил слишком много воды, показываем сообщение
    if sips >= 3: 
        "Нужно сходить в туалет."
    "Он всё же решается на встречу, предложенную в электронном письме."

   # Парк
    label hydepark:
    play music [ "sounds/park.mp3" ] fadein 10.0 loop

Возможно, вы помните Ren’Py по умолчанию имеет три звуковых канала: sound, music, и voice. Как видим, предыдущие строки кода демонстрируют нам использование музыкального канала. Мы циклично (loop) воспроизводим с нарастанием (fadein) в 10.0 секунд фоновые звуки парка.

    scene park
    with dissolve

    "Парк находился всего в нескольких минутах ходьбы от офиса. Идя по дороге к арке парка"
    $ items.append("монета")  

    # Сортируем список элементов
    $ items.sort()

    play sound "sounds/sound.wav"
    "Рон замечаете монету в земле. Он поднял ее и положил в карман."

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

Чтобы упорядочить инвентарь в списке отсортируем его в алфавитном порядке при помощи метода sort.

Настройка скорости текста

По умолчанию Ren’Py показывает диалог персонажа одним блоком. Если вы хотите использовать классический эффект печатной машинки, нужно открыть файл options.rpy и найти строку default preferences.text_cps = 0. Далее, ноль заменить на 20, после чего сохранить файл и перезапустить игру.

Обновляем систему инвентаря

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

init python:
    items = []
    def display_items_overlay():
        if len(items)>0:
            inventory = "Инвентарь: "
            for i in range(0, len(items)):
                item_name = items[i].title()
                if i > 0:
                     inventory += ", "
                inventory += item_name
            ui.frame()
            ui.text(inventory)
    config.overlay_functions.append(display_items_overlay)

В результате код выведет список предметов инвентаря в верхнем левом углу экрана.

В коде при помощи оператора python указывается, что далее идут строки, которые нужно интерпретировать как чистый Python. То есть язык, на котором написан сам Ren’Py. После чего определяем список с названием items. Далее проверяем если items содержит хоть один элемент, то выводим содержимое через запятую. Данный процесс будет выполниться в фоновом режиме на протяжении всей игры.

На будущее, для манипуляции с элементами в списке существуют различные методы, с которыми вы можете ознакомиться в Таблице 7-15.

Таблица 7-15. Методы для манипуляции со списками в Python и Ren’Py

МетодОписаниеПример
appendДобавить элемент в списокfruit.append("apple")
removeУдалить элемент из спискаpocket.remove("pie")
insertВставляет элемент в заданную позициюinventory.insert(2, "deodorant")
reverseВ списке выставляет элементы в обратном порядкеnames.reverse()
countПодсчитывает количество элементов, найденных в спискеfruit.count("Pear")
sortСортирует элементы в алфавитном порядкеlast_names.sort()
clearУдаляет все элементы из спискаinventory.clear()
lenВозвращает количество элементов в спискеif len(items)>0:
"Hooray!"

Продолжим создавать игру добавив следующий код после строки $ items.append("coin"):

    scene park2
    with dissolve

    "Возле Арки парка никого не было."
    "Рон довольно долго ждал, но никто так и не пришел."
    "Разочаровавшись, он отправился домой."

Теперь продолжим сценарий драматической сценой - пожаром в офисе:

    # Офис Инферно
    label inferno:

    "На обратной дороге Рон замечает, что горит его офис."
    reg "Если бы я задержался немного на работе перед тем, как пойти в парк, то может быть предотвратил пожар."
    play sound "sounds/phone.wav" loop

    "В этот момент из портфеля послышался звук звонящего телефон. Взволнованный Рон с трудом достаёт его и отвечает на звонок."

    stop sound
    show roy:
        xalign 0.0
        xpos 0.2
        ypos 0.2
    with easeinright

    roy "Спаси розовый DVD-диск. У тебя осталось около десяти минут, прежде чем его поглотит огонь."

    hide roy
    play sound "sounds/siren.wav" fadein 5.0 fadeout 5.0

    "Речь шла про систему безопасности «Август», которую должны интегрировать во все государственные службы страны. "

Добавляем функции

Функции — это многократно используемый код. Они так же приводят код в порядок и ускоряют процесс разработки. В Ren’Py функции вызываются при помощи команды call.

Перед тем как познакомится с функциями, добавим после строки label start: переменную $ dvd_found = False, чтобы запомнить есть ли у вас DVD-диск или нет. Это логическая переменная и может иметь только два значения: истина (True) или ложь (False).

Примечание:

Ren’Py требователен к значениям переменных логического типа, которые должны начинаться с заглавной буквы (т. е. True — это правильно, а true вызовет ошибку).

Теперь определим три функции перед меткой label start:.

label show_fire:
    $ num = renpy.random.randint(1, 4)
    $ which = "fire" + str(num)
    show expression which
    with fireflash
    return

label check_dvd:
    # воспроизвести звук если dvd_found не равно True
    if dvd_found == False:
      play sound "sounds/sound.wav"
    $ if dvd_found == False: items.append("DVD-диск")
    $ if dvd_found == False: items.sort()
    $ if dvd_found == False: dvd_found = True
    return

label checktime:
    # вызываем функцию show_fire из этой функции
    call show_fire

    play sound "sounds/woosh.wav"
    $ time -= 5
    if time < 5:
      jump burn
    return

Первая функция show_fire используется для случайного выбора фона из четырех изображений. Для этого в переменную num помещается случайное число от одного до четырех. Затем это число преобразовывается в строку и добавляется к строке fire. После чего передаём ее команде show, преобразовав оператором expression в исполняемую строку. В результате с эффектом fireflash (определённый ранее) отобразиться фон с огнём.

Вторая функция check_dvd сперва проверяет, равно ли значение переменной dvd_found False. Если это так, то элемент добавляется в инвентарь с помощью метода append, а сам список сортируется в алфавитном порядке. В конце, переменной dvd_found присваивается значение True, для избежания повторного добавления DVD-диска в инвентарь.

Для третей функции checktime понадобиться ранее используемая переменная time, со значением 25. Не бойтесь, в программировании повторное использование переменных вполне нормальная практика.

В эпизоде, где игрок лихорадочно ищет DVD-диск в офисе охваченным огнём имеется четыре комнаты (т. е. Помещение офиса - officefire, Рабочий стол Рона - firedesk_a, Соседний стол А - firedesk_b, Соседний стол Б - firedesk_c). А искомый диск находиться на рабочем столе коллеги firedesk_c, но игрок об этом не знает. Вот здесь и будет использоваться функция checktime, которая при каждом переходе между столами будет сокращать переменную time на 5 секунд, а как только время достигнет нуля, то функция выполнить переход к сцене гибели игрока в огне.

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

    # В Аду
    label actionscene:

    scene alley
    with dissolve

    play music [ "sounds/fireplace.mp3" ] fadein 10.0 loop

    "Оказавшись возле офиса, Рон, недолго думая, взбирается по пожарной лестнице."
    $ time = 25

    call show_fire

Система частиц с SnowBlossom

Главный герой попадает в огненный ад, а огонь как мы знаем сопровождается искрами. Давайте добавим такой эффект используя систему частиц. Где сама частица — это маленькое изображение (спрайт), а используя большое количество таких частиц, можно создать эффекты как дождь, снег и взрыв. Для этого в Ren’Py существует встроенный объект SnowBlossom. Он часто используется в визуальных новеллах для реализации падение листвы с дерева или лепестков цветущей сакуры, но мы применим данный эффект для создания анимации искр огня.

    # Добавляем и настраиваем эффект летящих искр с помощью SnowBlossom
    image sparks = Fixed(
        SnowBlossom(im.FactorScale("images/fireparticle.png",1.0),count=12,start=5),
        SnowBlossom(im.FactorScale(im.Alpha("images/fireparticle.png",0.8),0.6),count=15,yspeed=(50,125)))

Основные параметры SnowBlossom это count, start, yspeed и xspeed. Параметр start задаёт в секундах задержку появления новых частиц. Сейчас данный параметр равен 5 секундам. Параметр count задаёт количество частиц.

Далее параметр yspeed задает скорость по вертикали, а xspeed задает скорость по горизонтали. Значение этих атрибутов, может быть, как отрицательные, так и положительные, и состоять из одного числа или кортежа (например (5,10) или (-10,-5)). При использовании в Ren’Py кортежей, то значение для параметра будет выбрано случайно из указанного диапазона чисел. В нашей игре искры должны падать довольно медленно, для этого зададим скорость от 50 до 125.

Так же видно, что в коде используется два экземпляра SnowBlossom. Первый создаёт полноразмерные частицы, а второй уменьшенный вариант этих частиц. Так же во втором варианте через параметр im.Alpha, который равен 0,6, указывается альфа-канал, делающий искры огня полупрозрачными. Благодаря этому достигается иллюзия глубины.

Так же можно использовать более упрощенный вариант эффекта SnowBlossom:

  image sparks = Fixed(
       SnowBlossom(im.FactorScale("images/fireparticle.png",1.0), count=10, yspeed=(100,110), xspeed=(1,2), start=4))

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

Далее активизируем частицы при помощи команды show:

    show sparks

    menu officefire:
     "Выбери действие. У вас осталось [time] секунд. Вы находитесь в помещении офиса."
     "Подбежать к своему рабочему столу":
         call checktime
         jump firedesk_a

     "Подбежать к столу коллеги":
         call checktime
         jump firedesk_b

     "Подбежать к столу другого коллеги":
         call checktime
         call check_dvd
         jump firedesk_c

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

Когда игрок попадает в комнату с DVD-диском (firedesk_c), то выполняется функция check_dvd, которая добавить диск к инвентарю, и отметит, что он найден.

    menu outwindow:
     "Выбери действие. У вас осталось [time] секунд. Вы находитесь в помещении офиса."
     "Вылезти через окно":
         call checktime
         jump window

     "Подбежать к своему рабочему столу":
         call checktime
         jump firedesk_a

     "Подбежать к столу коллеги":
         call checktime
         jump firedesk_b

     "Подбежать к столу другого коллеги":
         call checktime
         call check_dvd
         jump firedesk_c

Для комнаты "Помещение офис" создаётся два почти идентичных меню. Первое это officefire переход, к которому выполняться при dvd_found равно False. Второе меню outwindow с дополнительным пунктом "Вылезти в окно", выведется если переменная dvd_found будет равна True. Другими словами, возможность покинуть охваченный огнём офис через окно, появляется, когда игрок найдет DVD-диск посетив комнату firedesk_c, а потом вернется в комнату "Помещение офис".

    menu firedesk_a:
     "Выбери действие. У вас осталось [time] секунд. Вы находитесь возле своего рабочего стола, но здесь нет DVD-диска, только пламя."

     "Вернуться в помещение офиса":
         call checktime
         if dvd_found == False:
              jump officefire
         else:
              jump outwindow

     "Подбежать к столу коллеги":
         call checktime
         jump firedesk_b

     "Подбежать к столу другого коллеги":
         call checktime
         call check_dvd
         jump firedesk_c

    menu firedesk_b:
     "Выбери действие. У вас осталось [time] секунд. Вы находитесь возле стола своего коллеги, но здесь нет DVD-диска, только дым."

     "Вернуться в помещение офиса":
         call checktime
         if dvd_found == False:
              jump officefire
         else:
              jump outwindow

     "Вернуться к своему рабочему столу":
         call checktime
         jump firedesk_a

     "Подбежать к столу другого коллеги":
         call checktime
         call check_dvd
         jump firedesk_c

Так. Комнаты firedesk_a и firedesk_b добавлены, осталось добавить комнату с DVD-диском:

    menu firedesk_c:
     "Выбери действие. У вас осталось [time] секунд. Вы находитесь возле стола другого коллеги."

     "Вернуться в помещение офиса":
         call checktime
         if dvd_found == False:
              jump officefire
         else:
              jump outwindow

     "Подбежать к своему рабочему столу":
         call checktime
         jump firedesk_a

     "Подбежать к столу коллеги":
         call checktime
         jump firedesk_b

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

label burn:
    "Задыхаясь от дыма, вы теряете сознание и сгораете заживо."
    if dvd_found == True:
        "В результате после пожара спасатели обнаружили труп с DVD-диском в руках."

    # Возвращаемся в главное меню
    $ renpy.full_restart()

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

Теперь вставим код, когда главный герой успешно находит DVD-диск и спасается от огня:

label window:
    "В конце концов вы находите спасение выбравшись из офиса через окно и спустившись по пожарной лестнице!"

Случайный диалог

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

    $ randomdialogue = renpy.random.choice(['Телефон в портфеле снова зазвонил.', 'Из портфеля раздался пронзительно громкий звук.', 'Зазвонил старый мобильный телефон.'])

    play sound "sounds/phone.wav" loop

    # Отображаем случайный диалог
    "[randomdialogue]"

    stop sound
    show roy:
        xalign 0.0
        xpos 0.2
        ypos 0.2
    with dissolve

    roy "Молодец. Теперь иди домой и жди дальнейших указаний. Если необходимо, защити диск ценой своей жизни."

В данной части кода используя переменную randomdialogue строка "[randomdialogue]" выводит одну из трех строк, хранящихся в переменной random_narration.

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

    # Генерируем случайное число от 20 до 80
    $ random_number = renpy.random.randint(20, 80)

    "Вы в мотивированы на [random_number]%%, чтобы продолжить квест!"

Здесь мы инициализируем переменную random_number, и присваиваем ей значение от 1 до 100. Далее выводим её в диалоге игры. Однако нам нужно отобразить символ %, а это зарезервированный символ, для этого заменим его на запись %%, иначе Ren’Py выдаст ошибку.

А если потребуется сгенерировать случайное значение с плавающей запятой от 0 до 1 (например, 0,5 или 0,9), то нужно воспользоваться методом renpy.random.random().

Стили и гиперссылки

Сценарий этой маленькой игры заканчивается следующим образом:

label home:

    scene london2
    with dissolve

    # Мы используем музыкальный канал для воспроизведения фона, так как он
    # позволяет зациклит и применить затухание к аудио дорожке.
    play music [ "sounds/london_bridge.wav" ] fadein 10.0 loop

    "Чем ближе Рон становился к дому, тем спокойней себя чувствовал." 

    play sound "sounds/phone.wav" loop

    "Телефон снова зазвонил."
    stop sound

    "Ответив, он услышал женский голос."
   
    # Показываем Рейн с серьёзным лицом
    show rai serious:
        xalign 0.0
        xpos 0.2
        ypos 0.2
    with dissolve

Как видим, код выводит диалоги без форматирования. В конце, командой show rai serious, изменяем лицо Рейн на серьёзное, которое было определено ещё в начале сценария.

    rai "{i}Ни в коем случае, не возвращайтесь домой!{/i} Покинь город прямо {u}сейчас.{/u} Сядь на поезд, и уезжай на север."
    rai "Главное не задерживайся! Чуть позже я тебе перезвоню. {b}И не потеряй диск!{/b}"

Следующая часть кода сменяет лицо Рейн на дерзкое, оно является лицом по умолчанию. Напомним, что если команда show применяется без каких-либо параметров, то будет показано изображение персонажа по умолчанию (например, rai.png, roy.png и reg.png).

    # Меняем лицо Рейн с серьёзного на дерзкое
    hide rai
    show rai:
        xalign 0.0
        xpos 0.2
        ypos 0.2

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

Теперь, давайте вставим немного рекламы в виде гиперссылки в диалоге.

    rai "О, когда будет свободное время, посетите {a=https://www.example.com}этот сайт{/a}."

Как видим гиперссылки в Ren’Py встраиваются при помощи тега a, и после символа равно указывается ссылка на ресурс.

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

    rai "Я каждый день на нём нахожу что-то новое. Он очень {size=+10}{i}увлекательный{/i}{/size}."
    hide rai

    # Заменяем фоновый звук на другой 
    play music [ "sounds/ambience.wav" ] fadein 10.0 loop

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

Добавляем видео

Воспроизвести видео в Ren’Py очень просто. Достаточно написать на языке Python renpy.movie_cutscene и передать путь к видеофайлу. Будьте внимательны к регистру символов и правильности пути к файлу, иначе Ren’Py выдаст ошибку. Что касается формата видео контейнера для проектов Ren’Py самым популярным является WebM.

    $ renpy.movie_cutscene("videos/Interlude.webm")
    scene door
    with dissolve

Примечание:

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

Скорость текста на лету

Подобный эффект пишущей машинки мы уже рассматривали ранее в этой главе. Для этого нужно было внести изменения в файл options.rpy. Хотя данный эффект применяется глобально, но мы можем на него повлиять локально из самого скрипта игры.

    "Оказавшись возле своей квартиры, Рон услышал странные звуки. Чтобы понять что там происходит, он приложил ухо к двери, {cps=5}но звук стал только громче.{/cps}"

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

Живой текст

Ren’Py имеет множество тегов для стилизации текста в диалоге игры.

    "{k=-1.5}Что-то не так.{/k} {k=1.5}Рон вспомнил совет девушки и ушел прочь.{/k}"
    "Он направился на вокзал. По дороге случайно оглянулся назад, и{vspace=25}{w}заметил, что за ним следует человек."

В данном фрагменте сценария демонстрируется эффект кернинг текста {k}, вертикальное пространство между строками {vspace} и ожидание {w}. Кернинг устанавливает отступ между символами. Может иметь как отрицательное значение (приблизить символы), так и положительное (отдалить символы).

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

    "С неестественно большими глазами, одетый в голубой комбинезон. Он выглядел как-то не естественно, хоть и имел фигуру человека. {fast} От него тянуло страхом, что аж замирало сердце."

Тег fast мгновенно, в соответствии с настройками в файле параметров Ren’Py, отображает текст, который идет перед ним.

    scene station
    with dissolve
    stop music fadeout 5.0

    "Рон побежал. Остановился только возле вокзала, задыхаясь и кашляя. Он надеялся, что оторвался от преследователя."
    "Следуя совету Рейн, Рон направился взять билет на ближайший поезд, который идет на север."
    "Вот мы и достигли конца учебной визуальной новеллы на Ren’Py."

Вот и подошла к концу первая часть "Август возвращается". Вы познакомились с основными возможностями Ren’Py, и в результате создали игру. Если вам хочется завершить данный проект, то придётся это сделать самостоятельно, а мы продолжим разрабатывать игру на следующем движке.

Часть II: Основная часть истории Рона на TyranoBuilder

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

После ввода названия укажем:

  • Game Type (Тип игры): Visual Novel (Визуальная новелла)
  • Screen Size (Размер экрана): Landscape (1280 x 720) (Альбомный (1280 x 720))
  • Game Settings (Настройки игры): убрать галочки с No Title Screen (Без заголовка экрана) и No Menu Button (Без кнопок меню)

На последнем шаге нажимаем кнопку Create New Project (Создать новый проект).

В созданном проекте выберите Background Image (Фоновое изображение) и удалите его, нажав на крестик удаления.

Персонажи

Для повествования нам нужны персонажи. Для этого в меню выберите Project\Characters (Проект\Персонажи). В появившемся окне Characters (Персонажи), при помощи поля ввода Character Name (Имя персонажа) добавим следующих персонажей: Клэр, Человек в комбинезоне, Рон и Рейн. TyranoBuilder автоматически создаст в каталоге ProjectName/data/fgimage пронумерованные подкаталоги для персонажей (например, 1 – это Клэр, 2 – это Человек в комбинезоне и так далее), в которых будут сохранены связанные с ними изображения.

Поезд

Продолжим историю. Наш отважный главный герой оказался возле поезда. Теперь скопируйте следующий текстовый блок и ставьте его в красное поле "Text (Текст)" под "Show Text (Показать текст)".

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

Далее следует еще один фрагмент повествования для второго компонента Show Text:

"Извините!" — кто-то обратился к Рону. Темноволосая девушка возникла из не откуда, заблокировав проход в вагон. "Это 
поезд на север?" - улыбаясь спросила она.

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

Ассеты и каталоги

TyranoBuilder использует аудио, видео, изображения и другие файлы. Всё это он распределяет по специально выделенным каталогам, которые автоматически создаются для каждого проекта. Давайте с ними познакомимся. Так как TyranoBuilder в основном распространяется через Steam, то, чтобы открыть каталог с проектом нужно выполнить следующие действия:

  • Найдите в библиотеке Steam приложение TyranoBuilder, на котором щелкните правой кнопкой мыши и выберите "Properties (Свойства)".
  • Откройте вкладку "Local Files (Локальный файлы)".
  • Нажмите на кнопке "Browse Local Files (Просмотреть локальные файлы)"

Откроется каталог программы TyranoBuilder. В котором находите и открываете каталог вашего проекта, после чего переходите в каталог data (например, myproject\nameproject\data). В результате отобразится структура каталога, где хранятся все файлы игры, с описанием которых можно ознакомиться в таблице 7-16.

Есть и другой способ попасть в данный каталог. В TyranoBuilder выбираете пункт главного меню "TyranoBuilder\Project List". Откроется список проектов, находите ваш проект и справой стороны от названия нажимаете на иконку с изображением папки.

Таблица 7-16. Основные каталоги проектов TyranoBuilder (каталог "nameproject\data").

КаталогИспользуется для
bgimageФоновых изображений
bgmФоновой музыки (в формате .ogg или .wav)
fgimageИзображений персонажей. Создаются автоматически
imageИзображений, связанных с пользовательским интерфейсом
othersПлагинов (т. е. установленные дополнения)
scenarioФайлов сцен (например, scene1.ks)
soundЗвуковых эффектов (в формате .wav или .ogg)
videoВидеофайлов (желательно формат .webM)

Одним из основных элементов игры является звуковое сопровождение. Для этого в TyranoScript используется специальный набор тегов (см. Таблицу 7-17). Чтобы программа понимала, что мы используем тег его оборачивают в квадратные скобки или используют знак @ в начале строки если тег не более одной строки (например, @playse Storage=cheer.ogg).

Примечание:

Для комментариев в TyranoScript используют точку с запятой (;).

Таблица 7-17. Теги для работы со звуком

ТегОписаниеПример
[playse]Воспроизводит звук. Параметры loop и clear не обязательны. Повторный вызов останавливает воспроизведение предыдущего звукового эффекта[playse storage=cheer.ogg loop=false clear=true]
[stopse]Останавливает воспроизведение звука[stopse]
[seopt]Устанавливает громкость звука[seopt volume=70]
[wse]Ожидает завершения воспроизведения звукового эффекта[wse]
[playbgm]Воспроизводит фоновую музыку. Параметры: loop, time (точка с которой начать воспроизведения в миллисекундах)[playbgm storage="song1.ogg" loop=true]
[stopbgm]Останавливает воспроизведение фоновой музыки[stopbgm]
[bgmopt]Устанавливает громкость фоновой музыки[bgmopt volume=50]
[xchgbgm]Подмешивайте одну музыку, пока другая затихает. Параметры: loop, time (в миллисекундах)[xchgbgm storage=song2.ogg loop=true time=4000]
[fadeoutbgm]Задает затухание фоновой музыки в миллисекундах[fadeoutbgm time=3000]
[fadeinbgm]Задает затухание фоновой музыки в миллисекундах[fadeinbgm time=5000]

Звуки в поезде

Продолжим наше приключение. Перетащите компонент "Change Background (Изменить фон)" из категории "Images (Изображения)" и разместите его перед добавленным текстом. В свойствах компонента нажмите кнопку "Browse (Обзор)", после чего загрузите файл с именем train2.jpg. Затем добавьте компонент "TyranoScript" перед компонентом _"Exit Scene (Покинуть сцену)". Раскройте его, и вставьте следующий код:

;Используя переменную "f.rumlevel", устанавливаем уровень опьянения равный ноль
[eval exp="f.rumlevel = 0"] 

В строке [eval exp="f.rumlevel = 0"], определяем переменную с именем f.rumlevel со значением ноль. Напомним вам, что внутри игровые переменные в TyranoBuilder должны начинаться с f. (например, f.happiness, f.attraction_level). Теперь добавим в компонент ещё код:

;Тег [r] вставляет новую строку
Девушка вызывала, только сомнение. - «Да, поезд идёт на север» - ответил Рон и пройдя мимо неё заскочил в вагон. [r]
;Тег [l] ждет щелчок мыши от игрока, а тег [cm] убирает весь текст.
Найдя своё место, Рон сел, и поезд трогается с места. [l][cm]
;Воспроизводим циклично фоновый звук в вагоне и движущегося поезда
[fadeinbgm storage=train_01.wav loop=true time=3000]
;Показываем новый фон
[bg storage=train1.jpg time=6000 wait=true]
Девушка снова встретилась с Роном, так как оказалось их места находятся рядом.[l][cm]
Она представилась, что её зовут Клер, и извинилась, что снова создаёт ему неудобства.[l][cm]
Когда Клэр села, то надела наушники, и закрыла глаза.[r]
Это обрадовало Рона, теперь он может спокойно собраться с мыслями и подумать.[l][cm]
;Проигрываем видео (невозможно пропустить)
[movie storage=interlude.webm skip=false]

На основе комментариев к коду, становится ясно, что, используя тег fadeinbgm в фоне плавно (нарастание звука происходит в течении 3000 миллисекунд) зациклено воспроизводиться шум поезда.

Также меняется фоновое изображение при помощи тега bg с использованием 6-ти секундным эффектом плавного перехода. А в конце кода воспроизводится видео в формате webM.

TyranoBuilder имеет и другие теги для работы со слоями, текстом и изображениями см. Таблицу 7-18.

Таблица 7-18. Теги для работы с текстом, слоями и изображениями.

ТегОписаниеПример(ы)
[image]Отображает изображение.
Параметры: layer, page, visible, width, height, x, y.
Если указать layer значение base, то изображение выведется как фон, а если указать число больше нуля, то изображение выведется на передний план.
Параметр page принимает значение только fore или back.
[image storage="dude.jpg" layer=1 page=fore visible=true width="256" height="256" x="640" y="200"]
[image storage="bg.jpg" layer=base page=back visible=true width="1280" height="720" x="0" y="0"]
[bg]Изменяет фон.
Параметры: method, time_, wait.
Параметр method принимает значение какой применить тип перехода (т. е. crossfade, explode, slide, blind, bounce, clip, drop, fold, puff, scale, shake, size).
Параметр wait указывает, следует ли останавливать обработку до завершения замены.
[bg storage=bg1.png method=slide time=2000 wait=true]
[bg storage=bg2.png method=puff time=4000 wait=false]
[layopt]Управляет отображением слоев.
Параметры: page, visible, left (позиция слева), top, opacity (имеет значение от 0 до 255, где 255 — полностью прозрачен).
[layopt layer=1 visible=true opacity=100]
[layopt layer=1 visible=false]
[anim]Анимирует или перемещает изображение или спрайт персонажа.
Необязательные параметры: layer, left, top, width, height, opacity, color, time, effect.
Параметр effect использует следующие значения: jswing | def | easeInQuad | easeOutQuad | easeInOutQuad | easeInCubic | easeOutCubic | easeInOutCubic |easeInQuart | easeOutQuart | easeInOutQuart | easeInQuint | easeOutQuint | easeInOutQuint | easeInSine | easeOutSine | easeInOutSine | easeInExpo | easeOutExpo | easeInOutExpo | easeInCirc | easeOutCirc | easeInOutCirc | easeInElastic | easeOutElastic | easeInOutElastic | easeInBack | easeOutBack | easeInOutBack | easeInBounce | easeOutBounce | easeInOutBounce.
[anim name="Billy" time=3000 left=100 top=40]
[anim name="Reginald" time=2000 left=800 top=40 effect=easeInOutQuint]
[anim name="Gayelord" time=1000 left=100 top=40 effect=easeInOutQuint color=red opacity=50]
[ptext]Отображает текст. Понимает HTML-теги.
Параметры: text, size, x, y, color, vertical.
[ptext layer=2 page=fore text="Привет <br><i>друзья!</i>" size=30 x=30 y=180 color=green]
[mtext]Отображает текст с анимацией. Имеет множество различных эффектов.
Параметры: text, x, y, in_effect, out_effect.
Демонстрацию в реальном времени можно посмотреть на https://tyrano.jp/mtext/open in new window.
[layopt layer=0 visible=true]
[mtext text="Привет!" x=200 y=100 in_effect="fadeIn" out_effect="hinge"]
[layopt layer=0 visible=true]
[mtext text=" Ух ты! Что это?" x=200 y=100 in_effect="bounceIn" out_effect="bounceOut"]
[filter]Применяет фильтр к слою или объекту.
Параметры: grayscale, sepia, saturate, hue, invert, opacity, brightness, contrast, blur.
[filter layer="0" sepia=50]
[filter layer="0" invert]
[free_filter]Отключает фильтры.[free_filter]
[movie]Воспроизводит фильм в формате mp4.
Параметры: skip (пропустить просмотр видео или нет).
;Показываем видео, которое нельзя пропустить
[movie storage="happymovie.mp4" skip=false]
[bgmovie]Воспроизводит видео в фоне.
Параметры: volume, loop.
Примечание: желательно использовать формат видео webM.
;Показать циклично видео с громкостью 80%
[bgmovie storage="movie.webm" volume=80 loop=true]
[stop_bgmovie]Останавливает воспроизведение видео в фоне.[stop_bgmovie]

Взаимодействие и переменные

Продолжим историю. Давайте добавим взаимодействия игры с пользователем. Для этого вставив в последний компонент TyranoScript следующий код:

;Реализуем диалоговое окно
Клер вытащила наушники, и достала флягу. Заметив, что Рон смотрит на неё, предложила сделать глоток.[l]
[dialog type="confirm" text="Хотите выпить?" label_ok="Да, пожалуй." storage="scene1.ks" target="yes_label" label_cancel="Спасибо, нет." storage_cancel="scene1.ks" target_cancel="no_label"]

После выполнения команды dialog, будет показано модальное окно с двумя кнопками. Если игрок согласится сделать глоток, то после нажатия кнопки "Да, пожалуй." он будет перенаправлен к месту с меткой yes_label. Если он откажется, нажав кнопку "Спасибо, нет.", то будет перенаправлен к метке no_label. Данные метки можно реализовать с помощью компонента Label.

Для этого перетяните компонент Label в конец сцены, и укажите название no_label. После чего после добавленной метки добавьте компонент TyranoScript со следующим кодом:

[cm]"Как хотите", — и Клэр делает несколько глотков из фляжки.[l][cm]
;Переходим к метке yes_label
[jump storage=scene1.ks target=*continue]

Теперь, так же добавим вторую метку yes_label, и компонент TyranoScript с кодом:

[playse storage=gulp.wav loop=false ]
;Увеличиваем уровень опьянения на единицу
[eval exp="f.rumlevel +=1"]
[cm]"Держи…", — сказала Клэр, протягивая вам флягу с ромом.[l][cm]

Если главный герой согласится выпить, то воспроизведётся звук глотка и выведется соответствующий диалог. Используемый файл звукового эффекта должен быть расположен в каталоге sound проекта игры. Кроме того при помощи тега eval увеличивается значение переменной f.rumlevel на единицу.

Добавим ещё один компонент TyranoScript.

Между рядами сонных пассажиров появился человек в синем комбинезоне.[l][r]
Он всё так же выглядел не естественно и пугающе.[p]
Человек, что-то кинул в Рона, тот инстинктивно вскочили и побежал по проходу вагона.[r]Клэр кивнула, дав сигнал странному человеку последовать за Роном.[p]
;Сюжет становиться сложнее
Идя быстрым шагом, Рон пытался найти укрытие.[l][r]Но человек в комбинезоне с лысой головой преследовал его не давая это сделать.[l][cm]
[bg storage=train3.jpg time=3000 wait=false]Однако Рон успевает зайти в туалет и запереть дверь. [playse storage=knock.ogg loop=false]В след последовали сильные удары в дверь. Это тупик.[l][cm]

В этой части сценария тег bg меняет фон вагона на фон уборной, после чего тег playse воспроизводит угрожающий стук в дверь. Так же можно заметить, для постепенного вывода текста используются ранее рассмотренные теги [l] и [cm].

Продолжим сценарий игры, добавив проверку переменной f.rumlevel:

;Если был сделан глоток вина, выведем дополнительное сообщение
[if exp="f.rumlevel==1"] Но выпитый алкоголь немного притуплял чувство страха... Уровень рома в крови - [emb exp=f.rumlevel].
[endif]

Здесь условный оператор проверяет переменную f.rumlevel равна ли она единице, и если да, то будет выведена дополнительная строка, говорящая, что главный герой немного пьян.

В следствии написания данной части сценария игры мы познакомились с основными операциями с переменными в TyranoScript:

  • [eval exp..] предназначен для присваивания значений.
  • [if exp..] предназначен для сравнения значений.
  • [emb exp..] предназначен для вывода переменных в текстовой строке.
  • Внутриигровые переменные имеют приставку f..

Случайный диалог

Теперь реализуем случайный диалог, как это было сделано в Ren’Py.

;Зацикливаем звук звонка и отображаем случайною строку
[playse storage=phone.wav loop=true ]
[cm][eval exp="f.random_number = Math.floor((Math.random() * 3) + 1)"]
[if exp="f.random_number==1"]Зазвонил мобильный телефон. [endif]
[if exp="f.random_number==2"]Зазвонил старый мобильный телефон. [endif]
[if exp="f.random_number==3"]Телефон зазвонил так громко, что заглушил все звуки вокруг. [endif]

Используя тег eval определяем переменную f.random_number. Затем присваиваем ей случайное значение от одного до трех при помощи двух функций: Math.floor и Math.random. Функция Math.random возвращает случайное число (например: 1,4), а функция Math.floor округляет его до ближайшего целого числа (например: 1).

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

Продолжим:

;Отображаем персонажа Рейн и отключаем звук телефона
[chara_show name="Рейн" wait=true top=40 left=50]
[stopse]
"Вы уже в пути? Хорошо. Они преследуют вас?"[p]
"Не волнуйтесь, оставайтесь на месте и просто ждите! Им не разрешено использовать силу."[p]
[anim name="Рейн" time=2000 left=800 top=40]
"По возможности покинь поезд на ближайшей станции. Вы меня поняли?" - связь с женщиной прекратилась.
[filter name="Рейн" blur=20][chara_hide name="Рейн" time=1000 wait=true]
[p]
"Ждать!" - но такой ход, только раздражал Рона. [playse storage=knock.ogg]А удары в дверь продолжались. Прошло где-то пять минут, и они резко прекратились.
[playse storage=knock.ogg volume=30][p]
[bg storage=train1.jpg time=3000 wait=true]
Рон осторожно открыл дверь уборной. Никого не было, ни мужчины в комбинезоне, ни Клэр.[p]

Как видно в данном коде применяются теги chara_show, chara_move и chara_hide, которые используются для вывода, перемещения и удаления персонажа Рейн. Более подробно узнать об этих тегах можно в Таблице 7-19.

Кроме того, были применены теги anim и filter. Первый перемещает Рейн в право, а второй добавляет 20% размытие. Так же эти эффекты можно применять как к спрайтам, так и к целым слоям.

Метки в TyranoScript

Компонент TyranoScript так же поддерживает метки, которые помечаются символом звездочка в начале строки. В следующей части кода выполняется проверка переменной f.rumlevel на состоянии опьянения Рона, и если он пьян, то тегом jump выполняем переход к метке *still_tipsy, иначе к метке *full_of_energy.

[bg storage="platform1.jpg"]
Прозвучало объявление: следующая остановка "Мягкая платформа"![p]
[camera zoom=2 from_zoom=1 x=180 y=100 time=2000]
;Сбрасываем установки камеры
[reset_camera]
Когда поезд остановился, Рон огляделся по сторонам, и как только убедившись, что за ним нет слежки, покинул вагон.[p]
[if exp="f.rumlevel==1"]  [jump target=*still_tipsy]
[else] [jump target=*full_of_energy][endif]

;Добавим метку
*still_tipsy
Однако выпитый Роном алкоголь клонил его ко сну, но нужно было идти.[p] [jump target=*resume_story]

;Добавим вторую метку
*full_of_energy
Адреналин просто вскипятил кровь в венах, активизировав все ресурсы организма.[p]
*resume_story
Только Рон успел потеряться в толпе, как его схватили за руку![p]

Эффект 3D-камеры

TyranoBuilder имеет функцию под названием 3D Camera. Однако это всего лишь простое панорамирование и масштабирование 2D-изображений. Использование данного эффекта показано в предыдущей части кода игры.

Используя тег [camera] и [reset_camera], в сцену был добавлен эффект актуализации внимания на сообщении диспетчера. Дополнительно в тег были переданы параметры: zoom – величина масштабирования, from_zoom – начальный масштаб, x и y – координаты куда нужно перенести камеру, time – время в миллисекундах, за которое камера должна выполнить перемещение. Для возвращения камеры в исходное состояние вызывается тег reset_camera. Однако данный эффект ресурсоёмкий, и желательно им не злоупотреблять.

Макросы TyranoScript

Макросы — это набор действий, определённые программистом. По есть, с их помощью можно определять новые теги. В приведённой части кода в первых двух строках используется тег macro для определения тега redtag и yellowtag, которые будут устанавливать цвет текста с помощью тега font и его атрибута.

;Определяем два макроса
[macro name="redtag"][font color=0xff0000][endmacro]
[macro name="yellowtag"][font color=0xffff00][endmacro]
[redtag] "Думаю, нам следует найти более тихое место для разговора", — по голосу Рон сразу узнал [font bold=true]Ройстона Медовая-Бабочка[font bold=false]  - человека из службы технической поддержки.[p]
[bg storage="park.jpg"][chara_show name="Ройстон" wait=true top=40 left=50]
[yellowtag]"Извините, это я отправил вам письмо от имени вашего друга."[p]
[chara_mod name="Ройстон" storage="chara/6/Roy2.png" time=0]
[resetfont]
"Мне нужно было вытащить вас оттуда."[p]

В дополнение к двум макросам в коде используется тег смены изображения персонажа chara_mod, а чтобы этот процесс произошел мгновенно без использования эффекта затухания атрибут time выставлен в ноль. Больше информации об этих тегах указано в Таблице 7-19.

Чтобы вернуть стиль и цвет шрифта по умолчанию, в конце указан тег resetfont.

Таблица 7-19. Теги для работы с персонажами.

ТегОписаниеПример(ы)
[chara_show]Показывает персонажа.
Параметры: wait, time, layer, left, top.
Параметр wait если указано true, тег будет ждать появления персонажа. Параметр time устанавливает время перехода в миллисекундах (по умолчанию — 1000). Параметр layer устанавливает на каком слое нужно показать персонажа (по умолчанию — передний план). Параметры left и top указывают положение изображения.
[chara_show name="Billy"]
;Показываем персонажа
;с задержкой
[chara_show name="Gayelord" wait=true top=100 left=50]
[chara_hide]Убирает со сцены персонажа[chara_hide name="Billy"]
[chara_move]Выполняет перемещение изображения персонажа.
Параметры: time, anim, left, top, width, height, wait, effect.
Параметр anim указывает анимировать перемещение или нет. Параметр effect устанавливает эффект для анимации. Параметры width и height устанавливают новый размер изображения
[chara_move name="Gayelord" time=2000 left=800 top=40 anim=true effect=easeInCubic]
[chara_mod]Смена изображения персонажа.
Параметры: time, reflect, wait, cross.
Параметр reflect указывает переворачивать изображение по горизонтали или нет. Параметр cross указывает, будет ли старое изображение плавно переходить к новому. Параметр time устанавливает время эффекта перехода в миллисекундах (по умолчанию 600 мс).
;Смена изображения персонажа
[chara_mod name="Billy" storage="billy/1/sadface.png"]
;Смена изображения персонажа с эффектом перехода
[chara_mod name="Billy" storage="billy/1/happyface.png" cross=true time=1000]
[chara_delete]Полностью удаляет персонажа из игры.[chara_delete name="Billy"]

Графические объекты в диалогах

При помощи тега graph можно добавить изображение в текст диалога. Сами изображения должны находиться в каталоге image проекта.

;Add some inline images
[macro name="phone"][graph storage="phone.png"][endmacro]
[chara_mod name="Ройстон" storage="chara/6/Roy.png" time=0]
У вас получилось создать очень надёжный файрвол под названием "Август".[p]
Это не понравилось правительству. На данный момент оно хочет узнать какие существуют уязвимости и бэкдоры.[p]
Что делать дальше вам сообщит Рейн по телефону. [phone] Вы уже с ней знакомы.[p]
[chara_hide name="Ройстон" time=2000 wait=true]

Графические кнопки

В TyranoScript есть система графических кнопок, и чтобы их вывести используется тег glink. Также данный тег имеет широкий набор параметров. Один из них это color который задаёт цвет кнопки (например: black, gray, white, orange, red, blue, rosy, green, pink). Есть параметры для настройки шрифтов и параметры для изменения общего вида кнопки.

Добавим в сценарий игры меню состоящее из трёх кнопок имеющие разные стили оформления.

Примечание:

При создании меню, содержащих графические кнопки, в конце нужно поместить тег [s], который приостановит игру до тех пор, пока игрок не сделает выбор.

;Добавляем графические кнопки
После сказанного, Ройстон оставил Рона одного на скамейке в парке.[p]
*cool_buttons
[glink target="ponder" text="Подумать" size=20 width="300" y=250 color=rosy font_color=0x000000]
[glink target="try" text="Отдохнуть" size=20 width="300" y=300 color=blue]
[glink target="resume_adventure" text="Продолжить" size=20 width="300" y=350 color=gray]
[s]
*ponder
Рон задумался о том что узнал и как поступить дальше.[p] [jump target=*resume_adventure]
*try
Рон попытался забыть всё что произошло, но у него это не получалось.[p]
*resume_adventure

Грандиозный финал с использованием Live2D

Перед тем как перейти от TyranoBuilder к Twine, покажем игроку прощальное сообщение. Так же добавим самый распространённый эффект в визуальных новеллах – землетрясение, применив тег quake, спрячем диалог при помощи тега position, и в завершение, покажем Live2D персонажа.

Для реализации данной части игры нам понадобиться модель Live2D, которую можно бесплатно скачать по ссылке https://www.live2d.com/en/learn/sample/open in new window. Для демонстрации воспользуемся моделью Хиёри Момосе.

Напомним, чтобы добавить Live2D в TyranoBuilder нужно подключить соответствующий плагин выбрав пункт меню "Project\Add-In Components", и указать галочку напротив Live2D Components. Если понадобиться показать компоненты Live2D, на панели компонентов выберите пункт меню "Project\Customize Tool Area", где на вкладке Components найдите Live2D и поставьте галочку, после чего нажмите кнопку "Apply". Не забудьте подгрузить модель Live2D, выбрав на верхней панели инструментов иконку "Live2D". Далее в окне "Live 2D Model Settings" нажав кнопку "Add a Live2D Model", найдите файл с расширением .model3.json.

Теперь всё готово, чтобы продолжить создавать игру:

Поздравляю! Вы подошли к концу изучения данной части обучающей игры.[p]
[position opacity=0][quake count=3 time=200 hmax=20]
[live2d_new name="hiyori_pro_t11" model_id="hiyori_pro_t11"]
[live2d_show name="hiyori_pro_t11"]
[live2d_mod name="hiyori_pro_t11" x=0.7 y=-0.5]
[live2d_motion name="hiyori_pro_t11" mtn="Idle" no=0]
[wait time="2000"]
[live2d_motion name="hiyori_pro_t11" mtn="FlickUp" no=0]
[mtext text="Поздравляю!" layer=2 size=48 x=680 y=160 in_effect="bounceIn" out_effect="hinge"]
[live2d_mod name="hiyori_pro_t11" scale=2]
[live2d_fadeout name="hiyori_pro_t11" time=4000]
[wait time="3000"] [close ask=false]

Как видно из кода для работы с Live2D используется много тегов (см. Таблицу 7-20). Сперва модель определяется и отображается с помощью тегов live2d_new и live2d_show. Затем задаётся позиционирование тегом live2d_mod. Далее тегом live2d_motion устанавливается тип анимации Idle, а потом FlickUp.

После чего тегом mtext выводится прощальный анимированный текст, а модель Live2D увеличивается в два раза и плавно исчезает. В конце при помощи тега close игра закрывается, но так как параметр ask равен false закрывается без подтверждения.

Таблица 7-20. Теги для работы с Live2D.

ТегОписаниеПример(ы)
[live2d_new]Загружает модель Live2D.
Параметры:
name - имя модели;
model_id - идентификатор модели;
idle – имя позы модели;
scale – масштаб модели;
x и y – координаты модели;
[live2d_new name="haru" model_id="Haru"]
[live2d_new name="haru" model_id="Haru" y=-0.8 x=-0.3 scale=2.5]
[live2d_show]Отображает модель на игровом экране.[live2d_show name="haru" y=-0.8 x=-0.3 scale=2.5]
[live2d_hide]Скрывает модель.[live2d_hide name="haru"]
[live2d_expression]Изменение выражения лица модели.
Параметр expression - выражение лица.
[live2d_expression name="haru" expression="f03"]
[live2d_motion]Переключает позу модели.
Параметры:
mtn - поза модели;
no - индекс подгруппы позы.
[live2d_motion name="haru" mtn="Test" no=0]
[live2d_mod]Модифицирует модель.
Параметры:
name - имя модели;
idle – имя позы модели;
scale – масштаб модели;
x и y – координаты модели;
[live2d_mod name="haru" scale=1]
[live2d_fadein]Плавно отображает модель.
Параметры:
time - время появления;
wait – следует ли ждать окончание анимации эффекта;
[live2d_fadein time=2000]
[live2d_fadeout]Скрывает модель с эффектом затухания.
Параметры:
time - время затухания;
wait – следует ли ждать окончание анимации эффекта;
[live2d_fadeout time=2000]

Ещё теги

TyranoBuilder имеет множество тегов общего назначения (см. Таблицу 7-21). Например, с помощью тега html можно встроить в игру веб-страницу или изменить курсор мыши или добавить глиф (значок, отображающий ожидание щелчка мыши).

Так же когда в игре очень много ассетов лучше всего выполнить их предварительную загрузку используя тег preload.

Таблица 7-21. Другие полезные теги в TyranoBuilder.

ТегОписаниеПример(ы)
[title]Устанавливает название игры.[title name="Вторая глава игры"]
[cursor]Изменяет изображение курсора мыши.[cursor storage="new_cursor.gif"]
[html]Добавьте HTML.
Параметры: top, left.
[html top="50" left="50"]
<iframe src="https://example.com/" width="560" height="315">
</iframe>
[endhtml]
[endhtml]Завершает вставку HTML. Используется в паре с [html].
[web]Открывает веб-сайт в браузере по умолчанию.[web url="https://example.com"]
[glyph]Устанавливает иконку индикатора ожидания щелчка мыши.
Иконка находится в каталоге: tyrano/images/kag/nextpage.gif.
Параметры: fix - разрешает указать позицию иконки, left, top.
;Добавляем глиф с координатами
[glyph fix=true left=100 top=100]
[dialog]Отображает диалоговое окно.
Параметры: type (принимает значения: alert, confirm, input), target, storage, label_ok, label_cancel.
В параметр storage указывается сценарий, к которому необходимо перейти после нажатия кнопки ОК, а в параметре target указывается метка, на которую нужно перейти в сцене.
Также можно переименовать кнопки «ОК» и «Отмена», используя параметры label_ok и label_cancel.
;Показать окно оповещения
[dialog type="alert" text="Содержание сообщения"]
;Отобразить окно подтверждения
[dialog type="confirm" text="Содержание сообщения" storage="scene2" target="ok_label"
storage_cancel="" target_cancel=""]
;Отобразить окно ввода текста.
[dialog type="input" text="Пожалуйста, введите свое имя." storage="scene2" target="ok_label"]
[loadcss]Загружает CSS стили во время игры. Используется, когда нужно изменить внешний вид игры.[loadcss file="./data/others/css/new.css"]
[wait]Останавливает выполнение сценария на указанное время в миллисекундах.[wait time="2000"]
[close]Выполняет выход из игры. Параметр ask - указывает будет ли показано диалоговое окно для подтверждения выхода.[close ask=true]
[preload]Используется для предварительной загрузки графических файлов и аудиофайлов. Если параметр wait установлен в true, то игра будет приостановлена до полной загрузки файла.[preload storage="data/images/apress.jpg" wait=true]

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

Часть III: Расскажем историю с помощью Twine

Теперь продолжим повествования истории с помощью Twine. Вот только, процесс создания игры немного отличается от традиционных методов, и сконцентрирован на работе с текстом. Но благодаря удобному интерфейсу, процесс разработки очень прост и не вызывает сложностей. Например, на рисунке 7-1 показана вся структура третей части игры "Август возвращается". Теперь осталось и нам познакомиться с Twine, начиная с основ и заканчивая более сложными элементами игры.

Рисунок 7-1. Структура проекта игры на Twine

Рисунок 7-1. Структура проекта игры на Twine

Для начала создадим проект на основе формата истории Harlowe. Выбираем вкладку Story, и нажимаем на New. Далее указываете имя, после чего подтверждаем его, нажав кнопку "Create". Нас сразу перебросит на страницу (программу) проекта с минималистическим пользовательским интерфейсом. Дважды щелкнув по первой и единственной комнате (на языке Twine – параграфе), прописываете следующий код:

Спустя минуту зазвонил мобильный телефон. Рон ответил на звонок, - "Рейн?". "Да, это я, [[Рейн]]. Ты уже встретился с Ройстоном? Превосходно."
(set: $variable to 0)

После добавления кода, Twine найдя двойные квадратные скобки автоматически создаст новый параграф "Рейн", а после запуска игры инициализирует переменную $variable. Она в коде на формате истории Harlowe определена при помощи круглых скобок и тега set:.

Шрифты и цвета

Давайте изменим шрифты по умолчанию. Для этого выберите вкладку Story и нажмите Stylesheet. Откроется окно для редактирования CSS стилей игры. Где вставьте следующие строки:

body, tw-story
{
    font-family: Courier New;
    font-size: 22px;
    color: #EE9900;
    text-shadow: 1px 1px 1px #000000;
    background: rgb(194,169,45);
    background: linear-gradient(180deg, rgba(194,169,45,1) 0%, rgba(110,71,71,1) 31%, rgba(0,0,0,1) 86%, rgba(127,117,117,1) 100%);
}

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

Нужно учитывать, что Twine использует только те шрифты, которые установлена на компьютере пользователя. Однако не стоить переживать, так как сейчас все не ограничиваться шрифтом Times New Roman, и операционные системы поддерживают определённый набор самых популярных шрифтов, которые помечаются как web safe fonts (веб-безопасные шрифты) (см. Таблицу 7-22).

Таблица 7-22. Распространенные веб-безопасные шрифты.

ШрифтТип
ArialБез засечек
Arial BlackБез засечек
BookmanС засечками
Courier NewС засечками
GaramondБез засечек
GeorgiaС засечками
HelveticaБез засечек
ImpactБез засечек
TimesС засечками
Times New RomanС засечками
Trebuchet MSБез засечек
VerdanaБез засечек

Продолжим. Второй параграф должен содержать следующий текст:

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

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

tw-link /* Устанавливаем цвет для основной ссылки */
{ color: #ffcc00; }
tw-link:hover /* Устанавливаем цвет для основной ссылки при наведении */
{ color: #ffff00; }
.visited /* Цвет для посещенных ссылок */
{ color: #ffcc00; }
.visited:hover /* Цвет посещённой ссылки при наведении */
{ color: #ffff00; }

Работа с переменными

Пришло время использовать переменные в проекте.

Тебя все еще ищет человек в комбинезоне, если ты попадешь к нему в руки, то всей стране грозит опасность.<br>
Запомни данные [[координаты]]. Там тебе будет гарантирована полная безопасность, когда ты туда доберёшься.
(if: $variable < 3)[[[Что это за место? |местонахождение]]]
(else:)[[[Время выдвигаться. |Путешествие]]]

В данной части кода используется тег if: для проверки значения переменной $variable. Если значение меньше трёх, то будет показана ссылка под названием "Что это за место?", которая ссылается на параграф "местонахождение". Где демонстрируется дальнейшая работа с переменной. Так же обратите внимание на то, как используются круглые и квадратные скобки в данном выражении.

Рассматриваемы тег if: используется вместе с тегом else:. Он выполниться, когда переменная $variable станет больше или равная трём, то есть, когда игрок посетит параграф "местонахождение" три раза. А результатом выполнения будет ссылка с название "Время выдвигаться", которая выполнит переход к параграфу "Путешествие". Обратите внимание на использование символа вертикальной черты (|) для отделения текста ссылки от самой ссылки.

Примечание:

Код в Harlowe обернутый квадратными скобками называется хуком (hooks).

(if: $variable is 0) [Рона заинтересовало это предложение.]
(if: $variable is 1) [Рон задумался о рисках.]
(if: $variable is 2) [Рон все же решатся попасть туда!]
(set: $variable to it + 1)
(link: "Загадочное место.")[(go-to:"телефона")]

Данная часть кода из параграфа "местонахождение" на основе значения переменной $variable показывает соответствующее сообщение и увеличивает значение переменной на единицу. Когда будут показаны все три сообщения, игрок вернётся в параграф "телефона", где ссылка на параграф "местонахождение" замениться на ссылку к параграфу "Путешествие". Через который игрок попадёт в параграф "острову", смотри код ниже:

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

Пользовательские теги

Давайте изменим вид параграфа на основе его содержания. Для это вставьте следующие CSS стили в Story Stylesheet игры:

tw-story[tags~="snow"] {
    background-image:url("https://upload.wikimedia.org/wikipedia/commons/4/44/Bouvet_island_0.jpg");
    background-size: cover;  color: blue;
}
tw-story[tags~="snow"] tw-link {
    color:blue;
}
tw-story[tags~="snow"] tw-link:hover {
    color:white;
}

Данные строки задают стиль основному тексту и ссылкам. Чтобы Twine понимал к какому параграфу применить эти стили используются ключевое слово tags (например: tags~="snow"), то есть стили будут применены к параграфу, у которого указан тег snow. Чтобы добавить тег к параграфу, нужно на панели инструментов воспользоваться кнопкой "+Tag". После чего выбираем тег из выпадающего списка или создаёте новый. В нашем случае добавим тег «snow» для параграфа "Путешествие".

Примечание:

Будьте внимательны к именам тегов в стилях и в параграфах, они должны быть одинаковые. То есть в Twine тег Snow не равен тегу snow.

В результате параграф "Путешествие" покажет игроку синего цвета текст, а на фоне остров с горной местностью, покрытый снегом.

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

Давайте еще добавим несколько стилей для пользовательских тегов в Story Stylesheet:

tw-story[tags~="snow2"] {
    color: ivory;
    background: rgb(34,193,195);
    background: linear-gradient(0deg, rgba(34,193,195,1) 0%, rgba(157,157,157,1) 100%);
}
tw-story[tags~="snow2"] tw-link {
    color:#444;
}
tw-story[tags~="snow2"] tw-link:hover {
    color:white;
}
tw-story[tags~="bunker"] {
    color: white;
    background: rgb(46,46,46);
    background: linear-gradient(0deg, rgba(46,46,46,1) 0%, rgba(157,157,157,1) 100%);
}
tw-story[tags~="hovercraft"] {
    color: white;
    background: rgb(28,142,118);
    background: linear-gradient(0deg, rgba(28,142,118,1) 0%, rgba(157,157,157,1) 100%);
}
tw-story[tags~="bham"] {
    color: cornsilk;
    background: rgb(110,71,71);
    background: linear-gradient(180deg, rgba(110,71,71,1) 20%, rgba(224,230,125,1) 100%);
}
tw-story[tags~="bham"] tw-link {
    color:peru;
}
tw-story[tags~="bham"] tw-link:hover {
    color:wheat;
}
tw-story[tags~="finale"] {
    color: white;
    background: rgb(2,0,36);
    background: linear-gradient(90deg, rgba(2,0,36,1) 0%, rgba(121,9,9,1) 35%, rgba(0,0,0,1) 100%);
}

Инвентарь

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

Сперва, нужно определить массив. Существует два способа сделать это: определить пустой массив или определить массив уже с существующими элементами. Давайте рассмотрим первый вариант.

(set: $items to (a:))

Второй вариант выглядит так:

(set: $items to (a: "ключ", "книга", "зеркало"))

Чтобы вывести содержимое массива виде строки, используется макрос joined:.

В инвентаре находится (joined:", ", ...$items).

Для добавления или удаления элементов из массива используются базовые арифметические операторы:

(set: $items to $items + (a: "ключ"))
(set: $items to $items - (a: "ключ"))

Если нужно проверить наличия какого-нибудь элемента в массиве используется комбинация макроса if: else: и ключевого слова contains.

(if: $items contains "ключ")[Вы использовали ключ, чтобы открыть [[дверь]].]
(else:)[Дверь остается закрытой. Вам нужно найти ключ.]

Примечание:

Инструмент "Инвентарь" в игре будет добавлен позже, а показанный здесь код — это примеры, как работать с массивами в Harlowe.

Вывод инвентаря и футер в Twine

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

(if: $items's length > 0)[Инвентарь: (joined:", ", ...$items).]
(else:)[Инвентарь пуст.]

В результате, данный код выведет строку, в которой будут через запятую перечислены все вещи из инвентаря, но если инвентарь окажется пустым, то игрок увидит сообщение "Инвентарь пуст". Но как показать инвентарь во всех параграфах? Для этого воспользуемся футером. Чтобы его создать, возьмите любой параграф и пометьте тегом footer, а Twine автоматически добавит его в конец каждого параграфа.

Теперь немного усовершенствуем код вывода инвентаря:

<hr>
(if: $items's length > 0)[Инвентарь: (joined:", ", ...$items).]
(else:)[Инвентарь пуст.]

После такой манипуляции футер будет отделён горизонтальной линией от остального контента в игре.

Осталось только определить массив $items. Для этого откройте параграф, с которого начинается игра и добавьте следующий код:

(set: $items to (a: "DVD-диск"))

Обратите внимание, что в массиве инвентаря по умолчанию уже присутствует элемент "DVD-диск". Это тот диск, который Рон спас из горящего офиса в первой части игры.

Возвращаемся к истории

Давайте вернемся к игре и в параграф "остров" добавим следующие строки:

На берегу его встретил человек, но из-за снежной метели был виден только силуэт незнакомца. Он подошел, снял очки и пожал руку Рону – "Добро пожаловать на остров". Незнакомцем оказался умерший коллега <b>Мервин Попплуэлл</b>.
Перейти в [[первую комнату]], [[вторую комнату]], [[третью комнату]] или [[четвёртую комнату]] бункера.

Теперь пометим параграфы тегами для которых были назначены стили в Story Stylesheet (например: snow, snow2, bunker). Так для параграфа "острову" добавьте тег snow2 (см. Рисунок 7-2). Он установит гамму ледяного цвета. Для параграфов комнат бункера добавьте тег bunker, который изменит фон.

Рисунок 7-2. Параграф "Остров" из "Август возвращается"

Рисунок 7-2. Параграф "Остров" из "Август возвращается"

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

Ниже предоставлена таблица 7-23, где указан код для каждой комнаты в бункере.

Таблица 7-23. Структура параграфов в бункере.


Первая комната


В этой комнате очень холодно.
(if: $items contains "USB-флешка")[Комната пуста.] 
(else:)[Вы нашли USB-флешку!(set: $items to $items + (a: "USB-флешка"))] 
Перейти в [[вторую комнату]], [[третью комнату]] или [[четвёртую комнату]].

Вторая комната


Эта комната пустует.
(if: $items contains "Руководство по программированию")[Комната пуста.] 
(else:)[Вы нашли руководство по программированию!(set: $items to $items + (a: "Руководство по программированию"))]
Перейти в [[первую комнату]], [[третью комнату]] или [[четвёртую комнату]].

Третья комната


Эта комната пустует и в ней холодно.
Перейти в [[первую комнату]], [[вторую комнату]] или [[четвёртую комнату]].

Четвертая комната


(if: $items contains "Записка с паролем")[Комната пуста.] 
(else:)[Вы нашли записку с паролем!(set: $items to $items + (a: "Записка с паролем"))] 
Перейти в [[первую комнату]], [[вторую комнату]] или [[третью комнату]].

Когда предметы собраны

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

(if: $items contains "USB-флешка" and "Руководство по программированию" and "Записка с паролем")[Рон собрал всё что нужно для работы на компьютере [[компьютер]].]

Как видно из кода у игрока проверяется, что найдено только три предмета, кроме "DVD-диска". Он уже должен быть, так как, не найдя его игрок погибает в пожаре. Сам код расположим в футере после вывод инвентаря. В результате получим следующий код для параграфа "Инвентарь":

<hr>
(if: $items's length > 0)[Инвентарь: (joined:", ", ...$items).]
(else:)[Инвентарь пуст.]
(if: $items contains "USB-флешка" and "Руководство по программированию" and "Записка с паролем")[Рон собрал всё что нужно для работы на компьютере [[компьютер]].]

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

Рон должен закончить разработку брандмауэра. Исключить наличие уязвимостей, и возможность использовать бэкдоры. Если они будут, то "Фракция" сможет внедрить вредоносный код практически в каждое электронное устройство, что сделает всю страну уязвимой для кибератак. 
"Этот брандмауэр должен быть крепок на столько, чтобы и сам Рон не смог его сломать", — подбодрил его Мервин.
<!-- Очищаем инвентарь -->
(set: $items to (a:))

[[Завершить код]]

Еда и макросы

Давайте еще познакомимся с некоторыми макросами Harlowe. Сперва, вставим следующий код в параграф "Завершить код", и пометим тегом snow2.

Спустя два дня жизни в бункере, Рон начал его воспринимать своим вторым домом. Чувство безопасности, превосходная еда: икра, авокадо с креветками, вафли, всё это больше напоминало рай.
<br>То, что надо для беззаботной жизни.
<!-- Пришло время применить "волшебство" к еде -->
(enchant: "авокадо с креветками", (text-colour: white) + (text-style:'bold'))
(display: "Еда")
Вы идёте [[спать]]

Как видно Harlowe позволяет при помощи макроса enchant: указать стили в самом параграфе. Что касается макроса display:, то он позволяет отобразить в параграфе содержание других параграфов.

Для работы макроса display: создадим параграф "Еда" со следующим содержимым:

(Особенно, вам очень понравились — авокадо с креветками!)

Обратите внимание, что макрос enchant: изменил стиль текста как в исходном параграфе "Завершить код", так и в параграфе "Еда", содержимое которого отобразилось позже за применённый макрос.

Маппинг данных и Наборы данных

Harlowe поддерживает такие структуры данных как маппинг данных (datamaps) и набор данных (datasets). Проще говоря, маппинг данных – это набор данных ключ-значение. Их можно использовать для списков лучших игроков или для хранения характеристик персонажа. Дата-сет – это коллекция данных не имеющих ключей, больше похож на простой список. Определение данных структур производится так же, как и обычные переменные в Harlowe через тег set и знак доллара ($).

Давайте рассмотрим маппинг данных, где доступ к ключам происходит через окончание 's (например: $info's). Но чтобы лучше понять, как работать с данной структурой данных взгляните на код из параграфа "спать", где кроме определения маппинга данных выполняется операции вывода данных и сравнения.

<!-- Определяем мапинг данных с двумя ключами (температура и энергия) и присваиваем им значения -->
(set: $info to (datamap: "temperature", -20, "energy", 5))
<!-- Отображаем структур мапинга данных -->
Уведомление: ваш уровень энергии (print: $info's energy) из 10. Текущая температура (print: $info's temperature) C.
<!-- Проверяем значения ключей и при необходимости отображаем сообщения -->
(if: $info's energy is < 10)[Вы немного устали.]
(if: $info's temperature is > 10)[Очень тепло!]
(else:)[Очень холодно!]<br>

Теперь познакомимся с дата-сетами, и чтобы понять принцип их работы, представляйте, что это одномерный массив. К ним так же применимы такие манипуляторы как is in (находится внутри) и contains (содержит). Как работать с этим типом показано ниже во второй части кода из параграфа "спать". Обратите внимание, что при выполнении кода, сообщение "Также в портфеле есть будильник" не выведется, так как будильник отсутствует в дата-сете.

На третий день Рон слышит странный шум. Сначала кажется, что его источник где-то внутри бункера, но спустя время стало ясно, что он идет с наружи.
<!-- Создаем на основе дата-сета инвентарь -->
(set: $briefcase to (dataset: "карандаш", "ластик"))
<!-- Проверяем присутствие данных в дата-сете используя "is in" и "contains" -->
(if: "карандаш" is in $briefcase)[В портфеле есть карандаш.]
(if: "будильник" is in $briefcase)[Также в портфеле есть будильник.]
(if: $briefcase contains "ластик")[Разумеется, есть и ластик в портфеле.]
(if: $briefcase contains "ластик" and $briefcase contains "карандаш")[Ощущаешь себя хорошо подготовленным.]
[[Выйти наружу.]]

Развлекаемся с массивами

Harlowe имеет множество макросов для манипуляции с массивами. Взгляните на код параграфа "Выйти наружу":

Звук шел от приближающейся к острову маленькой оранжевой точки на горизонте. Лодка?
<!-- Создаем новый массив $array, содержащий четыре строки в случайном порядке -->
(set: $array to (shuffled: "побеге", "карандашах", "рыбе", "Августе"))
Рон заволновался, и задумался почему-то о (print: $array)!
<!-- Создаем ещё один массив, состоящий из простых чисел, и сдвигает их на два элемента -->
(set: $numbers to (a: 1,4,5,8))
И какой пароль от портфеля:  (print: $numbers) или (set: $numbers to (rotated: 2, ...$numbers)) (print: $numbers)?
<!-- Создаем массив, заполнив его строками, и сортируем -->
(set: $bands to (a: 'Abba', 'Megadeth', 'Tiffany', 'Enya'))
В конце концов Рон поймал себя на мысли, что он думает о своих любимых группах: (print: (sorted: ...$bands)).
[[Спрятаться в бункер!]]

Одними из полезных макросов для работы с массивами являются: shuffled: (выбрать случайно), rotated: (сдвиг) и sorted: (сортировка). Как их использовать показано в коде выше. Также для работы с массивом используется оператор расширения виде троеточия (…). Он применяется для вставки всех элементов массива в макрос (например: sorted: ...$bands).

Продолжим повествование, добавив следующий параграф "Спрятаться в бункер!". Не забудьте применить к нему тег bunker.

Корабль причалил к берегу. Рон наблюдая из бункера, увидел как на берег сошла миниатюрная женская фигура в (either: "темно-синий", "модной") зимней куртке и (either: "в темных с большой оправой", "темных") очках.
Подойдя ко входу, она постучала. "Рон, это <b>Рейн</b>. Нам нужно в течении (random: 10,20) секунд покинуть остров!"
[[Подняться на судно]].

Данная часть кода использует два макроса это either:, который выводит случайную строку из указанного списка строк, и random:, который получает случайное целое число в указанном диапазоне.

Больше визуальных эффектов

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

(text-style: "Shudder")[Корабль покинул берег и сейчас несётся по волнам. Как вдруг из тумана выныривает судно. "Это они", — воскликнула Рейн.]<br>
(text-style: "Rumble")[Черный корабль Фракции приблизился и пошел рядом по правому борту, неся на себе дюжину солдат, экипированных толстой арктической одеждой.]
(text-style: "Shudder")[(text-style: "Smear")[Не дожидаясь нападения, Рон и Рейн прячутся в каюте.]]<br>
<!-- Применяем эффект к определённой части текста -->
(set: $string to (text-style: "Shudder")) Однако крепкая дверь $string[начи]$string[нает] [[сходить с петель]].

Как видно, чтобы применить несколько эффектов, нужно делать вложенные блоки. Так же можно поместить эффект в строковую переменную и применить её там, где это необходимо. В таблице 7-24 указаны ещё эффекты, с которыми вы можете поэкспериментировать.

Примечание:

Хорошей практикой использования эффектов является (text-style: “effect name”)[текст], а не использование переменных.

Таблица 7-24. Другие text-style эффекты.

Без анимацииС анимацией
BoldSuperscriptCondenseRumble
ItalicSubscriptShadowShudder
UnderlineMarkOutlineBlink
ExpandNoneFade-in-out

Twine в реальном времени

Игровой процесс на Twine, не ограничивается щелканьем мыши. На нем можно реализовать и напряженную динамическую сцену в реальном времени. Для этого понадобиться макрос под названием live:. Давайте посмотрим, как он работает на примере параграфа "сходить с петель".

Захватчики пытаются прорваться через дверь.

[[Оставайся на месте!]] / [[Открой дверь!]]
(set: $time to 15)
(set: $string to (text-style: "Shudder"))
(set: $string2 to (text-style: "Rumble"))
<!-- Отобразим звуки за дверью каждые 0,8 секунды -->
{(live: 0.8s)[
    (either: "","","Два раза ")
    (either: "$string[Удар!]", "Бам!", "Ага!", "Ух!", "$string2[Бам!]")
]}Осталось |amount>[$time] секунд!
(live: 1.0s) [
    (set: $time to it - 1)
    (if: $time is 0)[(go-to: "Открой дверь!")]
    (replace: ?amount)[$time]
]

Параграф предоставляет игроку выбор виде двух ссылок: правильной - это параграф "Оставайся на месте!" и не правильный "Открой дверь!", который окончит игру. Так же для быстрого принятия решения определяется переменная $time co значение 15. Если она достигнет нуля, то игрока перебросит в параграф "Открой дверь!".

Макрос live: просто выполняет повторно действия через определенный интервал времени. В данной части кода используется два таких макроса: один отображает динамические сообщения каждые 0,8 секунды, а другой уменьшает переменную $time на единицу каждую секунду, и с помощью макрос replace: выводит её значение. Кроме того, в коде используется макрос either:, для генерации случайных сообщений.

Чтобы обратный счётчик таймера был более заметным для игрока сделаем текст жирным и красным цветом. Для этого добавьте следующие стиль в Story Stylesheet:

tw-hook[name="amount"] {
    color: red;
    font-weight: bold;
}

Первое окончание игры

Если игрок решит открыть дверь, пока враг находятся на борту, то игра заканчивается. Код для параграфа "Открой дверь!":

Рон открыл дверь, чтобы сразиться с врагами, но был захвачен в считанные секунды.
(live: 3s) [
    (stop:)
        (text-style: "Outline")[<h1>ИГРА ЗАКОНЧЕНА</h1>]
    (link:"Попробовать сыграть еще раз")[(goto:"Старт")]
]

В коде используется макрос stop: в сочетании с макросом live, для создания 3-секундной задержки перед отображением слов "ИГРА ЗАКОНЧЕНА".

Скрываем вывод инвентаря

В игре есть параграфы, где не нужно отображать инвентарь, а в некоторых это даже излишне. Давайте исправим это. Найдем параграф "Инвентарь" (см. Рисунок 7-3), и усовершенствуем код:

(if: $show_footer is true)[
<hr>
(if: $items's length > 0)[Инвентарь: (joined:", ", ...$items).]
(else:)[Инвентарь пуст.]
(if: $items contains "USB-флешка" and "Руководство по программированию" and "Записка с паролем")[Рон собрал всё что нужно для работы на компьютере [[компьютер]].]
]

Теперь, когда переменная $show_footer равна true, то инвентарь будет показан игроку, если false, то инвентарь будет скрыт.

Примечание:

Будьте внимательны к регистру букв в значениях true и false, так как True это не то же самое, что true.

В результате, чтобы показать или скрыть инвентарь, нужно в параграфе прописать одну из следующих строк кода:

(set: $show_footer to true) <!-- Показать инвентарь -->
(set: $show_footer to false) <!-- Скрыть инвентарь -->

Рисунок 7-3. Окончательный код параграфа "Инвентарь"

Рисунок 7-3. Окончательный код параграфа "Инвентарь"

Первую строку поместим в параграф "Стар", а вторую в параграф "Подняться на судно". Кроме того, помните, переменная $show_footer будет существовать и хранить значение на протяжении всей игры, и её можно изменить в любой момент.

Добавляем прогресс бар

Самый лучший способ представления данных — это их визуализация. Соответственно, давайте добавим прогресс бар в параграф "Оставайся на месте!".

(set: $hose to 100)
Не добившись успехов, враги решили затопись судно и спустя какое-то время в каюту хлынула вода.

Послышался звук вертолета. "Рон слышишь!", — воскликнула Рейн - "К нам спешат на помощь!"

Ройстон спустился на палубу и взялся за плохих парней:
(live: 0.25s)[
    (if: $hose>0)[
        (set: $hose to $hose - (random: 1,5))
    ]
    (print: '<progress value="' + (text: $hose) + '" max="100"></progress>')
    (if: $hose < 10)[ [[Проследовать к вертолёту]]! ]
]

Для реализации данной идеи воспользуемся HTML-элемент <progress>, который вставится в параграф тегом print:.

Приступим к реализации. Определим переменную $hose со значением 100. Она будет передаваться в прогресс бар как значение. Далее используя макрос live, будем случайно уменьшать значение переменной $hose от 1 до 5 каждые 0,25 секунды. Когда значение будет меньше десяти отобразим ссылку на параграф «Проследовать к вертолёту».

Однако стандартный прогресс бар выглядит скучновато. Давайте воспользуемся CSS, чтобы исправить это, и добавим следующий стиль в Story Stylesheet:

progress {
    background-size: 20px 20px, 100% 100%, 100% 100%;
    box-shadow: 5px 5px 5px black;
}

Данный CSS придаст индикатору прогресс бара темно-зеленый цвет и добавит тень смещённую на 5 пикселей по горизонтали и вертикали.

Переходы и вращение

Давайте познакомимся ещё с некоторыми визуальными эффектами. Первый будет transition:, он добавляет к тексту эффект перехода. В коде для примера, к тексту применим эффект пульсации (pulse), что является хорошим способом отобразить заголовки новостей. Второй эффект это text-rotate:, который вращает текст, что придаст больше таблоидности к заголовкам новостей. В конце воспользуемся уже знакомым макросом text-style:, и добавим к тексту тиснение и размытие. Теперь напишем код для параграфа "Проследовать к вертолёту":

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

По дороге Рон из газет узнаёт пугающие новости:
(transition: "pulse") [
    (text-style: "emboss")[
        <h2>(text-style: "smear")[
            (text-rotate: 2)["Резиденция президента на карантине!"]</h2>
            (text-rotate: -2)["Премьер-министр исчез!"]
        ]
    ]
    Закончить читать [[газету]] 
]

Замена ссылки на параграф

При создании параграф "газету", воспользуемся макросом click-replace:, для создания ссылки "Войти во внутрь..", которая по щелчку вместо себя вставит содержимое параграфа "Резиденция президента".

После долгого путешествия Рон и его команда оказались в столице возле резиденции президента. "Фракции удалось захватить только резиденцию. Мы её окружили. Повсюду стоят снайперы, а район оцеплен. Пока что они требуют только вас" - пояснил Ройстон.

Войти во внутрь..
(click-replace: "Войти во внутрь..")[(display: "Резиденция президента")]

Код параграфа "Резиденция президента":

Двое охранников Фракции открыли дверь парадного входа резиденции, и Рон с Рейн вошли во внутрь. 

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

(link-goto: "Войти в зал", "Зал")

Спрашиваем пользователя

И вот наши герои в зале резиденции президента. Теперь пришло время познакомиться ещё с одним элементом Harlowe – это prompt. Он позволит запросить у игрока пароль. Если он окажется правильным, то игрока перебросит в параграф "Готово", иначе отобразится мигающее сообщение об ошибке с предложением повторить ввод снова. Повторная попытка – это простая перезагрузка страницы, которая произойдёт, когда параграф будет ссылаться сам на себя.

(set: $password="")
В зале их ждали двое: это Клэр из поезда и ее телохранитель в комбинезоне.
"Нам нужен доступ к файрволу Август" - с ходу сообщила Клер и пригрозила - "Иначе мы убьем премьер-министра и с помощью электромагнитного импульса отправим эту страну в каменный век".

Человек в комбинезоне подошёл к Рону и протянул ему планшет.
> Ввести пароль.(click: "пароль")[
    (put: (prompt: "Введите пароль (это рон):", "") into $password)
    (if: $password is "рон")[(goto: "Готово")](else:)[(text-style: "blink")[> В доступе отказано!](link-goto: "> Попробовать еще раз <", "Зал")]
]

Теперь познакомимся с макросом put:. Это просто другая интерпретация знакомого макроса set:, только с той разницей, что для put: используется другой синтаксис, но в результате они оба объявляют и изменяют значение переменной. То есть вместо (set: $password ...) воспользуемся (put: ... into $password).

Далее создадим параграф "Готово" и прикрепим к нему тег finale.

Рон открыл доступ к системе или убедил врага в этом.

"Отлично, Рон Легион-Смерти", - Клэр улыбнулась. Телохранитель выхватил планшет и передал его Клэр.
"Координаты местонахождения премьер-министра переданы", - она сделала несколько нажатий по планшету - "Вы свободны".

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

"Рон чувствуешь холод? Это сигнал, что нам нужно [[бежать]]!"

А для финального параграфа "бежать" напишем следующий код:

"Мы запустили жидкий азот в вентиляционную систему здания, чтобы понизить температуру внутри. Холод — это их слабое место. А ты молодец!", — Рейн похлопала Рона по плечу, поздравив его с удачным спасением - "Спецотряд уже выдвинулся к месту, где находится премьер-министр. Меньше, чем через час он будет на свободе."
(live: 3s)[(stop:)(transition: "Shudder") [(text-style: "Smear")[<h1>КОНЕЦ</h1>](link:"Начать заново")[(goto:"Старт")]]]
<audio controls autoplay>
    <source src="https://example.com/sample.mp3" type="audio/mp3">
    Тег audio не поддерживается вашим браузером.
</audio>

Неплохо бы в игру добавить звук, но, к сожалению, формат истории Harlowe не имеет таких макросов. Это можно реализовать с помощью HTML или JavaScript. В параграфе "бежать" воспользуемся элементом <audio>, который позволяет воспроизвести звук как автоматически, так и при помощи элементов управления.

Вот и завершили третью часть обучающей игры "Август возвращается" на Twine. Хотя некоторые эпизоды истории остаются ещё не рассказанными, но с основами создания игры на Harlowe мы уже познакомились.

Заключение

В этой довольно насыщенной главе было рассмотрено:

  • Как поделить историю на локации и определить роли персонажей для визуальной новеллы
  • Выстроить структуру взаимодействия комнат в игре.

Изучено следующие возможности Ren’Py:

  • Как работать с переменными и создавать на основе условного оператора ветвления в истории.
  • Как работать со списками и Python
  • Научились применять переходы, аудиовизуальные эффекты, и систему частиц.

В случае с TyranoBuilder узнали:

  • Как использовать TyranoScript при минимальном использовании графического интерфейса и его макросы.
  • Как применять переходы для персонажей
  • Как работать с Live2D

О Twine узнали следующее:

  • Какие возможности у формата истории Harlowe и какие существуют макросы.
  • Как создавать связи между параграфами, а также как их стилизовать при помощи каскадных таблиц стилей (CSS).
  • Как переменные влияют на события в игре
  • Как при помощи базовых структур данных, создать систему инвентаря.
  • Как оживить игру используя макрос live