きっかけ
弊社で運用中のアバターを介してビデオ通話ができるSNSアプリがすべてUnityで実装されており、UI/UXのカスタムが非常に難しい状況でした。
そこでUaalを導入し、ビデオ通話中のアバターの表示やプロフィールアバターの表示等はUnity、それ以外のSNS機能やサーバーとの通信はネイティブで行うことで役割分担を進めようと方針が決まりました。
Unity as a Libraryとは
Unity as a Libraryとは、ゲームエンジンUnityで開発した機能をAndroidやiOSのNativeアプリにモジュールとして組み込む技術です
利用用途として、例えば3D描画のあるSNSアプリを作りたいとなったとします。Unityは3D描画部分の開発に強いですが、SNS部分は弱いです。逆にNativeはSNS部分の開発に強いですが、3D部分はそこまで強くありません。そのようなときに、Unity as a Libraryを使うと、3D部分は3Dに強いUnityで作り、SNS部分はNativeで作ることが可能になります。
このように、Unityが強い部分はUnityが作り、Nativeが強い部分はNativeでアプリを作ることが可能となります。
UnityからエクスポートとAndroidプロジェクトへのインポート
unityプロジェクトのexport後、出力されたunityLibraryをandroidにインポートするまでの手順は以下の記事を参考にしました。
今回はandroidの設定以降の手順を備忘録として残しておこうと思います。
設定
プロジェクト内でインポートしたUnityLibraryを扱えるように設定していきます。
それぞれのプロジェクトによって多少の違いがありますのでご理解ください。
build.gradle(:app)
dependenciesブロックに以下を追記します。
implementation project(':unityLibrary')
implementation fileTree(dir: project(':unityLibrary').getProjectDir().toString() + ('\\libs'), include: ['*.jar'])
defaultConfigにndkブロックを追記します。
ndk {
abiFilters "armeabi-v7a", "arm64-v8a"
}
string.xml
xmlに以下を追記。
Unityから参照されているらしく、String.xmlに追加しないとクラッシュしました。
<string name="game_view_content_description">Game view</string>
gradle.propeties
以下の情報を追加
android.nonTransitiveRClass=true
org.gradle.jvmargs=-Xmx4096M
org.gradle.parallel=true
unityStreamingAssets=.bytes, .txt
unityTemplateVersion=3
setting.gradle
unityLibraryフォルダをAndroid Studioで認識できるようにするため、dependencyResolutionManagementの上に以下を追記。
include ':unityLibrary'
project(':unityLibrary').projectDir=new File('unityLibrary')
dependencyResolutionManagementブロック内でflatDirブロックを追加。
flatDir {
dirs "${project(':unityLibrary').projectDir}/libs"
}
build.gradle(:unityLibrary)
特に修正は必要ありませんが、Unityのバージョンが古いと下記の修正が必要になるかもしれません。
- namespaceを追記
- repositoryブロックを削除
- ndkパスの削除
ViewにUnityProjectを表示
AndroidアプリのViewにUnityのViewを表示していきます。
Viewの表示にはUnityPlayerのインスタンス化が必要になります。UnityPlayerはUnityの再生、停止、AndroidとUnityのデータのやり取りを管理するクラスです。
まずはUnityPlayerを管理するクラスをシングルトンで作成します。
class UnityManager private constructor() {
private var unityPlayer: UnityPlayer? = null
companion object {
val shared = UnityManager()
}
// Unityを初期化する
fun initializeUnity(context: Context) {
if (unityPlayer == null) {
unityPlayer = UnityPlayer(context as Activity)
}
}
// Unityのビューを取得する
fun getUnityView(): UnityPlayer? {
return unityPlayer
}
// Unityを起動する
fun startUnity() {
unityPlayer?.onResume()
LogUtils.debug("UnityManagerLog", "🚀 Unityを起動しました")
}
// Unityを停止する
fun stopUnity() {
unityPlayer?.onPause()
LogUtils.debug("UnityManagerLog", "🚀 Unityを停止しました")
}
// Unityのウィンドウを表示する
fun showUnity(boolean: Boolean) {
unityPlayer?.requestFocus()
unityPlayer?.windowFocusChanged(boolean)
LogUtils.debug("UnityManagerLog", "🚀 Unityウィンドウを切り替えました")
}
// Unityにメッセージを送信する
fun sendMessageToUnity(objectName: String, functionName: String, argument: String) {
unityPlayer?.let {
LogUtils.debug("UnityManagerLog", "Unityに送信するメッセージ: $objectName, $functionName, $argument")
UnityPlayer.UnitySendMessage(objectName, functionName, argument)
}
}
}
Unityは最初の起動に数秒かかるため、ビデオ通話時にスムーズにUnityViewを表示できるよう、アプリ起動時にMainActivityであらかじめUnityPlayerを起動しておきます。
private fun initUnityPlayer() {
UnityManager.shared.initializeUnity(this)
UnityManager.shared.startUnity()
}
最後にFragment内でUnityViewを表示したいLayoutにaddします。
ちなみにshowUnityメソッド内にあるunityPlayer.windowFocusChanged()のメソッドを直前に呼ばないとUnityのViewは表示されません。(理由はよく分からないため有識者の方ご教授お願いします🙇)
val unityView = UnityManager.shared.getUnityView()
UnityManager.shared.showUnity(true)
binding.mainVideoGridLayout.addView(unityView)
ビルドが通り、UnityのViewが正常に表示されれば完成です!
他にもUnityPlayer.UnitySendMessageを使用すると、UnityとAndroidでデータのやり取りが可能です。
完成図 |
---|
最後に
情報が少ないこともあってか、導入から実際の運用に至るまでかなり時間がかかりました!
今回の開発を通して、Androidの設定周りが少し理解できました✌️
AndroidアプリにUaalを導入することがあれば参考にしてみてください。
参考