flutter unity widgetを使ってVPS対応のARアプリをFlutterに組み込んでみた

面白プロデュース事業部 技術部の藤澤です。主に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の導入にあたって、こちらの記事をとても参考にさせて頂きました。

qiita.com

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.gradleminSdkVersionに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に辿り着きました。

github.com

どうも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側は、UnityWidgetonUnityCreatedで受け取れる UnityWidgetControllerから以下のようの値が送ることができます。

// 第1引数はメッセージを送りたいコンポーネントがアタッチされているGameObject名
// 第2引数はコールしたいメソッド名
// 第3引数は送りたい値の情報
_unityWidgetController.postMessage('GameObjectName', 'MethodName', variable);

Unity側は、postMessageで指定した名前のGameObjectに、指定した名前のメソッドが定義されたコンポーネントをアタッチすることで、値を受け取ることができます。

UnityからFlutterに値を送る

Unity側は、UnityMessageManagerというコンポーネントをScene上のGameObjectにアタッチして、以下のように値を送ることができます。

UnityMessageManager.Instance.SendMessageToFlutter(variable);

Flutter側は、UnityWidgetonUnityMessageから値を受け取ることができます。

全部組み合わせて・・・

最終的に、こんな感じのデモを作ってみました。

まとめ

flutter unity widgetを使って、Flutter上で動くVPSに対応したAR機能をUnityで実装しました。説明は割愛しましたが、URPやScreenSpaceのInput機能など、動くか際どいと思っていた機能もしっかり動作していました。すごい。

懸念点としては、やはりUnityビルドを含む分アプリサイズは大きくなる印象だったので、その辺りのチューニングは必要になりそうです。

FlutterにUnityを組み込まないといけなくなることは少ないかもしれませんが、少しリッチな3Dの表現や、豊富なUnity向けSDKをFlutter側に持ち込めるのは、ユニークなコンテンツを作る手助けになるかもしれないと感じました。

ARコンテンツをカヤックと一緒につくりたい方は、下記よりお問い合わせください!

www.kayac.com

カヤックではユニークなコンテンツを作りたいエンジニアを募集しています!

hubspot.kayac.com