Як і навіщо ми створили власну мову програмування для 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.
Чи варто створювати власну мову програмування? Кожен вирішує сам для себе, залежно від ресурсів і задач. Для нас це був неймовірний досвід, який не лише розширив наші можливості, а й показав: інколи найкращий шлях — не шукати готові інструменти, а створювати свої.
А тепер цікаво почути вашу думку.
Чи доводилося вам створювати моди для ігор?
Чи мали ви досвід розробки власних мов програмування або інструментів для них?
Діліться своїм досвідом і думками в коментарях — буде цікаво обговорити.
4 коментарі
Додати коментар Підписатись на коментаріВідписатись від коментарів