Реализация игровых механик ВН на движке GDevelop 5

Реализация игровых механик ВН на движке GDevelop 5

Для создания "чистокровной" визуальной новеллы старины Ренпая хватает за глаза. Но если хочется неплохо так выйти за рамки, невольно поглядываешь в сторону игровых движков широкого профиля. Они дают небывалую свободу и удобство в плане реализации любых, даже безумных идей, но есть один минус — традиционные для ВН элементы приходится программировать почти с нуля. В данной статье мы разобьём этот камень преткновения на пути к освоению новых горизонтов. Вернее конкретного горизонта, на котором маячит весьма любопытный движок GDevelop 5.

Статья актуально для версии 5.0.0

__________________________
__________________________
__________________________
ПАРА СЛОВ О ДВИЖКЕ

Чем же он так хорош, и хорош ли вообще? Стоит ли он внимания? Что же, всё познаётся в сравнении. Так что приведу список его преимуществ и недостатков сравнительно с Ren'Py. Разумеется, так делать не совсем корректно (как-никак движки разной направленности), но всё же:

  • Есть инструментарий для создания 2D игр любой направленности.
    Платформер, адвенчура, гонки — на GDevelop можно сделать всё. Для этого в нем есть физика, коллизия объектов, работа с инвентарём и много чего ещё. Список можно продолжать ещё долго.
  • Не нужно заучивать синтаксис поскольку вы не прописываете команды вручную, а просто выбираете их из списка. Причём список этот хорошо организован и найти нужную функцию в нём довольно легко.
  • Интуитивно понятный. Большинство возможностей софтины можно освоить вообще без Интернета. Просто ковыряясь в движке.
  • Оптимизирован. Игра не тормозит даже с обилием покадровой и процедурной анимации. Да, Ren'Py, так можно было.
  • Мультиплатформенный. Позволяет без танцев с бубном собрать билд не только на Windows, MacOS и Linux, но также на Android и HTML5 (чтобы играть прямо в браузере). Кстати, для Андроида в движке предусмотрены специфические команды, что позволяют игре управлять вибрацией телефона и ориентацией экрана.
  • Есть визуальный редактор сцены. Можно расставлять элементы прямо на экране, а не прописывать координаты вручную. Чертовски удобно.
  • Широкие возможности для создания крутого визуала. Тут вам и продвинутая система частиц, и движение объектов по траектории, и множество эффектов (размытие, помехи, белый шум, etc). Последние можно не только комбинировать, но и тонко настраивать.
    TODO:Видео-превью.
  • Есть библиотека бесплатных объектов. Преимущественно под jRPG, но для быстрого прототипирования сгодится.
  • Присутствует встроенный графический редактор для пиксельной графики.
  • Не заточен под ВН. Диалоговая система больше склоняется ко всё тем же jRPG. Но не волнуйтесь, мы с этим сейчас разберёмся.
  • Сравнительно меньше обучающих материалов, особенно на русском.
  • Позволяет делать не больше двух билдов в день и показывает свой логотип перед каждым запуском игры. Чтобы избавиться от этого ограничения можно оформить платную подписку.

Прочитав такое сравнение, у вас может возникнуть закономерный вопрос: а почему не Unity? Что же, отвечу ещё одним сравнением, и да поправят меня «юнитологи» если где ошибаюсь:

  • Легковесный. Занимает всего 100 МБ, а не 2 ГБ. Гораздо меньше грузит систему при работе. Плюс к этому не требует установки и регистрации.
  • Легче в освоении. Опять-таки за счёт своей интуитивно понятной системы.
  • Не нужно делиться прибылью от продаж с разработчиками движка.
  • Не поддерживает 3D графику.
  • Использует нетрадиционный подход к программированию. Это и плюс, и минус движка. Одни вещи программировать на нём проще, другие -- сложнее.

