Skip to content

Реальный опыт использования MonoGame для разработки мобильной игры

На протяжении достаточно продолжительного периода, я в свободное от отдыха время занимался разработкой небольшого доморощенного игрового движка на основе MonoGame. По началу было очень интересно попробовать реализовать различные вещи, которые популярные движки позволяют получить сразу из коробки. В процессе пришло понимание, что полный контроль — это безусловно здорово, но и цена высока. Однажды в процессе изучения Xamarin я наткнулся на простую демку, демонстрирующую использование MonoGame на Android. Тогда и пришла идея адаптировать имеющиеся наработки для мобильной платформы, чтобы сделать наконец что-то, быть может не слишком сложное, но завершённое…

За основу была взята идея простой физической симуляции шаров в ящике, управляемой положением мобильного устройства. Этакая пространственная вариация на тему цветных шаров, по мотивам которых существует много так называемых «тайм-киллеров». Периодически безмятежность процесса прерывалась различными тонкими моментами, фичами, багами, с которыми пришлось в разной степени мириться/бороться.

Прежде всего

пришлось обновиться до актуальной версии кода. Последняя стабильная версия 3.6 фактически не имеет поддержки OpenGL ES 3 и с поддержкой более ранних версий тоже справляется плохо, демонстрируя стабильные вылеты.

Доступ к ресурсам

по началу может стать причиной неожиданных ошибок в процессе исполнения. Дело в том, что Android основан на ядре Linux и потому обладает свойством чувствительности к регистру символов в именах файлов и путях к ним. Несовпадение регистра символов никак не проявляется при работе в Windows. Но стОит запустить тот же самый код на Android и приложение валится при попытке загрузки ресурсов.

Графика и все что с ней связано

предсказуемо стала источником большинства проблем, как и в случае перехода с XNA на MonoGame. Пришлось довольствоваться возможностями shader model 3 и мириться с большим количеством ограничений. Так например нет возможности использовать floating point render target. А значит для хранения например глубины сцены придется пользоваться какими-нибудь схемами кодирования.

Вертексные и индексные буферы имеют метод GetData. Он позволяет вытащить геометрию из уже созданных буферов, что может быть полезно например для создания модели физики для уже загруженной 3D-модели. Метод хоть и не самый быстрый, но очень удобный. Работая без проблем на десктопе, на мобильной платформе не поддерживается. А без него придется либо править Content Pileline, либо формировать упрощённую физическую модель другими методами.

Среди параметров растеризации есть FillMode, который будучи установленным в значение WireFrame, позволяет отрисовывать только полигональную сетку. Опять же бывает очень удобно в процессе отладки. Однако для Android эта функция не реализована.

Использование семантик COLOR и NORMAL при передаче параметров в пиксельные шейдеры, приводит к невозможности эти параметры использовать. При этом нет ошибок компиляции или исключений в процессе исполнения. Приложение просто намертво зависает. Приходится плодить семантики TEXCOORD1, TEXCOORD2 и т.д.

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

Однако полученная таким образом текстура нормалей на мобильной платформе из-за меньшей точности отличается. Разница между правильным (слева) и неправильным (справа) результатами видна невооруженным глазом. Для решения этой проблемы проще всего пользоваться традиционной готовой текстурой.

Производительность

едва ли будет проблемой, если игра достаточно проста и использует спрайтовую анимацию. В моём случае требовалось отрисовывать трехмерную сцену с эффектами и выполнять физическую симуляцию. Не слишком древний аппарат на 4-м Android’е захлебывался очень быстро. Более свежий аппарат на 7-м Andriod’е показывал лучшие результаты, но не идеальные. Вынос физики в отдельный поток решил проблему. Остальной код к асинхронной работе физики оказался готов. Почти… Лишь спустя какое-то время на записи видео игрового процесса удалось разглядеть рассинхрон между геометрией и затенением для неё. Причина была в обновлениях физики, которые вклинивались между формированием двух картинок. Исправить оказалось легче, чем заметить.

Причиной менее очевидных проблем может стать JIT-компиляция. И тут следует зайти из далека. Если не рассматривать различные варианты обфускации, то есть всего три варианта сборки, от выбора которых зависит что в итоге будет содержать результирующий APK-файл:

1. Все ваши библиотеки в неизменном виде. Вариант по умолчанию. Худший вариант ибо и размер APK-файла большой и код абсолютно открыт для исследования/модификации/заимствования.

2. IL-код ваших библиотек интегрированный в нативные бинарники (опция «Bundle Assemblies into Native Code», доступна в Enterprise версии). Позволяет значительно сократить размер APK-файла и усложнить любые манипуляции с кодом.

3. Нативный код (опция «AOT Compilation», доступна в Enterprise версии). Позволяет сократить время запуска и максимально усложнить манипуляции с кодом, но размер APK-файла снова большой.

В первых двух случаях APK-файл содержит IL-код ваших библиотек, который подвергается JIT-компиляции в процессе исполнения. Но не сразу и не весь, а по мере необходимости и частями. Поэтому если так случается, что значительный объем кода до некоторого момента времени не использовался, а потом вдруг понадобился, то его JIT-компиляция может оказаться достаточно продолжительной для того, чтобы привести к заметному притормаживанию. В игровых приложениях это приводит к падению частоты кадров, что конечно крайне нежелательно. Если подобные эффекты слишком заметны, то есть лишь два варианта: либо использовать AOT-компиляцию, либо выполнять прогоны целевого кода на этапе загрузки, чтобы в последствии не тратить время на JIT-компиляцию.

В итоге

каких-то специфичных только для MonoGame трудностей возникло немного. Это лишний раз подтверждает зрелость библиотеки и её готовность для решения подобных задач. Конечно сегодня, учитывая существование и относительную доступность таких движков как Unity, подобные методы можно охарактеризовать как скорее неверные. По крайней мере, если главной целью является быстрое достижение результата. Тем не менее, эти методы все же работают и нашли воплощение в игре BusyBalls.

Be First to Comment

Добавить комментарий