面白プロデュース事業部 技術部の藤澤です。主にUnityを扱った案件を担当しています。
この記事では、flutter unity widgetを使ってFlutterアプリ上に、 Unity製のVPSに対応したARアプリを組み込む方法についてご紹介します。
以前ハッカソンにて、Flutterアプリ上で動作する特定の空間に紐づいたAR機能を実装する必要がありました。Flutter経験が皆無の私でも実装できるアプローチを探った結果、flutter unity widgetというFlutter Packageの存在を知りました。
flutter unity widget
flutter unity widget はビルドしたUnityをFlutterアプリ上にWidgetの形式で組み込むことができるFlutter Packageです。2Dや3Dはもちろん、なんとARFoundation(UnityでARを実装することができるPlugin)も動くようです。
VPS(Visual Positioning System)
VPSは、サービス上に事前に登録された、現実空間を撮影した画像群(マップ)と、端末からのリアルタイムなGPSやカメラ画像情報等を紐づけることで、より正確な方向や位置を推定する技術を指します。
この技術を用いることで、特定のランドマークに訪れたことを判定して、AR演出を実行することも比較的容易になります。
いくつかのサービスからVPSを実現できますが、今回はImmersalというサービスを採用しました。
開発環境
FlutterやUnity、各種Pluginは以下のバージョンで構成されていました。
Flutter 3.13.4 flutter_unity_widget ^2022.2.0 Unity 2021.3.25f1 ARFoundation 4.2.8 XR Plugin Management 4.4.0 ARCore XR Plugin 4.2.8 Immersal SDK 1.19.2 Universal RP 12.1.11
また今回の対象プラットフォームはAndroidとし、開発環境にOpenJDKとAndroidSDK & NDKToolsがインストール済みの状態とします。
簡単なサンプルが動くところまで
まずは、SkyboxとCubeが写っているシンプルな画面がFlutterアプリ上に表示されるようにしてみます。
flutter unity widgetの導入にあたって、こちらの記事をとても参考にさせて頂きました。
pubspec.ymlの編集
pubspec.ymlにflutter unity widgetを追加します。
dependencies: flutter_unity_widget: ^2022.2.0
今回はFlutter 3.13.4なので、2022.2.0を導入しました。
Unityプロジェクトの作成、local.propertiesの編集
Flutterプロジェクトroot直下に、unityというディレクトリを作成し、その下にUnityプロジェクトを作成します。
Unityプロジェクト作成後、Edit>Preferences>External Tools
を開き、Android NDK Installed with Unity
の欄からNDKのpathをコピーします。
Flutterプロジェクト内のandroid/local.properties
を開き、以下を追記します。
ndk.dir={Android NDK Installed with Unityでコピーしたpath}
UnityPackageをインポート
flutter-unity-view-widgetのリポジトリからUnityPackageをダウンロードし、対象のUnityプロジェクトにインポートします。いくつか種類がありますが、今回はfuw-2022.2.0.unitypackage
を使用しました。
BuildSettingsとPlayerSettingsの変更
File>BuildSettings
を開き、PlatformTargetをAndroidで選択し、SwitchPlatformを実行します。
PlayerSettingsを選択し、以下の設定を変更します。
ScriptBackend -> IL2CPP Target Architectures -> ARMv7, ARM64を選択
ビルドデータの出力
いよいよFlutterに組み込むためのビルドデータを生成します。Flutter>ExportAndroid(Debug)
を選択で、ビルドが開始されます。
ビルド後、ConsoleViewに-- Android Debug Build: SUCCESSFUL --
というログが出力されていれば、正常にビルド完了しているようです。
Flutterプロジェクトにandroid/unityLibrary
という形でビルドデータが生成されているはずです。
dartファイルの作成
Unityを組み込んだページを作成します。リポジトリのREADMEに記載されているサンプルを利用します。
minSdkVersionの変更
私の環境の場合、このまま実行しようとするとminSdkVersion
でバージョンが合わずエラーを出力しました。android/app/build.gradle
のminSdkVersion
にUnityProjectで指定されているMinimum API Level
を指定することで、対処しました。
Flutterアプリが書き出される
正常に実行されると、Unityが組み込まれたFlutterアプリが書き出されているかと思います。
ARFoundation(ARCore)を動かす
本題のARが動く状態にします。
各種Packageをインポート
Window>Package Manager
を選択し、以下をインポートします。
ARFoundation 4.2.8 XR Plugin Management 4.4.0 ARCore XR Plugin 4.2.8
特にXR Plugin Management
のバージョンが重要で、4.3.1〜4.3.3
をインポートしてしまうと、ビルド生成時にAndroidManifest.xml
を毎回破棄してしまう不具合が発生して、正常に動作しないようです。そのため4.2.2 or 4.4
をインポートしてくださいとREADMEに記載されています。
BuildSettingsを変更
ARを動かすには、追加で以下のBuildSettingsの変更が必要になります。
Auto Graphics API -> チェック外す Graphics APIs -> OpenGLES3だけを選択 XR Plug-in Management -> ARCoreをチェック Minimum API Level -> Android 7.0 'Nougat'
またUnity側の変更に伴い、Flutterプロジェクト内build.gradle
のminSdkVersionを再度変更する必要があります。
settings.gradleを変更
ここで一度ビルドしてみると、私の環境では以下のようなエラーが出力されました。
A problem occurred evaluating project ':unityLibrary'. > Project with path 'xrmanifest.androidlib' could not be found in project ':unityLibrary'.
調べた結果、以下のIssueに辿り着きました。
どうもandroid/settings.gradle
に以下を追加する必要があるようです。
include ':unityLibrary:xrmanifest.androidlib'
無事動きました。 Unityが実行されてもカメラ映像が真っ黒だ!みたいな場合は、アプリにカメラアクセスの権限が付与されていない可能性が高いです。
Immersalを動かす
次は、Immersalを使ってVPSができるようにしてみます。
といっても、こちらはFlutter用に何か特別なことをする必要はなく、通常通りにImmersalを導入すれば動いてしまいます。Immersalの導入方法の詳細は割愛させて頂きますが、ざっくり以下の準備をします。(導入方法の詳細は公式ドキュメントをご参照ください)
・Mapperアプリを使用して、検出したい空間の写真を複数枚撮影(今回は40枚) ・ImmersalSDKをUnityプロジェクトに導入 ・各種必要なコンポーネント(ImmersalSDK、ARSpace、ARLocalizer)をアタッチ
以下のような空間マップ情報を登録し・・・
こんな感じで、実空間を検出できるようになりました。
FlutterとUnity間で通信する
FlutterとUnity間で値を送れるようです。
FlutterからUnityに値を送る
Flutter側は、UnityWidget
のonUnityCreated
で受け取れる UnityWidgetController
から以下のようの値が送ることができます。
// 第1引数はメッセージを送りたいコンポーネントがアタッチされているGameObject名 // 第2引数はコールしたいメソッド名 // 第3引数は送りたい値の情報 _unityWidgetController.postMessage('GameObjectName', 'MethodName', variable);
Unity側は、postMessage
で指定した名前のGameObjectに、指定した名前のメソッドが定義されたコンポーネントをアタッチすることで、値を受け取ることができます。
UnityからFlutterに値を送る
Unity側は、UnityMessageManager
というコンポーネントをScene上のGameObjectにアタッチして、以下のように値を送ることができます。
UnityMessageManager.Instance.SendMessageToFlutter(variable);
Flutter側は、UnityWidget
のonUnityMessage
から値を受け取ることができます。
全部組み合わせて・・・
最終的に、こんな感じのデモを作ってみました。
まとめ
flutter unity widgetを使って、Flutter上で動くVPSに対応したAR機能をUnityで実装しました。説明は割愛しましたが、URPやScreenSpaceのInput機能など、動くか際どいと思っていた機能もしっかり動作していました。すごい。
懸念点としては、やはりUnityビルドを含む分アプリサイズは大きくなる印象だったので、その辺りのチューニングは必要になりそうです。
FlutterにUnityを組み込まないといけなくなることは少ないかもしれませんが、少しリッチな3Dの表現や、豊富なUnity向けSDKをFlutter側に持ち込めるのは、ユニークなコンテンツを作る手助けになるかもしれないと感じました。