Архитектура квеста
Скриптов нет. Логика есть: как устроены ноды и переменные в Romergo
Когда говорят о логике в игровом редакторе, обычно быстро приходят к скриптам. Где-то автор пишет JavaScript, где-то Python, а где-то изучает собственный язык движка. Это даёт почти неограниченную свободу, но вместе с ней приносит синтаксические ошибки, зависшие циклы, несовместимые плагины и код, который через полгода страшно открывать даже его автору.
В Romergo я пошёл другим путём. Произвольных скриптов внутри квеста нет. Нельзя вставить функцию, сделать сетевой запрос или получить прямой доступ к плееру. Вместо этого история собирается из ограниченного, но достаточно выразительного набора: логических нод, переменных, условий и эффектов.
Это не значит, что в Romergo можно делать только простые развилки. Можно запоминать решения игрока, считать очки, открывать и скрывать варианты, выбирать разные маршруты в следующих главах, строить случайные события и создавать интерактивные интерфейсы. Просто вместо команды «выполни этот код» автор описывает «при таком состоянии должно произойти вот это».
Граф уже является программой
Самая базовая логика Romergo находится прямо на карте главы. Сцены соединяются переходами, а специальные ноды решают, куда пойдёт прохождение дальше.
Если сильно упростить, маленький квест выглядит так:
Старт
↓
Разговор с охранником
↓
Есть пропуск?
├─ да → Закрытый архив
└─ нет → Обходной путь
Это уже исполняемая логика. Player входит в стартовую ноду, показывает сцену, читает состояние истории и выбирает подходящий переход. Автор видит тот же маршрут как понятную карту, а не как набор if, goto и служебных идентификаторов.

*Фрагмент реального Flow в Builder: сцена Final Accusation передаёт решение в Switch, затем несколько If / Else проверяют состояние и разводят историю к разным концовкам. Открытая панель Logic внизу показывает весь доступный набор: Dead End, Checkpoint, If / Else, Switch, Random и Teleport.*
В панели логики сейчас есть шесть основных нод:
- **Конец игры** останавливает текущий маршрут. Это может быть поражение, плохая концовка или просто намеренно закрытый исход.
- **Чекпоинт** сохраняет состояние в этой точке и начинает новую историю возврата. Игрок сможет вернуться к чекпоинту, но не откатиться за него.
- **Если / Иначе** проверяет одно правило. Истинный выход ведёт по совпавшему условию, ложный остаётся запасным маршрутом.
- **Переключатель** подходит, когда вариантов больше двух. Его ветки проверяются сверху вниз: срабатывает первое совпавшее условие, а ветка без условия становится вариантом по умолчанию.
- **Случайный** выбирает один из подключённых выходов. Случайность хранит seed и шаг в состоянии прохождения, поэтому сохранение можно восстановить без хаотичного перебрасывания игрока на другой маршрут.
- **Телепорт** сам ничего не решает. Он помогает разорвать длинную визуальную связь и аккуратно соединить далёкие части большой карты.
Старт и Финиш обрамляют главу, обычные сцены показывают содержание, а логические ноды превращают их в маршрут. Уже на этом уровне можно собрать линейную историю, развилку, несколько концовок, случайное событие и безопасную точку возврата — без единой строки скрипта.
Переменная — это память истории
Одного графа недостаточно, если история должна помнить, что произошло раньше. Для этого в квесте есть переменные трёх простых типов:
booleanхранит факт: найден ли ключ, солгал ли герой, включена ли тревога;numberхранит число: доверие, здоровье, количество улик или очки репутации;stringхранит выбранное значение: имя маршрута, фракцию или код решения.
У каждой переменной есть ключ, понятная автору подпись и начальное значение. Числовой переменной можно дополнительно описать минимум и максимум, а служебную переменную — скрыть из панели состояния игрока или показывать только при выполнении условия.
Главное здесь то, что переменные принадлежат прохождению всего квеста, а не одной сцене. Если в первой главе игрок получил пропуск, в четвёртой главе можно проверить тот же флаг. При переходе через Финиш в следующую главу состояние не обнуляется. Оно сохраняется вместе с выбранными вариантами, применёнными эффектами, чекпоинтом и текущим шагом случайности.
Получается простой авторский цикл:
выбор игрока записывает значение
↓
переменная хранит состояние
↓
условие читает его позже
↓
граф, сцена или интерфейс реагирует
Например, в первой сцене выбор «Показать найденную фотографию» может установить portrait_revealed = true. Позже нода **Если / Иначе** проверит этот флаг и откроет новую сцену разговора. Ещё позже интерфейс терминала покажет дополнительную запись только тем игрокам, которые раскрыли фотографию.
Одна переменная связывает три разных части квеста. Для этого не нужно вручную передавать значение между сценами или главами: они читают одно общее состояние прохождения.

