Интеграция Unity кода в React Native

Всем привет! На связи снова команда dev.family с весьма необычной темой. В этот раз поговорим об играх. А именно, как интегрировать Unity в React Native.

На самом деле, это очевидно, что на React Native игру не напишешь. Оно и не надо. Движков, позволяющих разрабатывать игры под разные платформы и операционных системы, будь то iOS или Android, macOS или Windows, — огромное множество. Есть среди Unity и Unreal Engine. Сегодня мы посмотрим, как использовать первый из них в кросс-платформенных мобильных приложениях.

Думаю, не стоит говорить, что сделать полноценно игру на Unity удобнее. Зачем тогда вообще нужна эта странная и непонятная интеграция?

Начнем с того, что игры могут иметь обширное меню и различный функционал. Допустим, встроенный мессенджер, большие настройки, — те же гильдии внутри игры и тд. Речь не идет об AAA играх по типу Genshin, а, скорее, о маленьких инди или подобных, где всю игру можно сделать отдельным приложением, а сами механики и сцены скинуть на юнити. Также юнити хорош не только в создании игр, но и использовании таких технологий как VR или AR, где можно использовать сцены, различные модели, коллизии этих моделей и другое. Тут схожая ситуация с той, где у нас есть отдельное приложение и мы делаем переход на экран с Unity проектом.

Инициализация проекта на React Native

Для инициализации проекта на React Native вводим команду в директории, где хотим создать проект:  npx react-native init unityapp (Данный проект использует версию react-native 0.73)

Получаем стандартное начальное приложение на react-native

8d8d8893c42f2ca6dbb8c640cefffb06.png

После чего добавим пару библиотек для навигации, они нам нужны только чтобы переходить на другой экран. В нашем случае, — экран с юнити.

yarn add @react-navigation/native @react-navigation/native-stack react-native-screens react-native-safe-area-context

Затем cd ios && pod install && cd … для iOS и Android, следуя документации react-navigation, добавляем этот код в MainActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(null)
  }

И сверху добавляем импорт:

import android.os.Bundle;

После добавим два экрана, чтобы переходить с одного на другой, и сам навигатор (данный код лишь пример работы библиотек). Таким образом получаем два экрана: один — самого приложения и второй, — где будет наш юнити проект. Получается следующий результат:

ec70f915dc7b8277b7f1cc932860df64.pngc3a3b5f4db2ad21ab25aff8e032699b8.png

Собственно, с подготовкой нашего приложения мы закончили. Приступим ко второй част. Создадим проект на юнити.

Инициализация Unity проекта

Для начала создадим сам проект на Unity, если у вас нет готового. Перед этим установите Unity Hub на компьютер. Инструкцию можно найти здесь. Там есть установщики для всех платформ. После этого выберите и установите LTS версию, так как это поддерживаемая версия. Но вы и так, скорее всего, это поняли.

Теперь создадим сам Unity проект:

55744055107dab8e59eb5b9cda5cfb20.png

В данной статье мы будем рассматривать все на примере 2D игры, — клона Flappy Bird. То есть сейчас создадим 2D проект и сразу на мобильные платформы (по факту это ни на что не влияет):

075ca69b84739ea2ccfd818cf653dcc2.png

Таким образом, получаем проект и сразу открываем его.

0f5d370d191f9106349c125ce553dca5.png

Здесь вы видите уже готовый проект. Но вы ничего не пропустили. Мы решили не тратить время на описание всего, что было сделано в самом юнити, т.к. это не относится к нашей теме.

Весь код вы можете сами посмотреть, скачать или раскритиковать, если перейдете по ссылке (ссылки находятся внизу) и скачаете проект себе. Или если захотите повторить следующие этапы самостоятельно.

Собственно вот наша мини игра на Unity. Сейчас необходимо переместить ее в мобильное приложение, а по окончании игры перенести наш результат, записать его в приложении и вывести в списке с результатами. Приступим

Интеграция Unity в React Native

React Native part I

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

yarn add @azesmway/react-native-unity

После выполняем команду для установки подов под iOS:

cd ios && pod install

