Современная IT экосистема относительно раздроблена. Разрозненность в аппаратном обеспечении волнует бизнес и прикладных программистов значительно меньше, по сравнению с требованиями к вычислительной мощности используемой платформы. При этом основные сложности реализации связаны с использованием мобильных устройств, рынок которых разделился, в основном, на две наиболее популярные операционные системы: Android и iOS.
Бизнесу важно решать свои задачи на максимальном парке устройств и систем. Для решения данной проблемы возможно разработать одинаковую, по смыслу, программу под каждую систему. Однако это потребует привлечения отдельных команд для разработки под каждую из них. При этом в процессе развития и поддержки будут накапливаться отличия в реализациях.
Для уменьшения издержек, в среде программистов популярно использование кроссплатформенной разработки. В этом случае вся программа или ее часть реализуется универсально сразу для нескольких платформ одной командой специалистов. При этом инструментов кроссплатформенной разработки опубликовано довольно много.
Одной из крайностей при написании программы или ее части является использование языка C/C++, что позволяет добиться значительных показателей производительности, что приведет к увеличению сроков и общей стоимости разработки. Другой крайностью является Web-технологии. В настоящее время интернет-сайты открываются практически на любых современных устройствах с экраном. При этом даже при использовании специализированных оптимизированных библиотек разработчики сталкиваются с крайне низкой производительностью и ограничениями функциональности.
Еще одним решением данной проблемы стал кроссплатформенный проект Mono, представляющий собой свободную реализацию технологии Microsoft.NET Framework от компании Xamarin и поддерживающий язык C#. Производными продуктами которого являются средства разработки мобильных приложений Xamarin.Android, Xamarin.iOS, а также инструмент Xamarin.Forms, который позволяет создавать пользовательский интерфейс, отображающийся на разных устройствах похожим образом.
При этом на рынке игр и интерактивных развлечений одним из лидеров по популярности признан движок Unity3D, который так же поддерживает C#.
Так, для случаев, когда требуется создать классическое интерфейсное приложение скомпилированное под несколько платформ, возможно использование Xamarin и его Xamarin.Forms. При это, если необходимо применение 3D-графики, а также дополненной и виртуальной реальностью, то оптимальным будет использование Unity3D. Работа с 3D-графикой в Xamarin возможна, но значительно сложнее. Разрабатывать интерфейсы в Unity3D также возможно, но функциональность инструментария недостаточна, а плавность и отзывчивость интерфейса ниже. Кроме этого существуют наработки, побуждающие разработчиков работать в одной или другой среде.
Таким образом существует потребность использования удобной работы с 3D-графикой и хороший, легко изменяемый, интерфейс в одном приложении. При этом, привлекая к разработке по большей части только программистов специализирующихся на языке C#.
Начиная с версии Unity3D 2019.3, появилась возможность интегрировать проект в качестве библиотеки в нативные Android и iOS приложения. Однако больший интерес представляет возможность встраивать Unity3D в Xamarin приложение, используя при этом один и тот же Xamarin проект для обоих платформ.
В версиях Unity3D 2019.3+ изменилась структура экспортируемого проекта, как для iOS, так и для Android. Из-за чего весь основной проект заключен в библиотеке unityLibrary и UnityFramework, а для его запуска используется так называемая обертка (Launcher и Unity-iPhone). Особый интерес представляет возможность ее замены на альтернативный Xamarin проект.
Описание пути достижения подобного результата и является основной целью этой статьи.
Предварительная подготовка Unity3D
Для начала нужен Unity3D проект, с самим проектом нужно будет сделать минимум изменений. До внесения изменений желательно убедиться, что приложение работает как нужно. В настройках необходимо выставить Bundle Id для iOS и Android. Также можно выставить Signing Team Id для iOS, это может помочь далее в процессе подписи приложения.
Необходимо добавить AndroidManifest.xml, где необходимо закомментировать <category android:name="android.intent.category.LAUNCHER"/>, эта строка отвечает за вывод иконки приложения в меню Android. В данном случае эта иконка будет лишней.
Также нам может понадобиться переопределить UnityActivity, с помощью OverrideUnityActivity.java, это необходимо для перехвата системных событий юнити, отвечающих за выход из приложения и за выгрузку Unity части приложения из памяти.
Подготовим Xamarin проект
Так же как в Unity3D проекте, выставим Bundle Id для iOS и Android. В нашем случае это пустой Xamarin.Forms проект, с одним общим проектом и двумя проектами под iOS и Android соответственно. Добавим в общий проект интерфейс ILaunchActivity содержащий один метод LaunchUnity. А также 2 заглушки LaunchActivity в проекты iOS и Android. Визуально он будет представлять из себя кнопку для запуска Unity3D, нажатие на которую будет вызывать метод LaunchUnity.
Подготовим IOS часть нашего приложения
1. Для начала построим Unity проект под iOS. Открываем проект в xcode, проверяем подписи для целей Unity-iPhone и UnityFramework. Сертификат разработчика должен совпадать с тем, что будет использован при построении приложения.
2. Меняем Target Membership у папки Data на UnityFramework. В папке Data содержаться ресурсы, необходимые Unity3D приложению для работы. Эти ресурсы могут попасть в результирующий проект либо в составе UnityFramework, либо непосредственно могут быть добавлены в финальный проект.
3. Сделаем билд цели UnityFramework в xcode. Возьмем получившийся UnityFramework.framework в Finder и сохраним его.
4. Открываем Xamarin Solution в Visual Studio 2019 for Mac.
5. Создаем новый проект Binding.Unity.Ios (Binding Library), он будет отвечать за взаимодействие с Unity3D. К сожалению, в случае iOS генератор API не реализован, необходимо будет описать API самостоятельно. Добавим туда код биндинга к Unity API. Это 3 cs-файла из корня репозитория.
6. Добавим сохраненный ранее UnityFramework.framework как Native Reference в проект Binding.Unity.Ios и построим проект.
Если в процессе не возникло ошибок добавим Binding.Unity.Ios в зависимости к Main.iOS.
7. Добавим реализацию запуска Unity на iOS платформе.
Часть LaunchActivity.cs:
Console.WriteLine("Unity launching on ios");
var currentWindow = UIApplication.SharedApplication.KeyWindow; // сохраним текущее окно, где мы сейчас находимся
var args = Environment.GetCommandLineArgs(); // получим аргументы запуска исходного приложения, их необходимо будет передать Unity3D части
var ufw = UnityFramework.GetInstance(); // получим ссылку на Unity библиотеку
var listener = new MyUnityListener(currentWindow, ufw); // класс будет отвечать за перехват системных событий Unity3D
ufw.SetDataBundleId("com.unity3d.framework"); // задаем местоположение папки Data, это не нужно, если папка будет добавлена в основной проект
ufw.RegisterFrameworkListener(listener); // регистрируем обработчик системных событий
ufw.RunEmbeddedWithArgc(args.Length, args, new Foundation.NSDictionary()); // запускаем
ufw.ShowUnityWindow(); // и переключаемся на окно Unity3D
Класс MyUnityListener наследуется от UnityFrameworkListener и может переопределить методы UnityDidQuit и UnityDidUnload. Перехват UnityDidUnload позволяет при вызове из Unity метода Application.Unload, перейти в основном приложении на другое окно, например так: currentWindow.MakeKeyAndVisible();
Наш проект также использовал дополненную реальность на стороне Unity3D. Для работы этому функционалу нужен доступ к камере телефона. Это необходимо указать в Info.plist проекта Main.IOS: Privacy – Camera Usage Description = Needed for AR
Проверим настройки подписи Main.IOS и построим основной проект. В результате мы должны получить проект, который по нажатию кнопки в интерфейсе запустит Unity3D часть.
Подготовим Android проект
Построим Unity3D проект для платформы Android, для этого при построении проекта надо выбрать «export project».
Далее нам будет необходимо построить часть этого проекта на Android Studio, как Android AAR библиотеку. Для этого создадим пустой новый проект в Android Studio.
Откроем Settings.Gradle и добавим туда ссылку на UnityLibrary нашего Unity3D проекта:
include ':unityLibrary'
project(':unityLibrary').projectDir=new File('../unity-app/buildAndroid/unityLibrary') // путь до unity-app/buildAndroid/unityLibrary
Так как наш проект работает с дополненной реальностью, он использует нативные .aar библиотеки ARCore, как зависимости. Созданный нами Android проект не сможет объединить их с кодом Unity в единый AAR. Поэтому необходимо закомментировать их использование:
// implementation(name: 'arcore_client', ext:'aar')
// implementation(name: 'ARPresto', ext:'aar')
// implementation(name: 'unityandroidpermissions', ext:'aar')
// implementation(name: 'UnityARCore', ext:'aar')
Построим проект UnityLibrary в Android Studio, результатом чего станет файл unityLibrary\build\outputs\aar\unityLibrary-debug.aar.
Перейдем в Xamarin, добавим там проект Android Binding Library. В Jars нового проекта добавляем ссылку на полученный ранее AAR как LibraryProjectZip. Строим Binding Library.
В случае Android – код для взаимодействия будет сгенерирован автоматически.
Добавляем binding.unity.android как зависимость в Main.Android.
Добавим код запуска Unity:
Console.WriteLine("Unity launching on android");
var intent = new Intent(Forms.Context, typeof(CustomUnityPlayerActivity)); // создаем Intent
Forms.Context.StartActivity(intent); // и запускаем его
Здесь также используется CustomUnityPlayerActivity класс, который помогает там обработать события Unity3D, он наследуется от OverrideUnityActivity (того java-класса, что был добавлен в Unity3D проект). Мы можем переопределить OnUnityPlayerQuitted и OnUnityPlayerUnloaded. В OnUnityPlayerUnloaded мы, аналогично с iOS перейдем на исходное окно.
var intent = new Intent(Forms.Context, typeof(MainActivity));
Forms.Context.StartActivity(intent);
Остается пара мелочей: необходимо добавить в проект ресурс, без которого Unity3D часть не стартует, values/strings.xml с содержимым.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="game_view_content_description">Game view</string>
</resources>
Также нужно не забыть добавить закомментированные ранее .aar библиотеки ARCore в проект Main.Android как AndroidAarLibrary.
После этого можно построить проект и протестировать его.
В результате у нас получилось 2 приложения, основанные на одной кодовой базе. Первое тесты показали, проект запускается, в нем работает дополненная реальность. Катастрофической потери производительности не наблюдается.
На данный момент желательно решить несколько задач:
- Передача стартовой конфигурации Unity3D части проекта. Сейчас в приложение может быть добавлена только одна Unity3D библиотека, таким образом, если основное приложение будет использовать Unity3D в нескольких разных местах, то необходимо указывать Unity3D части, что ей нужно запустить;
- Хотелось бы обеспечить взаимодействие между Xamarin и Unity3D в процессе выполнения;
- В текущей реализации на экране либо присутствует UI от Xamarin части, либо Unity3D часть приложения, хотелось бы использовать UI Xamarin и Unity3D одновременно;
- Процесс разработки в текущем виде слишком трудоемок. Необходимо вручную строить Unity3D проект, xcode проект, Android проект и только после этого запускать Xamarin приложение. Это необходимо автоматизировать, каким-либо образом.