*У переменной route в одной карточке заданы стабильный ключ, понятное название, тип значения и видимость состояния. Связи с выборами и ветками находятся ниже на той же странице.*
Выборы пишут, ветки читают
В обычной сцене самый понятный способ изменить состояние — добавить эффект к варианту выбора. В визуальном редакторе это выглядит как присваивание:
«Довериться Мари» → ally = "mary"
«Уйти одному» → ally = "none"
После этого Switch может направить игрока в нужную сцену по значению ally. Страница переменных отдельно показывает, какие выборы записывают переменную и какие ветки её читают. Это небольшая деталь, но на большом квесте она заменяет мучительный поиск по десяткам сцен.

*Ветки Switch проверяются сверху вниз. В этом примере маршрут Laboratory срабатывает при route = lab, а Habitat — при route = habitat.*
Условия поддерживают обычные сравнения:
- равно и не равно;
- больше, больше или равно;
- меньше, меньше или равно;
- значение существует или ещё не создано.
В простом случае автор выбирает переменную, оператор и значение прямо в настройках If / Else или Switch. Для более составной логики общий runtime понимает группы **все условия**, **любое условие** и отрицание.
Такое правило можно описать структурно:
{
"op": "all",
"conditions": [
{ "op": "gte", "key": "evidence", "value": 3 },
{ "op": "eq", "key": "alarm_active", "value": false }
]
}
Это похоже на маленький скрипт, но принципиально им не является. Здесь нельзя вызвать функцию или изменить что-то случайное. Romergo заранее знает все допустимые операции, проверяет структуру и одинаково выполняет её в предпросмотре Builder и в опубликованном Player.
Эффекты: маленькие команды без языка программирования
Условие читает состояние, а эффект меняет его. Runtime Romergo поддерживает несколько атомарных операций:
setзаписывает конкретное значение;incиdecувеличивают или уменьшают число;toggleпереключает логический флаг;clampудерживает число внутри заданной границы.
В обычном инспекторе выбора основной сценарий намеренно простой: выбрать переменную и записать новое значение через set. В продвинутой логике интерфейсных сцен условия и эффекты можно задавать как проверяемые структуры. Например, переход между состояниями терминала может одновременно запомнить решение, добавить улику и включить тревогу:

