Flutterを活用してUnity製アプリの表現をさらに広げよう!

Unity製のAndroidアプリにFlutterを組み込む

Flutter「を」Unity製Androidアプリ「へ」

本連載は、iOS/Android向けのアプリでUIの表現力を高めることを目標に、Unity製アプリにFlutterを導入した例を具体的な実装方法を交えながら紹介する記事の2回目となります。

前回の記事で、なぜUnity製アプリにFlutterを導入するのかという話をしているのでまだ見ていない方はそちらも合わせて読んでいただけると幸いです。

Unity製のアプリにFlutterを組み込むためにはいくつかの手順が必要となるので今回から複数回にわたり実際のコードを交えながら解説していきます。

2回目となる本記事はUnity製のAndroidアプリにFlutterを組み込む方法の解説となります。

なお、本連載はUnityとFlutterの連携がメインとなるのでUnityとFlutterの基本的な部分の説明は省略させていただきます。

開発環境

具体的な話に入る前にまずは今連載を通して使用するマシンやソフトウェアのバーションを紹介します。

OS(macOS⁠チップはApple M3 Pro)

$ sw_vers
ProductName:            macOS
ProductVersion:         14.4.1
BuildVersion:           23E224

Unity

m_EditorVersion: 2022.3.24f1
m_EditorVersionWithRevision: 2022.3.24f1 (334eb2a0b267)

Flutter

$ flutter --version
Flutter 3.19.5 • channel stable • ssh://[email protected]/flutter/flutter.git
Framework • revision 300451adae (3 weeks ago) • 2024-03-27 21:54:07 -0500
Engine • revision e76c956498
Tools • Dart 3.3.3 • DevTools 2.31.1

Android Studio

Android Studio Iguana | 2023.2.1 Patch 2
Build #AI-232.10300.40.2321.11668458, built on April 4, 2024
Runtime version: 17.0.9+0-17.0.9b1087.7-11185874 aarch64

Xcode

Version 15.3 (15E204a)

各OSとUnity⁠Flutterの関係性

具体的な実装の話に入る前に、各OSでアプリ、Unity、Flutterがどのような関係になっているかを簡単に見ていきます。

通常であればAndroidもiOSもアプリの実行時にあらかじめデザインされたレイアウトをViewとして画面上に表示を行っています。

Unity/Flutterで開発をしていると各Engineがネイティブ側を隠蔽してくれるのであまり意識せずにアプリを開発することができますが、最終的にはAndroid、iOS上で実行しているため表示の仕組みはネイティブアプリと同じ仕組みで表示されています。

つまり、内部的に起こっていることとしてはアプリを起動したときにUnityEngine/FlutterEngineが初期化され、それぞれのエンジンがViewを生成しそのViewを使用してそれぞれのエンジンで実装されたコードを表示しているだけということになります。

ですから、UnityアプリでFlutterを動かすために必要なことはUnityからExportされたプロジェクトにFlutterを追加、アプリの初期化時にFlutterEngineを初期化しFlutter用のViewを生成することでUnityEngineとFlutterEngine両方が動くアプリができることになります。

OS上のイメージ図
https://unity.com/ja
図1

Unity製AndroidアプリにFlutterを組み込む

では具体的な実装に入っていきます。

簡略的にはなりますが、手順としては以下となります。

  1. FlutterModuleプロジェクトを作成
  2. UnityプロジェクトからAndroid(Gradle)プロジェクトをExport
  3. ExportされたAndroid(Gradle)プロジェクトに設定を追加
  4. 起動Activityクラスを作成しFlutterEngineを初期化しViewを作成

ここからは以下のようなフォルダ構成で進めていきます。

Androidビルド時にパスでFlutterプロジェクトを参照しているのでフォルダ構成が違うとうまくビルドできないので気をつけてください。

root(任意のworkフォルダ)/
 ├ unity/
 ├ module/
 └ builds
    └ android/

unity:  Unityプロジェクトフォルダ。プロジェクト名は任意で問題なし。
module: Flutterプロジェクトフォルダ。Androidビルド時にパス参照されているので名前は設定と一致させる必要がある。
builds: UnityからのExport先。

1. FlutterModuleプロジェクトを作成

Unityプロジェクトに組み込むためのFlutterプロジェクトを作成します。

基本的にはFlutter公式のAdd-to-appのページの手順を実行していくだけになります。

flutter create コマンドに -t module オプションを付けることでモジュールプロジェクトを作成することができます。

