Войти
ПрограммированиеФорумОбщее

Отчет: Перенос увесистого инди Lua-проекта на Unity малой кровью

Страницы: 1 2 Следующая »
#0
(Правка: 17:11) 16:06, 24 июня 2022

Что-то непогодится в моих краях. Да и год прошел - можно подводить итоги.

Наверняка все видели амбициозный проект "Crossroad In The Void: Олдскульная партийная рпг", ведущийся эксцентричным, но пытливым разработчиком уже чуть более двух лет.

Запустить видео по клику - Как делать игрыЗапустить видео по клику - Как делать игры


Предисловие

Чуть забегая вперед, раскрою прежний технологический стек проекта:
1. LÖVE (aka love2d). Это добротно сделанный фреймворк, написанный на C++ для написания 2D игр на Lua.
2. 3DreamEngine. Это расширение для LÖVE, целиком написанное сторонним разработчиком на Lua, превращающее LÖVE в простенький 3D-движок. т.е. рэйкастинг, загрузка моделей (OBJ), а также рендеринг, основанный на способности LÖVE выводить на экран многоугольники.
3. сторонняя библиотека для GUI написанная на Lua как расширение для LÖVE.
4. Ядро - много... нет, МНОГО луа скриптов, написанных автором. игромеханически игра находится в состоянии полнофункциональной альфы - по большому счету, фич можно уже не добавлять и только фиксить баги. Хотя что-то еще добавляется.
5. Визуальный контент. OBJ + текстуры. Любые эффекты - через код.
6. Общий контент. Карта статических объектов составляется в Tiled Map Editor, а вся динамика (положения и свойства NPC, ловушек, телепортов, сундуков, квесты, локализация) прописана в специальных декларативных Lua-файлах (похоже на JSON).

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

Как известно, Lua - язык довольно медленный. И даже LuaJIT (который как раз и входит в состав LÖVE) не способен спасти ситуацию, когда на луа начинается не расчет кол-ва урона, а рэйкастинг и рендеринг моделей, где буфер вершин представлен в памяти структурой вроде "shared_ptr<unordered_map<int, shared_ptr<unordered_map<string, float>>>>". Игра тормозила в самых неожиданных местах, при этом выглядя, мягко говоря, очень просто.

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


Что-то нужно менять

Будучи старым поклонником трилогии рпг-шек Might & Magic VI-VIII (в свое время на пару с The Elder Scrolls раздвинувшими стандарты размеров игровых вселенных), с чешущимися руками раз за разом проходил мимо треда CITV. И раз за разом в личке общался с автором по техническим около игровым вопросам - как вообще живется и какие взгляды у автора на то "что хочется" и "что можется". И все больше понимал, что технология, применяемая автором сильно ограничивает рост проекта и лучше что-то поменять. Автор в свою очередь резонно предложения о смене техстека отметал - ведь он делает игру мечты, а не экспериментирует со стеком, тем более игра изначально типа-олдскул, а значит графические улучшения не в приоритете.

Прошлым летом я психанул и понял что на это смотреть больше нельзя. Проект нужно технически апгрейдить чтобы удержать/привлечь художников и дать на время забыть о тормозах. Но речи о переписывании игры на U* быть не может, т.к. никто этим заниматься не будет. С таким же успехом можно бы и просто свою РПГшку запилить с нуля, оставив автора этого проекта в покое.

Но есть решения и более ковровые. По сути, большая часть проблем в API 3DreamEngine, а его API довольно небольшой (используемая в проекте часть). LÖVE побольше, но тоже не такой уж большой(та часть, которая используется в Ядре ). надо просто заставить Ядро запускаться в ином окружении с минимальными изменениями скриптов. В окружении с более матерым движком вместо 3DreamEngine (но мимикрируя под его API).

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


А какой есть выбор?

JMonkeyEngine

Поясню, я не настоящий программист и моя вотчина - java (бэкенд для энтерпрайза). Так что всерьез рассматриваю и такие варианты (к тому же есть опыт в близком жанре, показывающий что ява в какой-то степени применима). Возможно многие удивятся, но при определенной сноровке java разгоняется до половины скорости крестов. И реализации интерпретатора Lua на java весьма быстрые по сравнению с C# аналогами (но луаджиту тоже сильно сливают). Тем не менее, JMonkey предлагает довольно скудный тулсет, и кроме возможного (но непростого в достижении) буста в производительности здесь ловить практически нечего.