*Эффект прикреплён прямо к варианту ответа: три выбора записывают в route значения lab, habitat и server. Поле переменной и новое значение остаются раздельными даже в узком инспекторе.*
[
{ "op": "set", "key": "terminal_decision", "value": "cut-power" },
{ "op": "inc", "key": "evidence", "amount": 1 },
{ "op": "toggle", "key": "alarm_active" }
]
У такого подхода есть важное ограничение: это не калькулятор произвольных формул. Нельзя написать evidence * trust / 2, запустить цикл или создать свою функцию. Сложное поведение собирается из нескольких явных шагов, а расширение логики происходит через новые проверяемые операции runtime, а не через выполнение неизвестного кода.
Для автора это немного менее безгранично, зато история остаётся предсказуемой. Одни и те же эффекты можно проверить до публикации, сохранить, синхронизировать между игроками и воспроизвести после загрузки прогресса.
Почему внутри логической ноды пока нет ещё одного Flow
При всей предсказуемости у нынешнего подхода есть честный недостаток: человеку без опыта разработки форма «переменная — оператор — значение» уже может казаться программированием. А когда рядом появляются группы условий и несколько эффектов, декларативная структура остаётся безопасной для Player, но не обязательно становится простой для автора.
Кажется естественным однажды прийти к визуальным нодам и внутри самой логики. Вместо списка условий можно было бы соединять блоки **И**, **ИЛИ**, **НЕ**, сравнение переменной и изменение значения. На бумаге это выглядит дружелюбнее текста или JSON.
Но затем я представляю реальный сценарий: автор открывает логическую ноду на карте главы, а внутри неё обнаруживает ещё один Flow с собственными нодами и связями. Получается граф внутри графа. Нужно понимать, где ты сейчас находишься, как вернуться на уровень истории и в каком из двух Flow искать ошибку. Для новичка такая вложенность может оказаться страшнее нынешней формы.
Поэтому пока используется компромисс. Маршрут истории остаётся визуальным, а условия и эффекты внутри нод показываются компактными явными полями. Это работает, но я не считаю интерфейс окончательно решённым. Возможно, позже появится более понятное сочетание готовых шаблонов, пошаговой настройки и визуальных блоков — такое, которое действительно уменьшит сложность, а не просто перенесёт её в ещё один редактор.
Логика живёт не только на карте
Интерфейсные сцены используют то же состояние. Виджет, сообщение, вариант ответа, задача, уведомление или медиапоток может появляться только при совпадении showWhen. Значение метрики можно связать с переменной, а переход между состояниями виджета — запустить после задержки, по условию или после действия игрока.
Например, экран бортового компьютера может вести себя так:
1. Сначала виден только заблокированный терминал.
2. После выбора игрока эффект устанавливает terminal_unlocked = true.
3. Условие открывает схему корабля и новые кнопки.
4. Нажатие на кнопку меняет состояние виджета и увеличивает evidence.
5. Когда evidence >= 3, появляется скрытое уведомление и становится доступна новая ветка квеста.
Карта главы, обычные выборы и интерактивный интерфейс не создают три отдельные системы. Они читают и изменяют один объект состояния. Поэтому найденная в диалоге улика может открыть элемент интерфейса, а действие внутри интерфейса — изменить последующую сцену.
Именно здесь декларативная логика начинает ощущаться как настоящее программирование. Только вместо файлов, функций и событийной шины автор работает с понятными сущностями истории: фактами, выборами, условиями и переходами.
Зачем я намеренно не добавляю произвольный JavaScript
Возможность вставить скрипт кажется самым коротким путём к любой новой механике. Но как только опубликованный квест получает право исполнять произвольный код, меняется вся модель продукта.
Нужно решать, какие браузерные API доступны этому коду, как ограничить сеть и хранилище, что делать с бесконечными циклами, как переносить квест офлайн, как синхронизировать его в совместной игре и как гарантировать, что старый проект продолжит работать после обновления Player.
Декларативное правило намного скучнее произвольной функции — и именно поэтому надёжнее. Его можно провалидировать до запуска. Можно понять, какие переменные оно читает и пишет. Можно безопасно сохранить прогресс посередине цепочки. Можно одинаково выполнить квест в браузере, автономной сборке и встроенном Player.
Ограниченный словарь здесь не мешает логике, а задаёт её границы. Если появляется действительно полезная операция, её лучше добавить в общий runtime и дать всем авторам одинаковое проверенное поведение, чем заставлять каждый проект изобретать собственный движок.
На десерт — свой HTML и CSS
При этом Romergo всё же оставляет место для настоящего кода там, где он безопаснее всего: в оформлении обычной сцены.
В проекте можно создать собственную тему внешнего вида на HTML и CSS и использовать её в разных главах. HTML задаёт расположение подготовленных областей — окна реплики, портрета, имени персонажа, текста, вопроса и кнопок выбора. CSS позволяет заметно менять эти области: рамки, фон, форму карточек, отступы, типографику и реакцию на размер экрана.
Готовые темы **Standard**, **Minimalism**, **Brutalism**, **Romance** и **Neon** показывают подтверждённый диапазон без ручной вёрстки. При создании пользовательской темы Romergo копирует выбранный шаблон: после этого его HTML и CSS можно редактировать и сразу проверять в настоящем предпросмотре Player.

*Здесь нет концепт-арта: слева показан настоящий CSS встроенной темы Brutalism, справа — результат того же кода в телефонном предпросмотре. Переключатель Desktop / Mobile позволяет сразу проверить адаптивные правила, а пользовательская тема начинает работу с копии выбранного шаблона.*
Но граница остаётся прежней. В пользовательском HTML нет <script>, обработчиков событий, форм, iframe и внешних ресурсов. CSS не может загрузить внешний url() или @import. Обязательные области сцены должны остаться на месте; если шаблон их потерял, Player возвращается к безопасному стандартному оформлению.
То есть HTML и CSS отвечают за то, **как история выглядит**, а ноды, переменные, условия и эффекты — за то, **как она работает**.
Мне нравится именно такое разделение. Автор может сделать квест визуально непохожим на остальные, но его прохождение всё равно остаётся частью общего проверяемого runtime. Внешний слой можно радикально перекрасить и перестроить, не превращая каждую тему в отдельную программу.
Код без кода — не отсутствие логики
У Romergo нет цели заменить язык программирования картинками. Граф плохо подходит для сложной математики, а набор атомарных эффектов не станет универсальным движком автоматизации.
Зато для интерактивной истории чаще нужны другие вещи: запомнить факт, изменить счётчик, открыть вариант, выбрать маршрут, показать состояние интерфейса, сохранить точку возврата и донести все эти решения до следующей главы.
Для этого ноды и переменные оказываются не упрощённой версией скриптов, а более прямым языком самой истории. Автор описывает не команды компьютеру, а причинно-следственные связи мира: игрок сделал выбор, состояние изменилось, история отреагировала.
А если хочется немного настоящего кода, HTML и CSS всё равно ждут на десерт.