Building a iOS Unity + UIView / UIViewController / Interface Builder App

2013年2月15日

The original URL : http://alexanderwong.me/post/29949258838/building-a-ios-unity-uiview-uiviewcontroller
This is back up for me.

Building a iOS Unity + UIView / UIViewController / Interface Builder App

When you build your Unity 3D project for the iPhone/iPad, Unity will generate a new Xcode project for you from scratch. It will even overwrite most files on subsequent builds. The design pattern assumes you’ll only want to run Unity in the app and nothing else (no custom UIViews or UIViewControllers, etc).
However, there are a number of cases where you might want to integrate Unity into your app and be able to show custom UIViews. Perhaps, you want to use Unity only for a portion of your application flow. Configuring your Xcode project to allow you to do this is possible, albeit quite tricky.
Before we get started, you should note:
Unity cannot be completely stopped and then started on demand (as of the writing of this post). The best you’ll be able to do is pause Unity, change to a blank scene to free up some of Unity’s memory use, and then load a new scene and resume. (In AppController.mm might notice the UnityCleanup method and wrongly assume you can call it and call applicationDidFinishLaunchingWithOptions to stop and start Unity, but you can’t. If you try, your app will crash.)
Building everything in Unity makes it simple to build your application across platforms (iPhone + Android). Conversely, integrating Unity with your Xcode project means you’ll have to port over your Objective-C.
There is no official support (currently) for writing your application this way, and so it will rest solely on your shoulders to ensure your application continues to function in the future.
Here’s the game plan for how to integrate Unity:
Use your own AppDelegate (from now on referred to as YourAppDelegate) and forward all UIApplicationDelegate methods to AppController, which you instantiate in applicationDidFinishLaunchingWithOptions in your YourAppDelegate.
Set YourAppDelegate’s window property to keep track of the window returned by AppController. Instantiate your UIViewControllers or UIViews and add them as a subview to the aforementioned window. Your views are basically overlaid on top of Unity’s view.
Expose methods to play and pause Unity.
Let’s get started:
0. Import, link, and copy all files and libraries needed by Unity into your app.
Start by copying all files in the folders named “Classes” and ”Libraries” from Unity into your app. Under “Classes”, move main.mm out of your project so you can reference it later. Also copy ”Data” but do not subversion this folder as it is generated each time your build your Unity application.
Now, under Targets > YourAppTarget > Build Phases, click “Add Build Phase” and select “Run Script”. Paste the contents of the Run Script from the Xcode project generated by Unity here. Mine looks like:
rm -rf “$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Data”
cp -Rf “$PROJECT_DIR/Data” “$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Data”
Under “Compile Sources”, check that the Unity Library files are linked. If you are using ARC in your project, add:
-fno-objc-arc
to the “Compiler Flags” next to any of Unity’s .m or .mm files.
Under “Link Binary With Libraries”, add any missing libraries from the Unity Xcode app and set the required/optional field accordingly.
1. Merge Unity’s .pch and main.mm file with your own.
For the .pch file, you can just about copy and paste its contents and overwrite your own. If you have custom code in your own file, you’ll have to merge by hand.
To merge the main.mm files, copy and paste Unity’s code into your file, but replace their UIApplicationMain call with your own:
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([YourAppDelegate class]));
}
You’ll likely have to change the extension on your main.m file to .mm. Again, if you have any custom code, merge it by hand.
2. Configure your application delegate files.
In YourAppDelegate create the following properties as applicable:
@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) UINavigationController *navigationController;
@property (strong, nonatomic) AppController *unityController; // unity
In AppController.h and AppController.mm create a getting method to return the window created by Unity.
- (UIWindow *)window
{
return _window;
}
In application didFinishedLaunchingWithOptions instantiate AppController and store the window, also keep track of any UINavigation controllers you need.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// unity
BOOL returnBOOL;
if (_unityController == nil) {
_unityController = [[AppController alloc] init];
}
returnBOOL = [_unityController application:application didFinishLaunchingWithOptions:launchOptions];

//… your code here…

// unity, set window
_window = [_unityController window];

// navigation controller
_navigationController = [[UINavigationController alloc] initWithRootViewController:…];
[_window addSubview:_navigationController.view];

//… your code here…

// unity, return
return returnBOOL;
Lastly, forward all remaining UIApplicationDelegate methods like so…
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application
{

// unity
[_unityController applicationDidReceiveMemoryWarning:application];
}

- (void)applicationWillResignActive:(UIApplication *)application
{

// unity
[_unityController applicationWillResignActive:application];
}

- (void)applicationDidEnterBackground:(UIApplication *)application
{

// unity
[_unityController applicationDidEnterBackground:application];
}

- (void)applicationWillEnterForeground:(UIApplication *)application
{

// unity
[_unityController applicationWillEnterForeground:application];
}

- (void)applicationDidBecomeActive:(UIApplication *)application
{

// unity
[_unityController applicationDidBecomeActive:application];
}

- (void)applicationWillTerminate:(UIApplication *)application
{

// unity
[_unityController applicationWillTerminate:application];
}

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{

// unity
[_unityController application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}

- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{

// unity
[_unityController application:application didFailToRegisterForRemoteNotificationsWithError:error];
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{

// unity
[_unityController application:application didReceiveRemoteNotification:userInfo];
}

- (void)application:(UIApplication*)application didReceiveLocalNotification:(UILocalNotification*)notification
{

// unity
[_unityController application:application didReceiveLocalNotification:notification];
}
Now Unity will be launched when your application launches but your application flow should be unchanged. Since your UINavigationController is added to the window after Unity, it will cover Unity. To handle switching back and forth between Unity and your views a little better, let’s add the ability to pause and resume Unity.
3. Pause and resume Unity.
Again in AppController.h and AppController.mm, create the following pause method:
- (void)unityPause:(BOOL)pause
{
UnityPause(pause);
}
In YourAppDelegate, create the corresponding methods:
// Unity

- (void)playUnity
{
[_unityController unityPause:NO];
}

- (void)pauseUnity
{
[_unityController unityPause:YES];
}
4. Swap between Unity and your UIViews.
As previously mentioned, Unity will always be running in the background. To display your own views, simply add them to the window above Unity’s view and be sure to pause / resume and clear the scene as appropriate to help alleviate the overhead of running Unity in the background.
5. Configure “Build Settings” under targets before your build.
Unity is particularly finicky about your build settings and target. In fact, you’ll need to rebuild your Unity project to switch between an iOS device target and a simulator target. The target’s “Build Settings” will also have to be configured according to your intended device. The best way to set up “Build Settings” is to simply copy and merge settings line by line from the “Build Settings” generated by Unity’s Xcode project.
Create two targets in your project, one for simulator builds and one for device builds so you don’t have to modify settings each time. Then in Unity, build the Xcode project for the simulator and then your actual device. In between builds, copy over your build settings to your Xcode project’s respective targets.
Some things to note here: Be sure to set the TARGET_IPHONE_SIMULATOR=1 preprocessor macro on your simulator build. And, if you have any -all_load linker flags, you’ll have to remove them.
6. Build your Unity + UIView app.
First build your Unity project. Then in the generate Xcode files, copy the contents of the “Data” folder into your project’s “Data” folder. Ensure that your build settings in Unity match the iOS version and device type you intend to build your Xcode project for. If you build your Unity project for your iPhone and try to run your Xcode project in the simulator it will not work, and vice-versa.
If all goes well, you might see a few warnings but no errors, and your app should start right up.
If you need more control, you can communicate with your Unity scripts from your Objective-C by following the directions in this post:

Have your say