本連載は、iOS/
前回の記事ではUnityからExportしたAndroidプロエジェクトに対してFlutterを組み込む方法を解説しました。
3回目となる今回はUnity製のiOSプロジェクトにFlutterを組み込む解説となります。
前回までのおさらい
まず今回の本題であるUnityから出力されたiOSプロジェクトにFlutterを入れる方法を解説する前に前回までの話しをおさらいします。
前回までの記事で、
- なぜUnity製アプリにFlutterを入れる必要があるのか
- アプリ、Unity、Flutterの関係性
- 今回の記事の内容を実行している環境の紹介
- FlutterのModuleプロジェクトの作成
- UnityからExportされたAndroidプロエジェクトにFlutterを追加する
以上のことを解説しています。
今記事は前回までの記事を読んでいることを前提に解説を進めますので、まだ読まれていないかたはぜひそちらを読んでください。
Unity製のAndroidアプリにFlutterを組み込む
Unity製iOSアプリにFlutterを組み込む
では具体的な実装の解説に入っていきます。
簡略的にはなりますが、手順としては以下となります。
- UnityプロジェクトからXcodeプロジェクトをBuild
- XcodeプロジェクトにPodfileを追加しxcworkspaceを作成
- iOS起動時にFlutterEngineを初期化しViewを作成
前回の記事でも説明しましたが、ここから以下のようなフォルダ構成で進めていきます。
CocoaPods実行時にパスでFlutterプロジェクトを参照しているのでフォルダ構成が違うとうまくビルドできないので気をつけてください。
root(任意のworkフォルダ)/
├ unity/
├ module/
└ builds
└ ios/
unity: Unityプロジェクトフォルダ。プロジェクト名は任意で問題なし。
module: Flutterプロジェクトフォルダ。CocoaPods実行時にパス参照されているので名前は設定と一致させる必要がある。
builds: UnityからのBuildしたプロジェクトを配置。
前回のAndroidの記事でも紹介しましたが、基本的にはFlutterの公式に書かれていることをUnityプロジェクト向けにカスタマイズして実装を進めて行きます。
1. UnityプロジェクトからXcodeプロジェクトをBuild
Unity側は基本的にデフォルトの設定で問題ありませんが、Identificationの設定は各自でお願いします。
BuildSettingsでPlatformをiOSに変更しBuildを実行してください。
Build時に出力先を指定する必要があるため上述したroot/
を指定してください。
2. XcodeプロジェクトにPodfileを追加しxcworkspaceを作成
XcodeプロジェクトにFlutterの依存を追加するためにCocoaPodsを使用し xcworkspace
を作成します。
podコマンドが必要なためインストールしていなければ CocoaPods
のインストールを行ってください。
この記事で使用しているversionです。
$ pod --version
1.15.2
Flutterの公式ページを参考にPodfileを用意します
https://
以下の内容を Podfile
という名前のファイルとして保存しroot/
に配置し pod
コマンドを実行します。
source 'https://cdn.cocoapods.org/'
platform :ios, '12.0'
flutter_application_path = '../../module'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
target 'Unity-iPhone' do
use_frameworks!
use_modular_headers!
install_all_flutter_pods(flutter_application_path)
end
post_install do |installer|
flutter_post_install(installer) if defined?(flutter_post_install)
end
$ cd root/builds/ios
$ pod install
podコマンドを実行すると root/
が作成されるのでxcworkspaceをXcodeで開きます。
3. iOS起動時にFlutterEngineを初期化しViewを作成
Androidと同じようにUnityEngineの初期化コードを編集してFlutterEngineを初期化したいところではありますが、iOS側は少し複雑な手順を踏む必要があります。
まず注意すべきなのはPodfileで Unity-iPhone
ターゲットにFlutterの依存を入れているところです。
UnityのXcodeプロジェクトには Unity-iPhone
と UnityFramework
という2つのターゲットが存在しており、Unityの処理は基本的にUnityFramework側に集中しています。
こう説明するとPodfileでUnityFramework側にFlutterの依存を追加すればよいのでは?
その場合はUnity-iPhone、UnityFramework両方にFlutterの依存を追加することでFlutterを動作させることが可能となります、シンプルなFlutterプロジェクトであれば恐らく問題は無いと思うのですがFlutter側にFirebaseなどのpackageを入れた場合に両方のターゲットにFirebaseが含まれている構成になってしまいFirebaseの初期化に失敗し正しく動作しないという問題が発生してしまいます。
そこでFlutterの依存をUnity-iPhone側だけに集中させ、Unity-iPhone側でFlutterEngineの初期化を行い、UnityFramework側から必要に応じてイベントをUnity-iPhone側に送りFlutterの処理を実行するという形で実装します。
余談となりますがAndroidプロジェクトでも同じ構成になっており launcher
というプロジェクトに対して unityLibrary
というライブラリを追加する構成になっています。
本体となるアプリ
この構成になっているからこそ既存のAndroid/Unity as a Library
を実現できているのだと思われます。
UnityFrameworkからアプリイベントを通知
まずはUnityFramework側に存在するアプリイベントを取得する処理を追加します。
今回Targetをまたいでイベントをやり取りするために UNUserNotificationCenter
を使用します。
Unity側のアプリ初期化処理は ios/
に記述されているのでこのファイルを編集してきます。
・
+#import <UserNotifications/UserNotifications.h>
・
(BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
~~~
[self initUnityWithApplication: application];
+ NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
+ [nc postNotificationName:@"unity2flutter" object:nil userInfo:@{@"unity_initialized":@""}];
return YES;
}
・
(void)applicationDidEnterBackground:(UIApplication*)application
{
::printf("-> applicationDidEnterBackground()\n");
// 末尾に追加
+ [[NSNotificationCenter defaultCenter] postNotificationName:@"unity2flutter" object:nil userInfo:@{@"applicationDidEnterBackground":@""}];
}
(void)applicationWillEnterForeground:(UIApplication*)application
{
// 省略
+ [[NSNotificationCenter defaultCenter] postNotificationName:@"unity2flutter" object:nil userInfo:@{@"applicationWillEnterForeground":@""}];
}
(void)applicationDidBecomeActive:(UIApplication*)application
{
// 省略
+ [[NSNotificationCenter defaultCenter] postNotificationName:@"unity2flutter" object:nil userInfo:@{@"applicationDidBecomeActive":@""}];
}
(void)applicationWillResignActive:(UIApplication*)application
{
// 省略
+ [[NSNotificationCenter defaultCenter] postNotificationName:@"unity2flutter" object:nil userInfo:@{@"applicationWillResignActive":@""}];
}
(void)applicationWillTerminate:(UIApplication*)application
{
// 省略
+ [[NSNotificationCenter defaultCenter] postNotificationName:@"unity2flutter" object:nil userInfo:@{@"applicationWillTerminate":@""}];
}
UnityFrameworkからのイベントを受け取りFlutterEngineを管理するクラス追加
今回も全文記述すると長くなってしまうのでサンプルとなるファイルを添付します。
この2つのファイルを ios/
にコピーしてください。
Xcodeのプロジェクトビューで右クリックのメニューから Add Files to "Unity-iPhone"...
を選択しこの2つのファイルを選択し追加してください。
このとき追加するTargetが Unity-iPhone
にチェックがついていることを確認してください。
Unityにネイティブコードを追加する方法はUnityのAssets/
にファイルを追加する方法が一般的ですが、この方法で追加するとXcodeプロジェクトのUnityFrameworkターゲットにファイル参照が追加されることになります。
先ほど、Unity-iPhoneターゲットとUnityFrameworkターゲットについて少し説明しましたが、CocoaPodsによりUnity-iPhone側にFlutterの依存が追加されているためUnityFramework側にFlutterのコードを書いても参照できずエラーになってしまいます。
Flutterへの参照を必要とするFlutterControllerのコードはUnity-iPhone
ターゲットから参照できるようにプロジェクトに追加する必要があります。
ポイントとなる処理を解説していきます。
NSNotificationCenterのイベントを購読
クラスの初期化時にNSNotificationCenterからのイベントを購読しています。
UnityAppController.unity2flutter
というキーを設定しイベントをpostしているので同じキーを使用して購読しています。
受け取ったイベントは unityNotification
のメソッドに渡され処理されています。
// UnityAppConttoler.mmで "unity2flutter" というキーに対して通知をポストしているので受け取る
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(unityNotification:) name:@"unity2flutter" object:nil];
FlutterEngine初期化
unity_
というイベントを受け取ったタイミングでFlutterEngineの初期化を行い FlutterViewController
の表示を行っています。
- (void)initializeFlutter {
// FlutterEngineのインスタンス作成
flutterEngine = [[FlutterEngine alloc] initWithName:@"io.flutter" project:nil];
// Flutter初期化時にエントリポイントとなるメソッドを指定
[flutterEngine runWithEntrypoint:@"main" initialRoute:@"/"];
~~~
}
FlutterPluginのための設定
// FlutterのPlugin管理のためにEngineをセット
[GeneratedPluginRegistrant registerWithRegistry:flutterEngine];
FlutterViewControllerを作成
// FlutterEngineのインスタンを使いFlutterViewControllerを作成
flutterViewController = [[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
flutterViewController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
// UnityのRootViewに対してFlutterViewを表示
[rootViewController presentViewController:flutterViewController animated:NO completion:nil];
アプリのエントリポイントでFlutterControllerを初期化
先ほど追加したFlutterControolerを実際に初期化しFlutterを呼び出します。
ios/
にアプリ本体のエントリポイントとなる処理があるのでこのファイルを編集します。
+#include "FlutterController.h"
// 省略
int main(int argc, char* argv[])
{
@autoreleasepool
{
id ufw = UnityFrameworkLoad();
+ // FlutterControllerの初期化
+ id _ = [[FlutterController alloc] init:ufw];
[ufw runUIApplicationMainWithArgc: argc argv: argv];
return 0;
}
}
この状態でアプリをビルドするとUnityプロジェクトにFlutterを追加された状態でビルドされ、初期化時にFlutter表示を行っているので起動するとFlutterの画面が表示されるはずです!
ビルド自動化
前回のAndroidと同じく、UnityからiOSプロジェクトをExportしているのでUnity側を変更しビルドし直すたびにプロジェクトに変更がかかってしまいます。
なのでUnityプロジェクトにFlutterを組み込むにはビルドの自動化が非常に重要になってきますので自動化の知見も少し紹介します。
Unityで pod install を実行
上述したPodfileを別のパスに用意しておきビルドされたXcodeプロジェクトへのコピーとpod install
の処理をUnityのPostProcessで実行します。
private const string PodfilePath = "{Podfileのパス}";
private const string PodPath = "{podコマンドのインストールパス}/pod";
[PostProcessBuild(50)]
public static void OnPostProcessPodInstall(BuildTarget buildTarget, string path)
{
File.Copy(PodfilePath, $"{path}/Podfile", true);
var info = new ProcessStartInfo
{
FileName = "bash",
Arguments = $"-l -c '{PodPath} install'",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
// コマンドを実行するディレクトリを指定
WorkingDirectory = path,
// コマンド実行時の環境変数を指定
EnvironmentVariables =
{
["LANG"] = "en_US.UTF-8",
},
};
using var process = new Process();
process.StartInfo = info;
process.Start();
process.WaitForExit();
Debug.Log(process.StandardOutput.ReadToEnd());
}
Unityのコードを編集
Unityがビルドして作成したXcodeプロジェクトのコードを編集する必要があるのですが編集をうまく自動化する手が思いつかなかったのでC#で追加したい上の行の文字列を検索しその下にコードを入れるという地道なファイル編集しています
ios/
にFlutter側へ伝える通知処理を追加Classes/ UnityAppController. mm ios/
にFlutterControllerの初期化処理を追加MainApp/ main. mm
XcodeプロジェクトにFlutterControllerクラスを追加
Unity側にXcodeプロジェクトの設定を変更するためにPBXProject
というクラスが存在しますがこのクラスではファイルの追加はできないようだったのでruby gemのxcodeproj
を使用します。
FlutterController.
require 'xcodeproj'
def add_file(project, file_path)
# すでに同じファイルが存在するかチェック
absolute_path = File.expand_path(file_path)
file_reference = project.reference_for_path(absolute_path)
if file_reference.nil?
file_ref = project.new_file(file_path)
project.targets.each do |target|
# Unity-iPhoneのターゲットにファイルを追加
next unless target.name == 'Unity-iPhone'
target.add_file_references([file_ref])
puts "File reference for '#{file_path}' added to project."
break
end
else
puts "File reference for '#{file_path}' already exist in the Xcode project."
end
end
# xcodeプロジェクトの取得
project_path = './Unity-iPhone.xcodeproj'
project = Xcodeproj::Project.open(project_path)
# xcodeプロジェクトに対してFlutter制御用のコード追加
add_file(project, './MainApp/FlutterController.h')
add_file(project, './MainApp/FlutterController.mm')
project.save
まとめ
UnityがビルドしたXcodeプロジェクトに対してFlutterModuleを組み込む実装方法を紹介しました。
前回のAndroidと同じく基本的にはFlutter公式の Flutter Add-to-app
に書かれている既存アプリにFlutterを入れる方法をUnityからビルドされたプロジェクトに対して実行しているだけとなります。
私自身がメインスキルはUnityでAndroid/
UnityアプリにFlutterを組み込むと聞くとすごく難しそうに聞こえるかもしれませんが、前回と今回で紹介した方法をお読みいただいたとおり、基本の考え方はUnityがビルドしたAndroid/
そのため、どうしてもAndroid/
今回までで単純にプロジェクトにFlutterを組み込むところまで解説しました。
次回はUnityEngineとFlutterEngineでデータのやり取りを行い実際にアプリケーションとしての動作を実装する方法の解説を行います。