Unity as a Library のMainViewController.mmをじっくり読んだ
最近取り組んでいる仕事でiOSのネイティブアプリとUnityを連携させる(UnityアプリをiOS向けにビルドするのではなく、iOSアプリ内の一部viewにUnityを利用する)必要があったので、Unityが公式で配布しているサンプルプロジェクトのブリッジ部分の実装をしっかり読みました。
冒頭
//UIkitのインポート
#import <UIKit/UIKit.h>
//UnityFrameworkのインポート
#include <UnityFramework/UnityFramework.h>
//NativeCallProxyのインポート
#include <UnityFramework/NativeCallProxy.h>
//UnityFramework型をreturnする
UnityFramework* UnityFrameworkLoad()
{
NSString* bundlePath = nil;
bundlePath = [[NSBundle mainBundle] bundlePath];
bundlePath = [bundlePath stringByAppendingString: @"/Frameworks/UnityFramework.framework"];
//bundlePathからbundleを生成
NSBundle* bundle = [NSBundle bundleWithPath: bundlePath];
if ([bundle isLoaded] == false) [bundle load];
//UnityFrameworkのインスタンスを得る
UnityFramework* ufw = [bundle.principalClass getInstance];
if (![ufw appController])
{
// unity is not initialized
[ufw setExecuteHeader: &_mh_execute_header];
}
return ufw;
}
//UIAlertControllerを表示
void showAlert(NSString* title, NSString* msg) {
UIAlertController* alert = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {}];
[alert addAction:defaultAction];
auto delegate = [[UIApplication sharedApplication] delegate];
[delegate.window.rootViewController presentViewController:alert animated:YES completion:nil];
}
//MyViewControllerのヘッダ
@interface MyViewController : UIViewController
@end
ここまでは特に問題無し。
ここから注目。AppDelegateヘッダー部分
UnityFrameworkListenerでUnityFrameworkのライフサイクルイベントを、NativeCallsProtocolでUnity側で実装した関数をリスンします。Unityのviewに載せるためのUIButtonなどの宣言もここでしています。
//AppDelegateのヘッダ
//NativeCallsProtocolをUnity側で実装しておく
@interface AppDelegate : UIResponder<UIApplicationDelegate, UnityFrameworkListener, NativeCallsProtocol>
@property (strong, nonatomic) UIWindow *window;
@property (nonatomic, strong) UIButton *showUnityOffButton;
@property (nonatomic, strong) UIButton *btnSendMsg;
@property (nonatomic, strong) UINavigationController *navVC;
@property (nonatomic, strong) UIButton *unloadBtn;
@property (nonatomic, strong) UIButton *quitBtn;
@property (nonatomic, strong) MyViewController *viewController;
@property UnityFramework* ufw;
@property bool didQuit;
- (void)initUnity;
- (void)ShowMainView;
- (void)didFinishLaunching:(NSNotification*)notification;
- (void)didBecomeActive:(NSNotification*)notification;
- (void)willResignActive:(NSNotification*)notification;
- (void)didEnterBackground:(NSNotification*)notification;
- (void)willEnterForeground:(NSNotification*)notification;
- (void)willTerminate:(NSNotification*)notification;
- (void)unityDidUnloaded:(NSNotification*)notification;
@end
AppDelegate* hostDelegate = NULL;
以下はネイティブ部分のView実装。
//MyViewCotrollerのクラス拡張
//https://qiita.com/edo_m18/items/e1f9b5d8c272853a5316
@interface MyViewController ()
//各種UIButtonを宣言
@property (nonatomic, strong) UIButton *unityInitBtn;
@property (nonatomic, strong) UIButton *unpauseBtn;
@property (nonatomic, strong) UIButton *unloadBtn;
@property (nonatomic, strong) UIButton *quitBtn;
@end
@implementation MyViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//各種UIButtonを生成してself.viewに配置
self.view.backgroundColor = [UIColor blueColor];
// INIT UNITY
self.unityInitBtn = [UIButton buttonWithType: UIButtonTypeSystem];
[self.unityInitBtn setTitle: @"Init" forState: UIControlStateNormal];
self.unityInitBtn.frame = CGRectMake(0, 0, 100, 44);
self.unityInitBtn.center = CGPointMake(50, 120);
self.unityInitBtn.backgroundColor = [UIColor greenColor];
//タップ時のアクション:AppDelegate内に定義したメソッドを利用する
[self.unityInitBtn addTarget: hostDelegate action: @selector(initUnity) forControlEvents: UIControlEventPrimaryActionTriggered];
[self.view addSubview: self.unityInitBtn];
// SHOW UNITY
self.unpauseBtn = [UIButton buttonWithType: UIButtonTypeSystem];
[self.unpauseBtn setTitle: @"Show Unity" forState: UIControlStateNormal];
self.unpauseBtn.frame = CGRectMake(100, 0, 100, 44);
self.unpauseBtn.center = CGPointMake(150, 120);
self.unpauseBtn.backgroundColor = [UIColor lightGrayColor];
//タップ時のアクション:AppDelegate内に定義したメソッドを利用する
[self.unpauseBtn addTarget: hostDelegate action: @selector(ShowMainView) forControlEvents: UIControlEventPrimaryActionTriggered];
[self.view addSubview: self.unpauseBtn];
// UNLOAD UNITY
self.unloadBtn = [UIButton buttonWithType: UIButtonTypeSystem];
[self.unloadBtn setTitle: @"Unload" forState: UIControlStateNormal];
self.unloadBtn.frame = CGRectMake(200, 0, 100, 44);
self.unloadBtn.center = CGPointMake(250, 120);
self.unloadBtn.backgroundColor = [UIColor redColor];
//タップ時のアクション:AppDelegate内に定義したメソッドを利用する
[self.unloadBtn addTarget: hostDelegate action: @selector(unloadButtonTouched:) forControlEvents: UIControlEventPrimaryActionTriggered];
[self.view addSubview: self.unloadBtn];
// QUIT UNITY
self.quitBtn = [UIButton buttonWithType: UIButtonTypeSystem];
[self.quitBtn setTitle: @"Quit" forState: UIControlStateNormal];
self.quitBtn.frame = CGRectMake(300, 0, 100, 44);
self.quitBtn.center = CGPointMake(250, 170);
self.quitBtn.backgroundColor = [UIColor redColor];
//タップ時のアクション:AppDelegate内に定義したメソッドを利用する
[self.quitBtn addTarget: hostDelegate action: @selector(quitButtonTouched:) forControlEvents: UIControlEventPrimaryActionTriggered];
[self.view addSubview: self.quitBtn];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
@end
AppDelegate実装部分
ここからは実際の連携部分の実装。
sendMsgToUnityでUnityにメッセージが送信できます。
unity側からiOSの関数を実行するにはヘッダーで宣言していたプロトコルを利用します。showHostMainWindowのNSStringを引数に持つ方は、Unity側の実装です。(名前が同じ関数があって混乱した、、、)
// keep arg for unity init from non main
int gArgc = 0;
char** gArgv = nullptr;
NSDictionary* appLaunchOpts;
@implementation AppDelegate
- (bool)unityIsInitialized { return [self ufw] && [[self ufw] appController]; }
//Unityのウインドウを表示する
- (void)ShowMainView
{
if(![self unityIsInitialized]) {
showAlert(@"Unity is not initialized", @"Initialize Unity first");
} else {
//Unityのウインドウを表示する
[[self ufw] showUnityWindow];
}
}
//初期画面を表示する
- (void)showHostMainWindow
{
[self showHostMainWindow:@""];
}
//NativeCallProxy.hにてNativeCallsProtocolとして宣言されている
//NSStringとしてUnityから送られる引数でiOS側を操作する
- (void)showHostMainWindow:(NSString*)color
{
if([color isEqualToString:@"blue"]) self.viewController.unpauseBtn.backgroundColor = UIColor.blueColor;
else if([color isEqualToString:@"red"]) self.viewController.unpauseBtn.backgroundColor = UIColor.redColor;
else if([color isEqualToString:@"yellow"]) self.viewController.unpauseBtn.backgroundColor = UIColor.yellowColor;
//windowを前面に
[self.window makeKeyAndVisible];
}
- (void)sendMsgToUnity
{
//ここでufwにメッセージを送信する
[[self ufw] sendMessageToGOWithName: "Cube" functionName: "ChangeColor" message: "yellow"];
}
AppDelegateの実装のつづき。MyViewControllerをrootViewCotrollerに。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
hostDelegate = self;
appLaunchOpts = launchOptions;
self.window = [[UIWindow alloc] initWithFrame: [UIScreen mainScreen].bounds];
self.window.backgroundColor = [UIColor redColor];
//ViewController *viewcontroller = [[ViewController alloc] initWithNibName:nil Bundle:nil];
//ここで定義したMyViewControllerを利用する
self.viewController = [[MyViewController alloc] init];
self.navVC = [[UINavigationController alloc] initWithRootViewController: self.viewController];
self.window.rootViewController = self.navVC;
//windowを前面に
[self.window makeKeyAndVisible];
return YES;
}
以下はUnityのViewの初期化。
//ufwを初期化
- (void)initUnity
{
if([self unityIsInitialized]) {
showAlert(@"Unity already initialized", @"Unload Unity first");
return;
}
if([self didQuit]) {
showAlert(@"Unity cannot be initialized after quit", @"Use unload instead");
return;
}
[self setUfw: UnityFrameworkLoad()];
// Set UnityFramework target for Unity-iPhone/Data folder to make Data part of a UnityFramework.framework and uncomment call to setDataBundleId
// ODR is not supported in this case, ( if you need embedded and ODR you need to copy data )
// Unity-iPhone / DataフォルダーのUnityFrameworkターゲットを設定して、DataをUnityFramework.frameworkの一部にし、setDataBundleIdを呼び出します。
// この場合、ODR(オンデマンドリソース)はサポートされていません(埋め込みおよびODRが必要な場合は、データをコピーする必要があります)
[[self ufw] setDataBundleId: "com.unity3d.framework"];
[[self ufw] registerFrameworkListener: self];
[NSClassFromString(@"FrameworkLibAPI") registerAPIforNativeCalls:self];
[[self ufw] runEmbeddedWithArgc: gArgc argv: gArgv appLaunchOpts: appLaunchOpts];
// set quit handler to change default behavior of exit app
[[self ufw] appController].quitHandler = ^(){ NSLog(@"AppController.quitHandler called"); };
//ufwのUIView
auto view = [[[self ufw] appController] rootView];
//各種ボタンが配置されていない場合にviewにボタンを再配置する
if(self.showUnityOffButton == nil) {
self.showUnityOffButton = [UIButton buttonWithType: UIButtonTypeSystem];
[self.showUnityOffButton setTitle: @"Show Main" forState: UIControlStateNormal];
self.showUnityOffButton.frame = CGRectMake(0, 0, 100, 44);
self.showUnityOffButton.center = CGPointMake(50, 300);
self.showUnityOffButton.backgroundColor = [UIColor greenColor];
[view addSubview: self.showUnityOffButton];
[self.showUnityOffButton addTarget: self action: @selector(showHostMainWindow) forControlEvents: UIControlEventPrimaryActionTriggered];
self.btnSendMsg = [UIButton buttonWithType: UIButtonTypeSystem];
[self.btnSendMsg setTitle: @"Send Msg" forState: UIControlStateNormal];
self.btnSendMsg.frame = CGRectMake(0, 0, 100, 44);
self.btnSendMsg.center = CGPointMake(150, 300);
self.btnSendMsg.backgroundColor = [UIColor yellowColor];
[view addSubview: self.btnSendMsg];
[self.btnSendMsg addTarget: self action: @selector(sendMsgToUnity) forControlEvents: UIControlEventPrimaryActionTriggered];
// Unload
self.unloadBtn = [UIButton buttonWithType: UIButtonTypeSystem];
[self.unloadBtn setTitle: @"Unload" forState: UIControlStateNormal];
self.unloadBtn.frame = CGRectMake(250, 0, 100, 44);
self.unloadBtn.center = CGPointMake(250, 300);
self.unloadBtn.backgroundColor = [UIColor redColor];
[self.unloadBtn addTarget: self action: @selector(unloadButtonTouched:) forControlEvents: UIControlEventPrimaryActionTriggered];
[view addSubview: self.unloadBtn];
// Quit
self.quitBtn = [UIButton buttonWithType: UIButtonTypeSystem];
[self.quitBtn setTitle: @"Quit" forState: UIControlStateNormal];
self.quitBtn.frame = CGRectMake(250, 0, 100, 44);
self.quitBtn.center = CGPointMake(250, 350);
self.quitBtn.backgroundColor = [UIColor redColor];
[self.quitBtn addTarget: self action: @selector(quitButtonTouched:) forControlEvents: UIControlEventPrimaryActionTriggered];
[view addSubview: self.quitBtn];
}
}
ボタンのタップイベントの処理
- (void)unloadButtonTouched:(UIButton *)sender
{
if(![self unityIsInitialized]) {
showAlert(@"Unity is not initialized", @"Initialize Unity first");
} else {
[UnityFrameworkLoad() unloadApplication];
}
}
- (void)quitButtonTouched:(UIButton *)sender
{
if(![self unityIsInitialized]) {
showAlert(@"Unity is not initialized", @"Initialize Unity first");
} else {
[UnityFrameworkLoad() quitApplication:0];
}
}
ライフサイクルイベントの処理unityDidUnload, unityDidQuitはUnityFrameworkListenerから提供されます。
//UnityFramework.h にて UnityFrameworkListenerとして宣言されている
- (void)unityDidUnload:(NSNotification*)notification
{
NSLog(@"unityDidUnload called");
[[self ufw] unregisterFrameworkListener: self];
[self setUfw: nil];
[self showHostMainWindow:@""];
}
//UnityFramework.h にて UnityFrameworkListenerとして宣言されている
- (void)unityDidQuit:(NSNotification*)notification
{
NSLog(@"unityDidQuit called");
[[self ufw] unregisterFrameworkListener: self];
[self setUfw: nil];
[self setDidQuit:true];
[self showHostMainWindow:@""];
}
//アプリライフサイクル毎にufw.appControllerのライフサイクルも実行する
- (void)applicationWillResignActive:(UIApplication *)application
{
[[[self ufw] appController] applicationWillResignActive: application];
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[[[self ufw] appController] applicationDidEnterBackground: application];
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
[[[self ufw] appController] applicationWillEnterForeground: application];
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
[[[self ufw] appController] applicationDidBecomeActive: application];
}
- (void)applicationWillTerminate:(UIApplication *)application
{
[[[self ufw] appController] applicationWillTerminate: application];
}
@end
最後に main関数。この `if(false)`は実行されるのか??obj-c書ける方教えて下さい。。。
//メイン処理
int main(int argc, char* argv[])
{
gArgc = argc;
gArgv = argv;
@autoreleasepool
{
if (false)
{
// run UnityFramework as main app
id ufw = UnityFrameworkLoad();
// Set UnityFramework target for Unity-iPhone/Data folder to make Data part of a UnityFramework.framework and call to setDataBundleId
// ODR is not supported in this case, ( if you need embedded and ODR you need to copy data )
// Unity-iPhone / DataフォルダーのUnityFrameworkターゲットを設定して、DataをUnityFramework.frameworkの一部にし、setDataBundleIdを呼び出します。
// この場合、ODR(オンデマンドリソース)はサポートされていません(埋め込みおよびODRが必要な場合は、データをコピーする必要があります)
[ufw setDataBundleId: "com.unity3d.framework"];
[ufw runUIApplicationMainWithArgc: argc argv: argv];
} else {
// run host app first and then unity later
UIApplicationMain(argc, argv, nil, [NSString stringWithUTF8String: "AppDelegate"]);
}
}
return 0;
}
私は大学からJavaを習った勢なので、Obj-c++という言語はもはや呪文ですね。
めちゃくちゃ苦手意識があったのですが、C言語の入門書を買うところからコツコツと読みました。。。
ニッチな内容ですがこのブリッジ部分をしっかりと理解しておけば、iOSアプリとUnityを連動させたリッチなアプリケーション開発ができるはずですので、この調子で少しずつ前進したいと思います。
この記事が気に入ったらサポートをしてみませんか?