Як і навіщо ми створили власну мову програмування для Unity та модів

Чому в епоху сотень готових технологій ми все ж вирішили написати власну мову програмування для ігор?

На перший погляд, це звучить як забаганка або інженерний перфекціонізм. Насправді ж — це була єдина відповідь на вимогу зробити модинг у грі настільки простим, щоб ним міг скористатися будь-хто, навіть без досвіду в розробці. Історія про те, як ми з «розумного конфігу» дійшли до повноцінної мови Flex, може стати корисною кожному, хто думає про створення власних інструментів.

Передмова

Вітаю! Я розробник у FLEXUS і хотів би поділитися цікавим рішенням, до якого ми прийшли. Це перша стаття з циклу, в якій я коротко поясню, що таке Flex і для чого ми його використовуємо. Детальніше про технічну сторону розповім у наступних матеріалах цього циклу.

Здавалося б, у 2025 році існує достатньо інструментів, мов і сервісів під будь-яку задачу. Просто бери і використовуй те, що потрібно. То чому ми у FLEXUS дійшли до власної скриптової мови?

Забігаючи наперед: ми перепробували багато різного й лише потім дійшли висновку, що без власного рішення не обійтися.

У процесі розробки одного амбітного, але поки неоголошеного ПК-проєкту виникла потреба додати підтримку модів. Реалізація такої задачі в Unity — не тривіальна, але цілком можлива штатними методами. FLEXUS не був би FLEXUS, якби для амбітних проєктів не ставив амбітних завдань, тому основна мета була — зробити модинг доступним кожному: без знання мов програмування, XML, JSON, YAML, самої Unity тощо.

Друга ціль — поєднання логіки з конфігом. Наприклад, щоб значення HP залежало від умови: якщо умова виконується — 50, якщо ні — 20. З точки зору архітектури поєднувати дані й логіку — не найкраща ідея, але на старті ми мали на увазі лише просту умовну логіку в конфігах, не більше.

Перші спроби

Першим, що ми спробували, був JSON. Деякі його бібліотеки підтримують умовну логіку. Здавалося, усе збігається: структура проста, підтримка в Unity відмінна, виглядає більш-менш зрозуміло, але навряд чи це можна назвати «доступним» для більшості користувачів. Особливо коли мова йде про вкладену логіку, умовні блоки або включення одного конфігу в інший. Приклади виглядали складно навіть для розробників, не кажучи вже про новачків. Спроби розширити функціональність JSON зазвичай або закінчувалися милицями, або вимагали громіздкої backend-частини на боці гри.

Другим кандидатом став TOML. Пізніше власній версії ми дали назву FOML. У сирому вигляді конфіг виглядає лаконічно й зрозуміло для людини. Проблема в тому, що у стандартному TOML немає підтримки логіки. Ми почали розширювати одну з бібліотек: додали змінні, умовні оператори, методи. Все як треба, але чим далі — тим складніше. Складність зростала майже експоненційно. І не лише в логіці, а й у продуктивності: TOML-парсери в Unity працювали дуже повільно. У порівнянні з JSON відставали в рази.

Ми також розглядали готові мови на кшталт Python, JS, Lua, але всі вони не проходили наш головний фільтр — простоту для користувача.

Ідея власної мови

І вже тоді, коли майже відмовилися від ідеї «розумного конфігу», з’явилася інша думка: а що, як зробити щось своє? Раніше мені здавалося, що написати власну мову програмування — це щось із категорії академічної інженерії. Але випадково я натрапив на кілька статей про ANTLR. Там були приклади створення примітивної мови, включно з реалізацією backend-частини на C# (тобто інтерпретатора або віртуальної машини, яка виконує згенерований код). І стало очевидно: це саме те, що нам потрібно.

У голові було лише базове уявлення: хочемо щось схоже на псевдокод — максимально просте, без лапок, без вкладених дужок, без нагромадження форматів. Писати має бути просто. Читати — ще простіше. Як сказав наш CEO та ідейний натхненник Семен Козюра: «народна мова програмування».

Так з’явився Flex.

Що таке Flex