Посмотреть на официальном сайте
Скачать движок с официального сайта
P.S. После обновления дизайна anivisual картинки-гиперссылки отказываются работать. Пока что.
По сему картинка — отдельно, ссылка — отдельно.


__________________________
__________________________
__________________________
ОБЩИЕ ПРИНЦИПЫ РАБОТЫ В СРЕДЕ

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

Как только создаём сцену, движок показывает нам визуальный конструктор. Он состоит из самой сцены и пяти панелей. Каждую можно скрыть/раскрыть по желанию:
  • Панелька Objectsвыступает в роли эдакой палитры. В ней мы создаём объекты (текстового типа, тайлового, etc) и можем добавлять их на сцену.
  • Чтобы упростить программирование, различные объекты можно объединять в группу. Для этого предусмотрена панель Object Groups. Благодаря такой панели мы можем объединить картинку текстбокса, неймбокс и текстовую область в единую группу чтобы потом показывать\скрывать её одной командой.
  • Если кликнуть на объект, что располагается на сцене, его можно настроить с помощью панели Properties. Задать координаты, размеры, угол наклона, порядок отображения, слой, и даже встроить в объект переменные.
  • А перечень всех объектов на сцене можно посмотреть на панели Instances List.
  • Также есть возможность создавать слои (прямо как в Photoshop). Для этого предусмотрена панель Layers. Это даёт возможность разместить фон на одном слое, спрайты на другом, а интерфейс на третьем и они не будут «налезать» друг на друга, какая бы вакханалия не творилась в коде игры.


    Жмякнув на вкладку (Events) мы переходим в программный конструктор и тут начинается интересное:

    Дело в том что GDevelop, как было написано выше, использует своеобразный подход к программированию. Сейчас объясню.
    Весь код игры состоит из горизонтальных блоков, разделённых пополам (левая часть — условие, правая — действие). И каждый кадр (то есть 50-60 раз в секунду) движок просматривает все блоки. Если условие блока соблюдено, исполняется указанное в нём действие. Давайте лучше покажу схематически. Смотрите:

    Такой блок говорит движку: каждый раз когда пользователь нажимает клавишу «+» переменную Х нужно увеличить на единицу и вывести её на экран. (Количество условий и действий в рамках одного блока не ограничено). Теперь давайте уберём условие вовсе:

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

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

    Теперь движок поймёт инструкцию так: если курсор наведён на кнопку — подсветить её; но если курсор наведён на кнопку и при этом ещё и нажата ЛКМ — увеличить переменную Х и вывести её на экран.
    (Сделать блок вложенным можно «ухватившись» мышкой за его левый край и «потащив» вправо).


    Теперь, раз общие принципы ясны, приступим к практике и начнём делать базу игровых механик ВН. Эдакий упрощённый скелет, на который можно наращивать сколько угодно функционала и визуальных эффектов. Однако для такого вот наращивания нужно уметь работать с движком и понимать устройство самого скелета. Так что если вы уже на «ты» с данной софтиной — можете сразу переходить в конец статьи, качать архив с проектом и смело разбираться в коде. А если же с программой вы не состоите в столь близких отношениях, то не волнуйтесь — я наглядно покажу как эта база создавалась и попутно станет ясно как в целом работать с движком. Именно покажу, потому что одним текстом тут не обойтись. Смотрим видео, читаем пояснение, познаём тайны Вселенной. Поехали.


    ДИСКЛЕЙМЕР

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


    __________________________
    __________________________
    __________________________
    СЦЕНА


    Итак, набросаем самую элементарную сцену. Тут у нас фон BG, текстбокс ADVImage, область для вывода текста ADVText и область для вывода имени говорящего NameText. Как видите BG и ADVImage это объекты типа Sprite. Они представляют из себя не просто изображения а контейнеры для графики. В один такой можно запихнуть целый набор картинок и даже покадровых анимаций. Так в BG у нас загружено все три фона — каждый со своим именем. В дальнейшем мы сможем переключать их программно, обращаясь по этим именам.
    А вот объекты ADVText и NameText принадлежат типу BBText. Он не только выводит текстовые строки на экран, но позволяет использовать bb-теги для форматирования:
    Код

    [b]жирный[/b]
    [i]курсив[/i]
    [color=#hexColor]цветной[/color]
    [font=times]с заданным шрифтом[/font]
    [size=23]размером 23 пункта[/size]
    [outline=#hexColor]с обводкой[/outline]
    [shadow=#hexColor]с тенью[/shadow]
    [spacing=12] с межбуквенным интервалом 12[/spacing]
    [align=center] выровненный по центру (можно ещё по левому или правому краю)[/align]

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

    __________________________
    __________________________
    __________________________
    ВЫВОД ТЕКСТА ADV (УПРОЩЁННЫЙ)

    Теперь надо заставить движок выводить полотна текста, показывать спрайты и так далее.
    Каков принцип? Вспоминаем как всё устроено в RenPy:
  • есть файл скрипта (формата *.rpy) в котором содержится текст новеллы, команды (show, soud, etc) и ветвление (menu/jump/label);
  • есть встроенный редактор скриптов (Atom, JEdit);
  • и есть сам движок, который интерпретирует написанное в скрипте.

    В случае с GDevelop ситуация на удивление схожа:
  • также есть файл скрипта, только в формате *.json;
  • имеется встроенный редактор. Зовётся это чудо Yarn и его прелесть состоит в том что можно не только прописывать текст, но и схематично видеть все сюжетные ветки. Собственно они так и называются – Branch;
  • ну и сам движок GDevelop, естественно, интерпретирует команды из файла. Однако что названия команд, что действия при их вызове мы должны задавать сами.

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



    Будем идти от простого к сложному. Самая примитивная версия вывода текста имеет следующие блоки:

    [БЛОК 1] Если сцена запустилась – загрузить файл script.json в память игры, а в качестве стартовой диалоговой ветки выбрать Старт (собственно, других веток пока и нет).
    [БЛОК 2] Если текущая строка текстового типа (а значит не команда и не вариант ответа, ведь для них будут отдельные блоки) — вывести считанную строку на экран через текстовый объект ADVText.
    [БЛОК 3] Если пользователь щёлкнул левой кнопкой мыши — считать следующую строку.


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

    [БЛОК 1] Остаётся без изменений.
    [БЛОК 2] Вместо вывода строки полностью DialogueTree::LineText() говорим движку выводить строку, считанную побуквенно DialogueTree::ClippedLineText(). И добавляем действие Scroll clipped text (считать следующую букву).
    [БЛОК 3] Также остаётся нетронутым.


    Отлично, справились. Теперь усложним задачу и возьмём контроль над скоростью текста, а то как-то слишком быстро выводится. Для этого действие Scroll clipped textпривяжем к таймеру:

    [БЛОК 2] Теперь только выводит строку, считанную побуквенно.
    [БЛОК 3] Как только значение таймера delay переваливает за 0.05 секунды — считывается следующая буква, а сам таймер перезапускается.

    Примечательно что таймеры не нужно где-то предварительно объявлять и запускать. При старте игры движок сканирует все блоки и, если видит упоминания о таймере, — тут же самостоятельно его создаёт и запускает.
    Ну и в качестве задержки вывода символов мы можем поставить любое значение, не обязательно 0.05 секунды. Или вовсе задать переменную и дать игроку её настроить. Но погоди, -- скажут опытные пользователи движка, – ведь есть же для текстовых объектов поведение Auto typing text; зачем же столько кода прописывать? А затем что далее нам нужно будет программировать прокрутку и авточтение, а с этими функциями Auto typing text дружить не хочет.

    Вы можете скачать файлы проекта в текущем виде с GoogleDrive:

    Скачать

    __________________________
    __________________________
    __________________________
    ВЫВОД ТЕКСТА В ADV (ПОЛНОЦЕННЫЙ)


    Что же, теперь у нас вроде всё чётко и красиво, но есть одна проблема: если строка вывелась на экран не полностью и мы щёлкаем мышкой, то она стирается и на её месте начинает выводиться следующая. А нужно чтобы недопоказанная строка после щелчка выводилась целиком.
    Для этого вводим булеву переменную disturbed. Это такой себе индиткатор, показывающий движку нажали мы ЛКМ во время вывода строки (true) или уже после того как строка целиком вывелась на экран (false):

    [БЛОК 4->5->6] Если щелкнули мышкой после того как строка была полностью считана (Clipped text has completed scrolling), а disturbed говорит что вывод строки не прерывался — начать считывать следующую строку.
    [БЛОК 4->5->7] Если щелкнули мышкой после того как строка была полностью считана, а disturbed говорит что ранее вывод строки был прерван — следующую строку считывать не нужно. Вместо этого поменять значение индикатора на противоположное (Toggle the Boolean variable)
    [БЛОК 8->9->10] Если нажата левая кнопка мыши и при этом строка не выведена полностью, а индикатор в состоянии false — принудительно считать строку полностью и переключить индикатор.

    Примечание: если вы ничего не поняли, это нормально. Признаться, я и сам не до конца понимаю каким чудом оно работает. Этот участок кода я писал в… не совсем нормальном состоянии. Просто если будете модифицировать программу — не трогайте блоки с четвёртого по десятый. Вот.

    Скачать

    __________________________
    __________________________
    __________________________
    ПРОКРУТКА И АВТОЧТЕНИЕ


    Смотрим как устроен механизм прокрутки:

    [БЛОК 1] Если нажат один из Ctrl-ов (именно нажат, то есть блок срабатывает не разок по нажатию, а каждый кадр пока кнопка вдавлена) — показать текущую строку полностью.
    [БЛОК 1->2] А если при этом таймер scrollDelayотсчитал 0.2 секунды — перейти ко следующей строке и перезапустить таймер. Такая задержка нужна чтобы текст не пролистывался со скоростью света. Ну и, как видите, названия таймеров для чтения и для прокрутки разные.



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

    [БЛОК 1] Нажатие «а» на клавиатуре переключает значение булевой переменной autoreading. То есть если во время нажатия она была false, то после становится true. И наоборот.
    [БЛОК 2] Если авточтение включено (autoreading is true) и при этом после вывода строки прошло 2 секунды – начать отсчёт заново и перейти ко следующей строке.
    Формула StrLength(DialogueTree::LineText())/60+StrLength(DialogueTree::LineText())*0.05+2 означает «время на вывод всех символов строки без задержки + задержка между символами + 2 секунды».

    Скачать

    __________________________
    __________________________
    __________________________
    КОМАНДЫ


    Команды в редакторе Yarn имеют вот такой формат:

    Мы сами придумываем название команды, сами решаем сколько ей нужно параметров (ставятся через пробел) и сами объясняем движку что он должен делать когда эту команду считывает.
    Неймбокс

    Давайте начнём с простого, а именно: со смены имени говорящего в неймбоксе.
    Итак, назовём нашу команду name, и укажем ей два параметра: имя говорящего и цвет. Для наглядности на видео я показал три имени: Красный, Зелёный и Синий. Отображаются имена соответствующими цветами, поэтому команды для смены имён выглядят так:
    <<name Красный ff0000>>
    <<name Зелёный 00ff00>>
    <<name Синий 0000ff>>

    Но движок не знает что с ними делать, так что создаём блок для интерпретации команды name:

    Если считанная строка является командой смены имени (Command <<“name”>> is called) то изменить текст неймбокса на текст первого параметра и залить его цветом, указанным во втором параметре. Перед именем ставим широкий пробел, а после имени – переход на следующую строку. Это понадобиться нам позже, когда будем программировать режим NVL.
    Фон

    Теперь можем создать команду для смены фонового изображения. Помните, в объект BG мы загружали сразу все три фона и каждому вписывали имя? Это даёт нам возможность менять фон "поимённо". Для этого в Yarn прописываем команду bg и в качестве параметра указываем имя фона, который нужно отобразить. Затем в самом движке создаём такой блок:

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

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

    Замечу что Track_1, Track_2 и Sound_1 это придуманные мною сокращения (чтобы в скрипте не прописывать название композиции целиком). Можете придумать каждой композиции своё сокращение.
    Спрайты

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

    Для начала загрузим спрайты в движок. У нас два персонажа, по три спрайта на каждого. Соответственно – делаем два объекта содержащих по три картинки. Обратите внимание что изображения эти разных размеров, так что если будем задавать положение спрайтов относительно их левого верхнего угла, то для правильного позиционирования придётся под каждого персонажа прописывать свою громоздкую формулу. Чтобы упростить себе жизнь переставим опорную точку по центру нижней границы изображения. Так мы сможем задавать положение относительно неё. На видео всё есть.
    Далее пропишем в скрипте две команды:
  • <<show [персонаж] [эмоция] [координаты по оси Х]>> — создаёт выбранного персонажа и размещает его на экране. Также она меняет его эмоцию/координаты если перс уже был создан. Конечно, можно создать для этих целей отдельную команду, но, опять же, не хочу захламлять код.
  • <<hide [персонаж]>> — удаляет персонажа со сцены.
    А в движке пропишем следующие блоки:

    Скачать

    __________________________
    __________________________
    __________________________
    ВЫВОД ТЕКСТА В NVL


    Теперь мы можем не только запрограммировать режим отображения текста NVL, но и прописать команду для переключения между ним и ADV.
    Для этой затеи нам нужно создать только два объекта: изображение текстбокса NVLImage и текстовое поле NVLText. Перетаскиваем их на сцену и объединяем в группу NVLGroup.
    В скрипте прописываем команду смены режима:
    <<text_mode NVL>> — перейти в режим NVL
    <<text_mode ADV>> — перейти в режим ADV

    А в движке так:

    Как видите, для работы NVL нам нужно ввести целых три новые переменные:
  • mode — хранит в себе текст первого параметра команды text_mode. Благодаря этой переменной движок будет знать какой режим отображать в данный момент.
  • textReaded — по мере чтения в неё дописываются прочтённые строки вместе с именами. В NVLText выводится текст этой переменной + текущее имя + текущая строка. При вызове команды text_mode переменная очищается.
  • lineCounter — хранит количество строк на экране (чтобы textReaded не вылезала за нижнюю границу экрана). При вызове команды text_mode переменная сбрасывается до нуля.

    Теперь вернёмся в начало программного кода и модифицируем блоки отображения текста:

    Тут нам нужно добавить блоки 2 и 3. Они будут каждый кадр «подглядывать» в переменную mode и, в зависимости от её значения, показывать/скрывать текстбоксы ADVGroup и NVLCroup.
    В конец добавляем блок 12, в котором сказано: как только количество строк на экране перевалит за 6 — очистить переменную textReaded и сбросить счётчик lineCounter.
    В блок 7 добавляем два действия: дописать в textReaded прочтённую строку, имя говорящего и пустую строку; добавить 1 ко счётчику lineCounter.
    Ну и не забываем в блоке 1 вывести через текстовое поле NVLText всё что накопилось в textReaded + текущее имя говорящего + текущую строку, считанную побуквенно.

    Авточтение и прокрутку также нужно научить работать в режиме NVL:

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

    __________________________
    __________________________
    __________________________
    МЕХАНИКА ВЫБОРОВ (БАЗОВАЯ)


    Выборы в Yarn прописываются следующим образом:
    [[ Текст выбора | Ветка, на которую он ведёт ]]
    Стоит вписать вариант ответа и редактор любезно создаст все нужные ветки диалога.
    Однако пока что движок не знает как эти выборы отобразить на экране и как игрок будет с ними взаимодействовать. Нужно ему объяснить. Если прошерстим всяческие видео-туториалах, то увидим что эту задачу реализуют в манере jRPG – варианты ответа выводятся в текстовое поле и пользователь выбирает их стрелками вверх/вниз на клавиатуре. Для ВН такой подход не подходит (уж простите за каламбур). Так что будем отображать выборы через кнопки интерфейса и позволим игроку щёлкать по ним мышкой.

    В таком случае нужно сразу определиться: какое максимальное количество вариантов ответа будет на экране одновременно. Я решил что 3 (можно и больше, но не хочу загромождать код). А раз так — создаём в панели Objects три пары объектов кнопка+текст.
    Возвращаемся в программный конструктор:

    [БЛОК 1] Если текущая строка является вариантом ответа – скрыть текстбоксы, создать на сцене кнопки выборов, а их текст выровнять по центру.
    [БЛОК 2->3] Если игрок нажал ЛКМ и курсор при этом находился на первой кнопке — выбрать первый вариант ответа.
    [БЛОК 2->4] Если игрок нажал ЛКМ и курсор при этом находился на второй кнопке — выбрать второй вариант ответа.
    [БЛОК 2->5] Если игрок нажал ЛКМ и курсор при этом находился на третей кнопке — выбрать третий вариант ответа.
    [БЛОК 6->7] Если игрок отпустил ЛКМ и курсор при этом находился над одной из трёх кнопок — подтвердить выбор.
    [БЛОК 8] Нужен для переключения на выбранную ветку.

    Пропишем ещё две дополнительные группы блоков. Одну для подсветки текста кнопок при наведении, а другую — для удаления кнопок с экрана:

    [БЛОКИ 1, 2, 3] Если курсор наведён на кнопку — подсветить её текст.
    [БЛОКИ 4, 5, 6] Если курсор не наведён на кнопку — убрать подсветку.
    [БЛОК 7] Удаляет с экрана кнопки выборов если выбор уже сделан.
    [БЛОК 8] Вот тут интересно. Если вариантов ответа только два, а не три — данный блок удаляет третью кнопку с экрана. А если вариант ответа вообще один, то он не показывается на экран, а сразу выводит игрока на указанную ветку. Получается эдакий аналог ренпаевского jump <label>.

    __________________________
    __________________________
    __________________________
    МЕХАНИКА ВЫБОРОВ (ПРОДВИНУТАЯ)


    Теперь усложним задачу введя систему лавпоинтов и условных операторов. Для этого в Yarn есть целый набор готовых команд:
  • <<set $X to 1>> — создаёт переменную X со значением 1
  • <<set $X to $X+1>> — добавляет к переменной X единицу
  • <<if $Х == 1>> — если переменная Х равняется 1 то перейти к следующим строкам
  • <<else>> — если условие if не выполнено
  • <<endif>> — обозначает конец условия if

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

    Теперь смотрим что прописано в каждой из веток.

    Start:


    <<text_mode ADV>> ......................................Устанавливаем режим ADV
    <<show Hana neutral 200>>..........Показываем спрайт Ханны в левой трети экрана
    <<show Mayo neutral 600>> ..........Показываем спрайт Майо в правой тети экрана
    <<name>>.......................................................Очищаем неймбокс
    Здравствуй, путник.
    С кем бы ты хотел прогуляться?

    [[С воительницей Ханной|With_Hanna]].......Этот ответ ведёт на ветку With_Hanna
    [[С волшебницей Майо|With_Mayo]]............Этот ответ ведёт на ветку With_Mayo


    With_Mayo:


    <<hide Hana>>.....................................Убираем с экрана спрайт Ханны
    <<show Mayo smile 400>>..............Радостную Майю выставляем по центру экрана
    <<name Майо f0664d>>
    Правильно сделал.
    Иначе я бы превратила тебя в лягушку.
    <<set $MayoLovepoint to 1>>.................Устанавливаем лавпоинт Майи равным 1
    [[go to:|Place_Choise]...........................Переходим на ветку Place_Choise


    With_Hanna:


    <<hide Mayo>>.......................................Убираем с экрана спрайт Майо
    <<show Hana smile 400>>..............Радостную Ханну выставляем по центру экрана
    <<name Ханна 66f04d>>
    Молодец что выбрал меня.
    Иначе я бы тебя укокошила.
    <<set $HanaLovepoint to 1>>................Устанавливаем лавпоинт Ханны равным 1
    [[go to:|Place_Choise]]....................Также переходим на ветку Place_Choise


    Place_Choise:


    <<name>>
    Теперь выбери, где вы будете прогуливаться.
    [[Днём в лесу|Forest]]
    [[Вечером под луной|Moon]]


    Forest:


    <<bg Лес>>...................................................Сменить фон на «Лес»

    <<if $HanaLovepoint == 1>>.........................Если в начале мы выбрали Ханну
    ___Обожаю лес.
    <<else>>..........................................Если в начале была выбрана Майо
    ___<<show Mayo angry 400>>
    ___Не люблю лес.
    <<endif>>

    <<set $HanaLovepoint to $HanaLovepoint+1>>
    ............................В любом случае к лавпоинтам Ханны прибавляется единица
    [[go to:|The_End]].............................Переходим на конечную ветку The_End


    Moon:


    <<bg Луна>>

    <<if $MayoLovepoint == 1>>...........................Если в начале мы выбрали Майо
    ___Красотища.
    <<else>>..........................................Если в начале была выбрана Ханна
    ___<<show Hana angry 400>>
    ___Ну и мрачняк.
    <<endif>>

    <<set $MayoLovepoint to $MayoLovepoint+1>>
    ..............................В любом случае к лавпоинтам Майо прибавляется единица
    [[go to:|The_End]]..............................Переходим на конечную ветку The_End


    The_End:



    <<if $HanaLovepoint == 2>>............................Если с Ханной погуляли в лесу
    ___Ты покорил сердце Ханны.
    <<else>>
    ___<<if $MayoLovepoint == 2>>........................Если с Майо погуляли под луной
    ______Ты покорил сердце Майо.
    ___<<else>>..............................................А если что-то пошло не так
    ______Сегодня ты никого не покорил.
    ___<<endif>>
    <<endif>>


    Скачать

    __________________________
    __________________________
    __________________________
    ИСТОРИЯ


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

    [БЛОК 1] На старте игры очистить переменную historyStrings. Переменная эта глобальная, то есть доступна для всех сцен проекта.
    [БЛОК 2->3] Если текущая строка текстовая (а не команда или вариант ответа) и при этом полностью считалась — добавить её в historyStrings.
    [БЛОК 4] Если нажата клавиша “h” — перейти на сцену с историей.

    В history scene блоки будут следующие:

    [БЛОК 1] При переходе на сцену — отобразить содержимое historyStrings и пролистать полученное полотно текста в самый конец.
    [БЛОК 2->3] Если пользователь крутнул колёсико мыши вверх и первая строка текста не ушла ниже 50 пикселей от верхнего края экрана — сдвинуть текстовую область на 50 пикселей ниже.
    [БЛОК 4->5] Если пользователь крутнул колёсико мыши вниз и последняя строка текста не ушла вверх от нижнего края экрана — сдвинуть текстовую область на 50 пикселей выше.
    [БЛОК 6] Если в экране истории пользователь нажимает клавишу “h” — возвращаемся к предыдущей сцене.

    Готово. В этом финальном проекте есть все вышеописанные функции с примером их реализации:

    Скачать
    18
    Декабрь
    13
    4.6
    1380
    Добавлять комментарии могут только зарегистрированные пользователи.

    Комментарии к записи: 13

    avatar
    #1 Lisper в 10:48 (04/Дек/2021) 7
    А следующую статейку хочу посвятить 3D CG. Как это делается, почему так жутко выглядит, и как можно исправить ситуацию.

    Ну и на карандаше стоит движок Game Maker Studio 2. Его также хочу хорошенько пощупать и поделиться впечатлениями.
    avatar
    #10 Vertikali в 23:32 (06/Дек/2021) 0
    Было бы интересно.
    .
    А я собираю примеры движков и конструкторов, на которых разрабатывают текстовые игры и визуальные новеллы. Вот такой список: Inform, ink, Quest Soft Player (QSP), Ren`Py, RPG Maker, Twine, TyranoBuilder, Unity + Fungus, Unity + Naninovel, Godot, Construct 2.
    avatar
    #11 Vertikali в 23:37 (06/Дек/2021) 0
    Не знаю, на сколько это видео (Ютубе) может быть полезно, но...
    .
    Game Maker 8.0 - 1 Урок. Создание новеллы - Есть идея, что теперь?
    9 дек. 2015 г
    https://youtu.be/ucJ9Xs4sMpY
    avatar
    #2 Daenmor в 16:29 (04/Дек/2021) 3
    Очень полезная статья, однозначно лайк. Заинтересовалась прогой  nwhat
    avatar
    #3 Katruna в 16:46 (04/Дек/2021) 2
    Ура! Дождалась!
    avatar
    #4 Katruna в 16:46 (04/Дек/2021) 2
    Спасибо большое!
    Как будет время свободное- буду вдумчиво изучать всю предоставленную информацию!)
    avatar
    #5 Хемуль в 17:16 (04/Дек/2021) 1
    Полезная статья! Правда, мне как пиратскому фан-переводчику сильнее всего интересно, насколько легко новеллы на данном движке взламываются для перевода?
    avatar
    #6 Raspberry в 19:48 (04/Дек/2021) 4
    Вот ни разу я не программист, но статью читала взахлеб.
    Пасибки.   nlove
    avatar
    #7 Gremlin12 в 00:55 (05/Дек/2021) 3
    Понятно и подробно объясняешь, думаю, что разработчикам это пригодится. Нравится, что в этой проге можно еще и сразу пиксельную графику делать, то есть возможности сделать простую игру без дополнительных софтин. Не стал себя грузить всей этой информацией, так как не собираюсь что-то пилить в этом конструкторе, но лайк за интересный контент.
    avatar
    #8 Fictional в 21:07 (06/Дек/2021) 1
    Очень много букв, но всё понятно и плотно. Наконец-то ещё одно произведение искусства, среди кучи бесполезных блогов. 
    »Мы заостряем внимание на таких вещах, их не замечают, о них молчат, но их надо знать.«
    /\_/\
    \'__'/
    avatar
    #9 Vertikali в 23:22 (06/Дек/2021) 1
    Как будто объединили Construct 2 с Twine. Мне любопытно. Можно в этом конструкторе создавать составные переменные, что-то типа массивов, списков, кортежей? И как там на счёт визуальных эффектов? В RenPy например, для эффекта перемещения камеры, разрабы пилят 2.5D пространство.
    avatar
    #12 conceptualist в 02:51 (10/Янв/2022) 0
    Спасибо, очень подробный и толковый обзор. Вообще-то у меня не было планов осваивать еще какой-то новый движок, но с таким руководством прямо очень сильный соблазн...
    avatar
    #13 Teashrock в 22:27 (17/Янв/2022) 0
    Цитата
    Весь код игры состоит из горизонтальных блоков, разделённых пополам (левая часть — условие, правая — действие). И каждый кадр (то есть 50-60 раз в секунду) движок просматривает все блоки.
    Иными словами: чем больше скрипт, тем больше задержка между кадрами.