--org com.example はパッケージ名、末尾のmoduleはプロジェクト名なので自由につけて大丈夫ですがこの連載ではcom.example.moduleというプロジェクト名で進めます(AndroidStudioでFlutterSDKをいれるとAndroidStudioからFlutterプロジェクトを作成することも可能です⁠⁠。

$ flutter create -t module --org com.example module
$ flutter pub get

詳しくは以下ページを参照してください。

Integrate Flutter

次に通常のプロジェクトとモジュールプロジェクトの違いについて説明します。

通常のプロジェクトであればプロジェクト作成するとフォルダ内にandroid、iosといった各プラットフォームのネイティブプロジェクトのフォルダが生成されそのプロジェクトを使用してビルドされることになります。

モジュールプロジェクトの場合は直接アプリをビルドすることを意図していないのでandroid/iosフォルダは存在しません。

プロジェクト作成後に flutter pub get などのコマンドを実行するとプロジェクトフォルダ内に .android.ios という隠しフォルダとしてプロジェクトフォルダが生成されます。

既存プロジェクトにflutterを組み込む際にこの隠しフォルダのプロジェクトが使われることになります。

2. UnityプロジェクトからAndroid(Gradle)プロジェクトをExport

Unity側の設定は、ScriptingBackendを IL2CPP に設定し、Target Architectures で ARM64 を有効にします。

ScriptingBackend
図2

BuildSettingsの Export Project にチェックを付けてExportを実行してください。

Export時に出力先を指定する必要があるため上述した、root/builds/android を指定してください。

Export
図3

3. ExportされたAndroid(Gradle)プロジェクトに設定を追加

UnityからExportされたAndroidプロジェクトに手動でFlutter依存を追加していきます。

UnityからExportされたAndroidプロジェクトをAndroidStudioで開くと以下のような構造になっていると思います。

AndroidはGradle(ビルドツール)を使用してビルドされるためGradleの設定ファイルにFlutterの設定を追加する必要があります。

android/gradle.propertiesandroid/settings.gradleandroid/unityLibrary/build.gradleの3つに設定を追加していきます。

AndroidProject
図4

android/gradle.properties(プロジェクト全体の設定)

org.gradle.jvmargs=-Xmx4096M
org.gradle.parallel=true
unityStreamingAssets=
unityTemplateVersion=5
+// Flutter側でAndroidXを使用している
+android.useAndroidX=true
+// Flutter側にアプリのホスト名を知らせる必要がある
+flutter.hostAppProjectName=launcher

android/settings.gradle(gradleのマルチプロジェクト設定⁠このプロジェクトがunityLibraryとflutterに依存していることが記述される)

include ':launcher', ':unityLibrary'

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
    repositories {
        
        google()
        mavenCentral()
        flatDir {
            dirs "${project(':unityLibrary').projectDir}/libs"
        }
+       // 依存解決のためにflutter repositoryを追加
+       maven {
+           url 'https://storage.googleapis.com/download.flutter.io'
+       }
    }
}

+// Localに存在するFlutterModuleプロジェクトをパス指定でプロジェクトに組み込み
+setBinding(new Binding([gradle: this]))
+evaluate(new File(
+        settingsDir.parentFile,
+        '../module/.android/include_flutter.groovy'
+))

android/unityLibrary/build.gradle

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

+    // unityLibraryにからFlutterを参照できるように追加
+    implementation project(path: ':flutter')
}

4. 起動Activityクラスを作成しFlutterEngineを初期化しViewを作成

通常のUnityアプリであれば起動クラスはUnity側で生成されたものを使用しますが、そこにFlutterの処理を追加したいので自分で用意し起動時にこのクラスが呼ばれるように設定します。

自分で用意すると言っても基本的にはUnityが生成している com.unity3d.player.UnityPlayerActivity のクラスをベースにFlutterのために必要な処理を追加しただけです。

ソースコードの全体を書くと長くなってしまうので、記事では編集部分のみを紹介します。ソースコードは以下リンクよりDLしてください。

MainActivity.java

ソースコードの編集準備

android/unityLibrary/src/java/com.example.module Packageを追加し、 android/unityLibrary/src/main/java/com/example/module に MainActivity.java ファイルをコピーしてください。

UnityPlayerActivityとの差分を見てもらうと何が追加されているのかがわかりやすいと思います。

それでは、すべてを解説すると長くなってしまうので初期化部分の重要なところだけを抜粋して解説していきます。

FlutterEngineの生成

Engineの生成でポイントとなるのが executeDartEntrypoint です。

サンプルではdefault値をセットしていますが、default値は main() になります。
Flutter側のサンプルを見るとエントリポイントとして main() が存在しこの関数をエントリポイントとして指定しています。

private static final String FLUTTER_ENGINE_ID = "engine_1";

~~~