ВАЖНО: перед тем, как дальше работать с андроидом, выполнить yarn run android, чтобы появился файл local.properties (он хранит в себе путь к android sdk). Это понадобится в дальнейшем для запуска прилаги, когда будет установлен проект на Unity.

Unity Android

Начну с плохой новости: Hot Reload с Unity проектом мы поддерживать не сможем. Объясню почему: чтобы интегрировать игру в React Native проект, нужно собрать билды самой игры и потом перенести к нам. Как вы поняли, билды и есть та причина, которая лишает нас всякой возможности Hot Reload«а. Перейдем к сборке.

Начнем с Android платформы.

Для начал откроем саму настройку сборки (жаль, что нельзя все сделать одной командой, как в привычном JS, но имеем что имеем).

Переходим в File > Build Settings

f4d8d179f1865b5f7f17d9af734dd3a8.png

Дальше, как видно на картинке, в пункте 4 переходим в Player Settings.Раскрываем раздел Other Settings и убираем галочку с Auto Graphics API. После этого под Graphics API добавляем OpenGLES3 & OpenGLES2 (не смотрим на то, что оно пишет deprecated), нажимая на значок Плюса. Настройки Color Gamut можете сделать под себя (или оставить как есть, потому что лень разбираться ;-)).

693295caa9498656d7bf5f73e0cb8dbb.png

После этого спускаемся еще ниже до таба Configuration. Там значение поля Scripting Backend меняем с Mono на IL2CPP. Это нужно, чтобы ниже появились новые варианты Target Architectures. Там выбираем все значения (для чего? Да на всякий случай! Шутка) arm нам нужны для мобильных платформ. А x86_64 оставим, поскольку раньше мобильные смартфоны был с процессорами этой архитектуры).

63b8a6c1b92c2513be554bec40eddd60.png

На этом настройка билда для андроида закончена. Можем перейти к сборке и интеграции:

Для начале в корне нашего проекта создаем директорию unity/builds/android . В этой папке будут хранится наши сборки для iOS и Android приложений.

После нажимаем на кнопку Export и выбираем только что созданную папку, экспортируем билд для андроида.

a025fc3084e06d231fcbc971262918ec.png

Затем переходим в папку Android нашего приложения и делаем следующее: В android/settings.gradle добавляем

include ':unityLibrary'
project(':unityLibrary').projectDir=new File('..\\unity\\builds\\android\\unityLibrary')

В android/build.gradle

allprojects {
  repositories {
    // this
    flatDir {
        dirs "${project(':unityLibrary').projectDir}/libs"
    }

В android/gradle.properties

unityStreamingAssets=.unity3d

В android/app/src/main/res/values/strings.xml

Game view

Дальше переходим в нашу созданную папку unity/builds/android, после в unityLibrary/src/main/AndroidManifest.xml и в этом файле удаляем тег и то что в нем. Это может вызвать проблемы с запуском Android.

Таким образом приготовления закончены, можем добавить UnityView в наше приложение.

Заходим в App.tsx и заменяем View на UnityView

const UnityScreen = () => {
  return ;
};

И не забываем про импорт самого UnityView, а то мало ли ;).

На выходе получаем следующее:

Проблемы, с которыми мы столкнулись (Android)

При интеграции Unity проекта в React Native, хоть все было по инструкции, мы столкнулись со следующими проблемами/ошибками:

• У нас был установлен пакет mobilenotifications.androidlib и он вызывал ошибку при запуске: unityLibrary при сборке не мог его найти. Это можно исправить, закомментировав строку implementation project ('mobilenotifications.androidlib') в unityLibrary/build.gradle. На самом деле, его можно вообще выпилить, но было принято решение: работает — не трогай. Поэтому его пока что оставили.

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// implementation project('mobilenotifications.androidlib') - вот эту
}

• Отсутствие compileSdkVersion в build.gradle. Это мы поправили так: заменили compileSdkVersion на ту же что и android/build.gradle в корне проекта и добавили compileSdkVersion version в unityLibrary/build.gradle в defaultConfig.