Urho3D

Я не мог не рассмотреть какой-нибудь крестовый двиг, т.к. было интересно пощупать кресты обстоятельнее, а к тому же есть потенциальная опция срезать углы - оставить LÖVE для 2D и рендерить 3D другим движком, шаря окно/холст/хз_что. Отличная была идея, и суппортеры урхи (вернее RBFX) очень отзывчивые ребята, но через пару недель ковыряния в исходниках LÖVE, доках/туториалах по OpenGL и попытках найти/воспроизвести желаемый шаринг, у меня отклеился ус и началась аритмия. В другой раз...

UE

Ну тут понятно - high-end ассеты - это не олдскульно ни разу, кресты вызывают аритмию, а блупринты даже для java-программиста - как-то уж слишком.

Stride

Шарп, но не нашел причин выбрать его, а не Unity. Может быть когда-нибудь потом, когда Unity изменит лицензию или совсем скатится.

Unity

Старался максимально убедиться, что вариантов больше нет, прежде чем начать работать в эту сторону, т.к. в успехе этого варианта я был практически уверен. C# для java-программиста как родной. Да и ну вы сами понимаете, художники в юньке могут сами эффекты делать и свет настроить.

Окей, выбрал Unity. Нужен рантайм для Lua. MoonSharp (Lua интерпретатор на C#) очень тормознутый. Причем IL2CPP не помогает ничем - скорость такая же в рамках погрешности что и у Mono (это для тех, кто считает, что IL2CPP для скорости). NLua/KeraLua (готовые биндинги к нативному интерпретатору) несовместимы с LuaJIT т.е. тоже медленно, к тому же предоставляют слишком high-level обертки, что тоже обязательно сворует скорость и затруднит работу с документацией Lua API. Но очень быстро получилось заэмбеддить LuaJIT с помощью P/Invoke без единой строчки на C++. Вынес простые низкоуровневые биндинги (для простоты обращения к оригинальным докам) в отдельную либу и поддерживаю по мере возникновения проблем. Либа умеет в удобный просмотр скриптовых объектов при отладке C# (отладка Lua поддерживается обычными луашными средствами).

+ Показать

Конечно, такой фокус добавил в работу с Unity нативные краши с вылетом без вменяемого отчета (из-за глупых ошибок в работе с Lua-стеком), но это происходит достаточно редко чтобы держать сердечный ритм в норме.

Было решено полностью воспроизвести на шарпе средствами Unity весь нужный функционал как LÖVE, так и 3DreamEngine, т.к. подсунуть LÖVE в Unity - ну совсем не вариант, раз уж с открытым Urho3D не получилось. Как ни странно, хватило недели до того, чтобы получилось запустить игру уже под Unity с некоторыми ограничениями, незаметными на первый взгляд. Эти наработки намного убедительнее, чем советы "перепиши на юнити", так что дальше пошла уже командная работа.

Ранний вариант переноса:

Запустить видео по клику - Как делать игрыЗапустить видео по клику - Как делать игры

Proof of Concept:

Запустить видео по клику - Как делать игрыЗапустить видео по клику - Как делать игры


Развитие игры на новой платформе

Первое что было сделано после начальной стабилизации:

  • добавление поддержки анимаций - в скриптах при этом ничего практически не поменялось, конфигурация делается прямо в ассетах юнити.
  • перепады высоты. игромеханичести - игра все еще плоская (и такой и будет), но аккуратные перепады высот здорово разнообразят визуал без нарушения корректности игровых ситуаций. Все модели монстров и других объектов на стороне Unity выравниваются по ландшафту, стрелы выравниваются по прямой между стартом и финишем (по хинтам из скриптов).
  • делегация рэйкстинга Unity
  • теперь статический визуал настраивается в Unity в добавок к семантической статике в Tiled и динамике в скриптах.
  • замена двумерных эффектов красивым VFX и настройка их преимущественно руками художника. сделано через механизм оверрайдов - пути моделей в некоторой степени стали символическими, т.к. художник может на них повесить свои префабы, которые будут выданы скриптам вместо старых моделей.
  • Уже вскоре время благодаря усилиям художника в проекте стали появляться красивые тестовые ландшафты с анимированными деревьями, анимированные персонажи, костерки.

    Запустить видео по клику - Как делать игрыЗапустить видео по клику - Как делать игры


    Тулсет и свобода

    Через три месяца уже стало понятно, что переход на такую гибридную платформу по рискам компенсируется пользой и можно дропнуть поддержку LÖVE. После чего удалось сильно оптимизировать некоторые аспекты, чуть-чуть переписав скрипты (просто не делая лишних переборов, которые в 3DreamEngine были логичными, а в Unity - лишними).

    Через примерно полгода уже назревал запрос в более-менее полноценном редакторе уровней на основе Unity. А именно - редактирование статики и расстановка точек для динамики. Процесс разработки тулсета затянулся из-за изначальной попытки оставить совместимость с Tiled. Но в итоге от Tiled полностью отказались в пользу Unity со своей обвязкой. Функционал поддерживается и развивается по мере его использования.
    Изображение

    Далее наконец дошли руки до моей старой хотелки. Напомню, что я поклонник именно M&M VI-VIII, а не M&M IV-V+X, т.е. вот эти повороты кратные только 90° и перемещение рывками по клеткам - это вообще не то. Но я знал что это не каприз автора, а попытка уменьшить производственные риски. Так что был реализован механизм, что вне боя партия может ходить в произвольном направлении на произвольное расстояние, как в обычных шутерах. По игровой модели, правда, партия все равно находится всегда в каких-то клетках, и в режиме боя на земле появляется сетка и партия мягко вгоняется в середину своей клетки. А вот пошаговый бой в режиме клеток (но со свободной камерой - в будущем) - это выглядит уже вполне. Да, у этого режима есть нюансы, но на мой взгляд недостатки (вгоняние в середину при начале боя и странности с некоторыми препятствиями) с лихвой заглушаются духом свободного исследования.

    (в отличие от видео, сейчас сетка видна только в бою, и она не так далеко простирается)

    Запустить видео по клику - Как делать игрыЗапустить видео по клику - Как делать игры


    Актуальный технологический стек

    1. Unity
    2. Фреймворк на C#, являющийся средой для Lua-программы, частично имитирующий API LÖVE и 3DreamEngine, а частично предоставляющий скриптам новые функции.
    3. сторонняя библиотека для GUI написанная на Lua как расширение для LÖVE. она вполне работает на основе имитации API LÖVE.
    4. Ядро - те же луа скрипты, написанные автором. скрипты сначала переехали на новую платформу с ничтожными изменениями (пара if-ов в критичных местах), но затем уже стали появляться несовместимые изменения. хотя на фоне масштабов скриптов они все еще ничтожны.
    5. Визуальный контент. Полная свобода в настройке эффектов с минимальным вовлечением программистов. Современные форматы моделей, анимации.
    6. Общий контент. Мир редактируется прямо в Unity. Статику можно ставить любую, но держать в голове клеточную логику. Положения всех объектов (включая динамические триггеры) расставляются тоже прямо в Unity. Свойства проходимости редактируются тоже прямо в Unity. А вот игровые свойства объектов (содержимое сундуков, например) все еще в JSON-образных Lua-файлах. Это вопрос востребованности - появятся World-дизайнеры, желающие инструмент - сделаем инструмент.

    ***

    Считаю реализацию перехода успешной. Условие "не мешать автору делать игру" выполнено. Проблема производительности испарилась. Нативных крашей не было уже очень давно (по крайней мере на Windows). Возможности художников значительно расширены, игра уже стала выглядеть лично для меня ощутимо приятнее. И конечно получил массу удовольствия от игры в настоящего программиста =D а теперь вот поддерживать все это добро... ;( шучу, это тоже интересный процесс - дел по платформе еще много.

    В проекте рады энтузиастам! Особенно World-дизайнерам (квесты, локации).

    #1
    16:06, 24 июня 2022

    .

    #2
    18:29, 24 июня 2022

    Можно запариться и переписать полностью весь код на с#))

    #3
    18:46, 24 июня 2022

    Creatchi
    когда готовы приступать?)

    #4
    20:47, 24 июня 2022

    И как по сравнению с MoonSharp скорость? Судя по всему - приемлемо. Будет время - гляну.

    У меня просто был проект - практически вся логика была на Луа. Но все крутилось в Юнити. Использовалось вот это. https://github.com/xebecnan/UniLua - реализация Луа на шарпе.
    Быстрее MoonSharp-а, но все же тормозит.

    Я тогда сделал вывод для себя, что на Луа надо писать что-то достаточно просто. Уж точно не всю логику.

    #5
    (Правка: 21:22) 21:19, 24 июня 2022

    seaman
    Мерил на скрипте, что-то делающем с бинарными деревьями.

    LuaJIT...................  0.121  
    Lua 5.4................... 0.772
    LuaJ (openjdk 11)......... 1.33
    Moonsharp (.Net).......... 6.51  
    MoonSharp (Unity/IL2CPP).. 12.1  
    Moonsharp (Unity/Mono).... 13.4
    Moonsharp (Unity Editor).. 20.7

    Последний - это, насколько помню, дебажный рантайм (в другом режиме редактор я и не запускаю), поэтому настолько медленее предпоследнего.

    Лично меня скорость Lua вполне устраивает (уж LuaJIT - точно). Конкретно в этом проекте на Lua все еще написана вся симуляция и UI, и оно работает вполне быстро с большим запасом. Просто на Lua точно не стоит делать performance-critical части программы рендеринга и физики. Вещи типа 3DreamEngine это чистый фан, не более.

    Главная проблема Lua, почему на нем не стоит писать ВСЕ - это динамическая типизация. В процессе тестирования то и дело всплывают недопереименованные 40 лет назад переменные/функции и тому подобное. Я уже прям подумываю чекер написать, который будет анализирвоать код на предмет такого (если код написан в определенном стиле, это выглядит возможным).

    Думаю, на Lua стоит писать только небольшие изолированные модули. Смех смехом - микросервисы на нем будут норм =D

    #6
    21:35, 24 июня 2022

    Главная проблема Lua, почему на нем не стоит писать ВСЕ - это динамическая типизация.

    Дико плюсую.

    #7
    (Правка: 21:42) 21:39, 24 июня 2022

    Код на Lua легко писать, но очень сложно поддерживать.

    На больших скриптах код на Lua - Write Only.

    #8
    21:42, 24 июня 2022

    Max Dark
    есть те кому и писать сложно. например я через пару часов разработки на Lua начинаю писать очередной вариант "системы классов", абсолютно кладя болт на то, что я хотел сделать изначально =D

    #9
    (Правка: 22:31) 22:31, 24 июня 2022

    Интеграция Луа в проект - отдельный ад.

    Либы вроде sol2 упрощают немного

    #10
    22:56, 24 июня 2022

    Max Dark
    Для шарпа NLua/KeraLua аналогично. Хотя я без них обошелся и не сказал бы что прям ад. В шарпе можно рефлекшном упростить.

    #11
    (Правка: 23:40) 23:37, 24 июня 2022

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

    А если нужно принять/выдать хитрую таблицу, то там жопа.

    Интеграция луа без доп либ больше похожа на ассемблер - запихнуть значение на стек/взять со стека.
    То еще удовольствие.

    В AngelScript(как пример) это решается одной строкой.

    #12
    (Правка: 1:59) 0:40, 25 июня 2022

    Max Dark
    По идее можно написать один раз генератор маппера) Собственно что и происходит в AngelScript, насколько я понимаю.
    Так-то да, это возня со стеком очень муторна, если ее руками делать) а в случае с Unity это ведь еще и неведомая доселе возможность ловить сегфолты =D

    #13
    6:43, 25 июня 2022

    Круто, интересно
    Я так понимаю, что до появления свободного перемещения вся игровая логика (включая дискретное перемещение по игровому миру) была инкапсулирована в Lua скриптах, а Unity выступала чисто как presenter? Знают ли теперь Lua скрипты про перемещение внутри клетки, или это чисто визуальная глазурь, не существующая на уровне игровых правил?

    #14
    (Правка: 12:34) 12:32, 25 июня 2022

    meekobold
    спасибо!

    До сих пор вся игровая логика инкапсулирована в Lua скриптах) более того, львиная доля презентации тоже до сих пор в скриптах (и уезжает оттуда только по мере очень явной необходимости)
    связка {Unity+C#-фреймворк} здесь предоставляют функции вроде "loadModel(modelPath)" и "drawModel(modelHandle, x, y, z)". т.е. это даже чуть ниже уровня абстракции самого Unity. Можно сказать что это этакий флэш-плеер :)

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

    Страницы: 1 2 Следующая »
    ПрограммированиеФорумОбщее