// FlutterEngineのインスタンスを生成
flutterEngine = new FlutterEngine(this);

// FlutterFragment生成時にCacheEngineを使用するのでCacheに登録しておく、FLUTTER_ENGINE_IDを使用してEngineを取得できる。
FlutterEngineCache.getInstance().put(FLUTTER_ENGINE_ID, flutterEngine);

// Flutter側のエントリポイントを指定。default = main()
flutterEngine.getDartExecutor().executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault());

FlutterPluginのための設定

Flutter側に何のPackageも追加せずに使用するなら不要かもしれませんが、Flutterプロジェクトに追加でPackageを入れる場合この処理が必要になります。

// FlutterのPlugin管理のためEngineをセット
GeneratedPluginRegistrant.registerWith(flutterEngine);

FlutterViewを作成

既存アプリにFlutterを組み込み表示するためにはいくつかの選択肢がありますが今回はFragmentとして表示することにしました。

そのためUnityPlayerActivityではただのActivityが継承されていますが、FragmentActivityに変更しています。

詳細や他の選択肢に関しては公式ページを参照してください。

https://docs.flutter.dev/add-to-app/android/add-flutter-screen https://docs.flutter.dev/add-to-app/android/add-flutter-fragment https://docs.flutter.dev/add-to-app/android/add-flutter-view

FragmentActivityを継承することで使用することができる getSupportFragmentManager でFragmentManagerを取得し、FlutterFragmentViewを生成、表示を行っています。

// FragmentActivityを継承するように変更
public class MainActivity extends FragmentActivity implements IUnityPlayerLifecycleEvents {

    private static final String TAG_FLUTTER_FRAGMENT = "flutter_module";

    ~~~

    // CacheしてあるEngineを使用しFlutterFragmentを作成する
    flutterFragment = FlutterFragment.withCachedEngine(FLUTTER_ENGINE_ID).renderMode(RenderMode.surface).transparencyMode(TransparencyMode.transparent).build();

    // FragmentManagerを使用し実際にViewを表示
    getSupportFragmentManager().beginTransaction().add(rootViewId, flutterFragment, TAG_FLUTTER_FRAGMENT).commit();

    ~~~
}

AndroidManifest.xmlで起動クラスを変更

android/unityLibrary/src/AndroidManifest.xmlに記述してある com.unity3d.player.UnityPlayerActivitycom.example.module.MainActivity に変更します。

これで先ほど作成したMainActivityが、アプリ起動時に呼び出されることになります。

この状態でアプリをビルドするとUnityプロジェクトにFlutterを追加された状態でビルドされ、初期化時にFlutter表示を行っているので起動するとFlutterの画面が表示されるはずです!

UnityアプリでFlutterが起動している
図5

ビルド自動化

UnityからAndroidプロジェクトをExportしているのでUnity側を変更し、ビルドし直すたびにプロジェクトに変更がかかってしまいます。

なのでUnityプロジェクトにFlutterを組み込むにはビルドの自動化が非常に重要になってきますので自動化の知見も少し紹介します。

起動Activityクラスを追加

MainActivity.java のファイルをUnityの Assets/Plugins/Android/ に追加するだけで自動でAndroidプロジェクトに取りこまれます。

Gradleの設定追加

UnityにはAndroidManifestの設定やGradle設定を変更できる機能があるのでそこにFlutterの設定をあらかじめ設定しておきます。

PlayerSettings -> Android -> Android publishing に各設定ファイルのTemplateを作成し任意の設定を追加できる機能があるので、必要な設定ファイルにチェックを入れFlutterの設定を記述すればExportした際にFlutterの設定が入った状態でプロジェクトが生成されます。

AndroidPublish設定
図6

まとめ

かなり駆け足になってしまいましたがUnityからExportしたAndroidプロジェクトにFlutterを組み込む方法を紹介しました。

基本的には公式の Flutter Add-to-app に書かれている既存アプリにFlutterを入れる方法をUnityからExportされたプロジェクトに対して実行しているだけとなります。

私自身がメインはUnityでAndroid/iOS側にそこまで詳しい訳では無いのでもっと良い実装方法があるとは思いますが一例として読んでいただけると幸いです。

冒頭の方でも書きましたが、UnityEngineとFlutterEngineがそれぞれのViewを制御していて、アプリ側は各EngineとViewの初期化とOS側のイベントを各Engineに通知しているだけです。

この関係を正しく理解できているとコードも読みやすいと思いますしそこまで難しいことはやっていないことがわかると思います。

次回はUnityからビルドされたiOS(Xcode)プロジェクトに対してFlutterを組み込むという内容となります。

おすすめ記事

記事・ニュース一覧