По суті, це скриптова мова, яка під час першого запуску компілюється у власний набір інструкцій (bytecode) та кешується в оперативній пам’яті на час поточної сесії (після перезапуску гри кеш очищується). Виконання відбувається на віртуальній машині, яку ми написали на C#. Це дозволяє запускати скрипти швидко, без зайвих витрат на парсинг і алокації. А кінцевий користувач цього всього навіть не бачить. Він бачить просто:

if IsStrong == true {
  HP = 50
} else {
  HP = 20
}

Ніяких JSON-структур, ніяких обгорток — лише умова й результат.

Flex підтримує базові типи: числа, булеві значення, рядки, масиви, об’єкти. Є базова арифметика, логіка, умовні блоки, цикли, системні методи. Це дійсно виглядає, як псевдокод, який хтось написав на дошці, але він виконується.

Ще приклад:

player = {
   Name = "Arthur"
   HP = 100
}

if player.HP < 50 {
  Print("Warning: low HP!")
}

Навіть без документації зрозуміло, що відбувається. Ментально це щось між псевдокодом, Lua і простими діалоговими мовами на кшталт Ink — тільки більш універсальне, без вкладених дужок і залежностей від інженера.

А ось приклад ближчий до реального використання — скажімо, для вибору персонажа з масиву:

// Flex
// External args: CurrentCharacterIndex
// Output: character

var characters = Include("../Configs/Characters")
index = CurrentCharacterIndex

if index >= characters.Length() {
   index = characters.Length() - 1
}
character = characters[index]

Include() — це вбудована функція Flex, яка підключає інший скрипт або конфіг та повертає його дані у вигляді об’єкта.

Коментарі зверху не обов’язкові. Їх можна використовувати просто як підказку, що саме скрипт очікує на вході й що має повернути. При цьому сам скрипт не знає, хто такий character, і що з ним буде далі, його задача зведена до одного: вибрати персонажа з масиву.

Так, тут досі залишаються {} та [], діють певні обмеження, але мова пробачає і не вимагає суворо дотримуватися формату. Хочеш — пиши прапорці як true або Off. Зробив помилку й написав iF замість if? Можеш не виправляти — Flex тебе зрозуміє. Не хочеш оголошувати нову змінну? Візьми вже наявну й не переймайся, що раніше там було число, а зараз — колір. Flex знає, що з цим робити.

Як Flex співіснує з Unity

Flex створювався як практичний інструмент під Unity. Ми хотіли, щоб інтеграція була максимально зручною і не вимагала нічого зайвого від розробника. Для цього довелося серйозно попрацювати з оточенням Unity.

Скрипти Flex зберігаються у вигляді звичайних TextAsset. Коли скрипт вперше виконується, він у рантаймі компілюється у власний байткод та кешується в пам’яті на час сесії. Варто зауважити, що сам Flex не має жорсткої залежності від Unity API чи конкретного середовища виконання — бібліотека може працювати в інших проєктах, якщо забезпечити місток для інтеграції.

Щоб зробити роботу зі скриптами комфортною ще на етапі розробки, ми інтегрували бібліотеки ANTLR безпосередньо в Unity. Поверх цього були створені додаткові інструменти: редакторський пайплайн, система перевірки синтаксису, генерація шаблонів коду тощо. Крім того, Unity розпізнає наші скрипти як окремі ассети завдяки кастомному AssetImporter та власному розширенню файлів, що дозволяє інтегрувати їх у стандартний пайплайн.

У сам білд потрапляє лише згенерована бібліотека Flex — без ANTLR, тулзів і додаткових залежностей. Це робить фінальний проєкт максимально легким і не перевантаженим непотрібним кодом.

Ще один ключовий елемент — це місток між проєктом і бібліотекою Flex. Це окремий шар інтеграції, який керує компіляцією, виконанням, передачею аргументів, обробкою результатів і надає контрольоване API для доступу до внутрішніх систем гри. Через нього також можна обмежувати доступ скриптів до певних функцій (sandbox-підхід).