android {
ndkPath "/Applications/Unity/Hub/Editor/2022.3.16f1/PlaybackEngines/AndroidPlayer/NDK"

compileSdkVersion 34
buildToolsVersion '34.0.0'

compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}

defaultConfig {
compileSdkVersion 34 // вот здесь
minSdkVersion 22
targetSdkVersion 33
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
versionCode 1
versionName '1.0.0'
consumerProguardFiles 'proguard-unity.txt'
}
}

• И не забудьте проверить, чтобы targetSdkVersion совпадал с тем что в android/build.gradle;)

В итоге, у нас есть рабочее приложение, написанное на React Native, которое имеет в себе игру написанную на Unity. Но это пока что только на Android. Теперь давайте перейдем к iOS.

Unity iOS

Честно говоря, для нас это была самая сложная часть, хоть и самая короткая. Далее объясним почему. Итак, приступим.

Наверное, больше всего времени ушло на часть c Libraries/Plugins/iOS, так как, если следовать инструкции самой библиотеки, нужно поменять таргет UnityFramework. Но во время сборки при переходе в папку Libraries никакой папки Plugins найдено не было. На github issues мы нашли ответ, что папку нужно добавить самим и поместить туда два файла NativeCallProxy.m & NativeCallProxy.h (их можно взять из репозитория с кодом Unity). Теперь добавляем в папку Assets > Plugins/iOS с этими двумя файлами

После этого нужно собрать билд. Переходим снова в Unity > File > Build Settings. Тут мы выбираем iOS и нажимаем на кнопку Switch Platform (по сути, как и в случае с Android).

466c177698ca1efd33e6e132f9301950.png

Далее переходим в окно Player Settings. Тут указываем Bundle Identifier как у нашего приложения, затем Signing Team ID (если делаем sign, это не обязательно). И посмотреть чтобы Scripting Backend стоял IL2CPP.

Далее в Build Settings нажимаем на кнопку Build и выбираем местоположение нашего билда, для удобства мы собрали его в [root_project]/ios/unityBuild, чтобы его не потерять ;), но это не обязательно так как весь билд в кодовой базе нам не понадобится.

После того как мы собрали билд, открываем в Xcode Unity-iPhone.xcodeproj и выполняем следующие действия:

e1df1b120750c5a16b7aee1c8ac70c98.png

• Переходим в Libraries/Plugins/iOS и выбираем NativeCallProxy.h. В Target Membership у UnityFramework меняем с Project на Public.

deaec5103a5c654ee7d21baf133db0dc.png

• Выбираем папку Data и меняем Target Membership на UnityFramework.

cf11f3b3abb74cfb9b90400907463071.png

• Далее, если требуется, выбираем команду разработки и собираем UnityFramework (сборка обязательна!!!).

7075072575b2796f123698e2db8c7840.png

• После того, как билд был собран, копируем наш UnityFramework в [root_project]/unity/builds/ios

27ad0ce2346520b5723cb11611396085.png

• В заключении выполняем командуrm -rf ios/Pods && rm -f ios/Podfile.lock && npx pod-install

После всего вышеперечисленного собираем наше приложение на iOS. Обращаем внимание, что корректно работать UnityView будет именно на реальном iOS устройстве, в отличие от Android.

Конечный результат

Заключение

Вот мы и разобрали, как устроена интеграция Unity-проекта в мобильное приложение на React Native. На самом деле, мы понимаем, что задача весьма нетривиальна, и далеко не все проекты могут в этом нуждаться. Однако, как мы и говорили в вводной части, такие примеры встречаются и имеют право на жизнь.

В статье мы пошагово рассмотрели, как устроена данная интеграция. Если тема окажется актуальной и наберет большой охват, напишем следующую, в которой мы улучшим нашу интеграцию, чтобы передавать данные с react-native в Unity и наоборот. Или вы можете попробовать сделать это самостоятельно, следуя инструкции из документации к библиотеке.

С вами была команда dev.family и до новых встреч ;)

Ссылки

• Репозиторий, где можно посмотреть код мобильного приложения

• Репозиторий, где можно взять код игры

• Библиотека

© Habrahabr.ru