Система модів є вершиною цього айсберга і формально не є частиною Flex, а радше надбудовою, яка інтегрується в проєкт. Моди фізично підключаються через окрему папку, з якої скрипти Flex підвантажуються у рантаймі. Вона задає структуру, за якою мають писатися моди, надає розширене API для роботи з ними: підключення, запити, обробка результатів виконання тощо. У нашому випадку розробник має фізичну можливість запускати скрипти напряму через місток, але в рамках проєкту основна взаємодія з Flex відбувається саме через Mod system.

Окремий модуль — вбудована ігрова консоль, через яку можна писати й одразу виконувати код Flex прямо під час гри. У межах дозволених прав це дає змогу змінювати параметри «на ходу», викликати потрібні методи або робити перевірки — без перезапуску сцени чи гри.

Також Flex органічно підходить на роль ядра системи візуального скриптінгу, яку ми плануємо розробити, щоб знизити поріг входу у модинг. Flex-скрипт компілюється у стабільний набір інструкцій реєстрової віртуальної машини, тож вузли графа безпосередньо відповідають цим інструкціям. Така відповідність дає змогу генерувати Flex-код із графа — і навпаки, відновлювати граф зі скрипту. У поєднанні з містком це відкриває можливість будувати графи всередині гри (або в окремому редакторі) і одразу перевіряти результат.

Підсумки

Flex — це не просто ще одна скриптова мова. Це інструмент, який ми створили з конкретною метою: зробити модифікацію поведінки в іграх простою й доступною. Ми не намагалися вигадати універсальне рішення для всього — навпаки, Flex був свідомо спроєктований під потреби Unity-проєктів і мод-системи. І саме ця вузькість дала нам найбільшу свободу.

Flex не нав’язує строгих правил і парадигм, а дозволяє швидко й безболісно досягати результату. Завдяки кешуванню, віртуальній машині й контрольованому виконанню через місток ми зберігаємо продуктивність і безпеку. Завдяки інтеграції з Unity та зручним редакторським інструментам — простоту використання, а завдяки мод-системі — можливість будь-кому змінювати гру без глибоких технічних знань.

Ми починали з ідеї «розумного конфігу», а в результаті отримали повноцінну мову, пристосовану до геймдеву. І це той випадок, коли рішення переросло поставлені задачі, а подальший розвиток Flex виходить за межі Unity.

Чи варто створювати власну мову програмування? Кожен вирішує сам для себе, залежно від ресурсів і задач. Для нас це був неймовірний досвід, який не лише розширив наші можливості, а й показав: інколи найкращий шлях — не шукати готові інструменти, а створювати свої.



А тепер цікаво почути вашу думку.
Чи доводилося вам створювати моди для ігор?
Чи мали ви досвід розробки власних мов програмування або інструментів для них?
Діліться своїм досвідом і думками в коментарях — буде цікаво обговорити.

Підписуйтеся на Telegram-канал @gamedev_dou, щоб не пропустити найважливіші статті і новини

👍ПодобаєтьсяСподобалось10
До обраногоВ обраному2
LinkedIn


Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Спеціально зареєструвався, бо прикро не бачити коментарів під цією статтею.
Дякую за топік, було цікаво почитати.
Не впевнений, що обрав би такий шлях, але було би цікаво побачити його результати через деякий час.

Дякую! Радий, що ця тема вас зацікавила. Який шлях ви обрали б для створення модів до ігор без залучення Unity?

Мені здається, що навіть така спрощена мова має високий поріг входу для звичайних гравців, тому не має великого значення, чи існують там зайві дужки, чи є там вимоги до case’у літер. Тому я би максимально узяв синтаксис C#, бо в інтернеті є багато прикладів його застосування та нейронки теж з ним адекватно працюють.
Але почав би я навіть не з цього, а з простого візуального інструментарія. Щось на кшталт того, з чим працюють гд для створення контенту. Та, можливо, Visual Scripting для більш просунутих модерів. Від простого до складного.
Але, напевно, це дорожче у розробці. Не мав подібного досвіду, не знаю.

Так, погоджуюсь про поріг входу, тому тут дивимось одразу у два напрямки: спрощення синтаксису і Visual Scripting. Для більш просунутих лишається можливість створювати моди через Unity. Чому обрали такий складний шлях через Flex? Кросплатформеність і рантайм. Але ідея про гд інструментарій досить цікава, дякую!

Підписатись на коментарі