This is going to appear to be complicated based on the length of this article it's really not. I try to fully show some examples here, and provide some images for those who may not know where certain things are in xcode.
This would not be possible without www.the-nerd.be, Frederik Jacques.
All of the settings in the xcconfig file, the UnityProjectRefresh.sh
script and the project import are directly derieved from his work. The video
he made in the provided link is worth watching.
This covers Unity 5+. At the time of this writing this has been
successfully used with Unity 5.5.2f1
and Swift 3.1
under Xcode 8.3.2
.
This works with storyboards.
You only get ONE unity view. You CANNOT run multiple Unity Views in your application at once. You will also need a way to communicate to <-> from your unity content to your iOS app. I would recommend an event bus in both your Unity code and your iOS code. AKA one central place on both sides to emit events to and listen to events on each side.
In other words you will need 2 busses, 1 on the Unity side that you can call into to emit events from on the iOS side, and one on the iOS side that Unity can call into to emit events on.
You can read more about communication between the 2 worlds from the following links:
More about embedding
http://forum.unity3d.com/threads/unity-appcontroller-subclassing.191971/
Specifically there is a bit on commuicating here with some sample code. Note, this is not for UNITY 5, but it shows the samples in OverlayUI related making functions available to the Objective-C side of things to be called from your Unity Code.
http://forum.unity3d.com/threads/unity-appcontroller-subclassing.191971/#post-1341666
Communicating from Unity -> ObjC
http://blogs.unity3d.com/2015/07/02/il2cpp-internals-pinvoke-wrappers/
Communicating from Unity <-> ObjC
http://alexanderwong.me/post/29861010648/call-objective-c-from-unity-call-unity-from
First you need to have a project in unity, and you need to build it for iOS.
Under Unity 5 the project's scripting backend is already set to il2cpp
so you
pretty much just have to :
File -> Build Settings
- Select your scene(s)
- Press the build button
- Remember the folder you built the project too.
There is a bit more to do here, but ideally the Unity.xcconfig
and
the UnityProjectRefresh.sh
script make this easier.
Setting expectations, the project import process here takes some time, it's not instant, Unity generates a lot of files and Xcode has to import them all. So expect to stare a beachball for a few minuts while it does it's thing.
Ok! Fire up Xcode and create a new Swift
project or open an existing
Swift
project.
Here is what we will be doing, this will seem like a lot, but it's pretty straight forward. You will fly through these steps minus the unity project import/cleanup which is not diffiucilt, it's just time consuming given the number of files.
- Add the Unity.xcconfig file provided in this repo
- Adjust 1 project dependent setting
- Add a new
run script
build phase - Import your unity project
- Clean up your unity project
- Add the
objc
folder in this repo with the new custom unity init and obj-c bridging header - Rename
main
inmain.mm
to anything else - Wrap the UnityAppController into your application delegate
- Adjust the
GetAppController
function inUnityAppController.h
- Go bananas, you did it! Add the unity view wherever you want!
Drag and drop the Unity.xcconfig
file into your Xcode project.
Set the project to use those settings.
So that does a lot for you in terms of configuration, now we need to adjust 1 setting in it. Since we don't know where you decided to export your unity project too, you need to configure that.
Open up your project's build settings and scroll all the way to bottom, you will see:
UNITY_IOS_EXPORT_PATH
Adjust that path to point to your ios unity export path
You can also adjust your
UNITY_RUNTIME_VERSION
If you are not using 5.5.2f1
.
Now we need to ensure we copy our fresh unity project on each build, so we add a new run script build phase.
Select Build Phases from your project settings to add a new build phase.
Copy the contents of the UnityProjectRefresh.sh script into this phase.
This is outlined in this www.the-nerd.be video at around 5:35 - 7:30 as well, but it's now time to import our Unity project.
Create a new group and call it Unity
, the name doesn't matter it's just helpful to name things so you know what they are).
You will need to open the folder you built your Unity iOS project into. It will be the same folder you
specified for the UNITY_IOS_EXPORT_PATH
above.
Do 1 folder at a time, this will take a minute or more to do, there are lots of files.
We are going to drag in the following folders (You don't need to copy them):
/your/unity/ios/export/path/Classes
/your/unity/ios/export/path/Libraries
This is all in the www.the-nerd.be video as well 7:35 - There is two location we will clean up for convenience. For both of these we ONLY WANT TO REMOVE REFERENCES DO NOT MOVE TO TRASH
We don't need the Unity/Classes/Native/*.h
and we don't need Unity/Libraries/libl2cpp/
.
The Unity.xcconfig we applied knows where they are for compiling purposes.
- Remove
Unity/Libraries/libl2cpp/
7:35 - 7:50 in www.the-nerd.be video. - Remove
Unity/Classes/Native/*.h
7:55- 8:44 in www.the-nerd.be video.
You can copy these if you want, they are tiny.
UnityBridge.h
is theSWIFT_OBJC_BRIDGING_HEADER
specified inUnity.xcconfig
UnityUtils.h/mm
is our new custom init function.
The new custom unity init function is pulled directly our of the main.mm file in your unity project. Swift does not have the same initialization convention as an objecitve-c app, so we are going to tweak things slightly.
In your xcode project under Unity/Classses
locate the main.mm
file. Within that file locate
int main(int argc, char* argv[])
Once you find that you can go ahead and see that UnityUtils.mm
, which we imported
above, is effectively this function. Should Unity change this initialization you will need
to update your UnityUtils.mm
file to match their initialization. Note that we don't
copy the UIApplicationMain
part. Swift will handle that.
Anyway, we need to rename this function to anything but main
:
int main_unity_default(int argc, char* argv[])
We are taking away control from the unity generated application delegate, we
need to act as a proxy for it in our AppDelegate
.
First add the following variable to your AppDelegate
var currentUnityController: UnityAppController!
Now we need to initialize and proxy through the calls to the UnityAppController
.
All said and done you will be left with the following:
//
// AppDelegate.swift
//
// Created by Adam Venturella on 10/28/15
//
// Updated by Martin Straub on 15/03/2017.
// Added some stuff to pause unity in order to stop consuming cpu cylces and battery life, when not being displayed.
// Indeed, unity will still sit in memory all the time, but that seems to be a more complex thing to solve.
// Just use `startUnity` and `stopUnity` for running/pausing unity (see also ViewController example below).
//
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var currentUnityController: UnityAppController?
var application: UIApplication?
var isUnityRunning = false
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
self.application = application
unity_init(CommandLine.argc, CommandLine.unsafeArgv)
currentUnityController = UnityAppController()
currentUnityController!.application(application, didFinishLaunchingWithOptions: launchOptions)
// first call to startUnity will do some init stuff, so just call it here and directly stop it again
startUnity()
stopUnity()
return true
}
func applicationWillResignActive(_ application: UIApplication) {
if isUnityRunning {
currentUnityController?.applicationWillResignActive(application)
}
}
func applicationDidEnterBackground(_ application: UIApplication) {
if isUnityRunning {
currentUnityController?.applicationDidEnterBackground(application)
}
}
func applicationWillEnterForeground(_ application: UIApplication) {
if isUnityRunning {
currentUnityController?.applicationWillEnterForeground(application)
}
}
func applicationDidBecomeActive(_ application: UIApplication) {
if isUnityRunning {
currentUnityController?.applicationDidBecomeActive(application)
}
}
func applicationWillTerminate(_ application: UIApplication) {
if isUnityRunning {
currentUnityController?.applicationWillTerminate(application)
}
}
func startUnity() {
if !isUnityRunning {
isUnityRunning = true
currentUnityController!.applicationDidBecomeActive(application!)
}
}
func stopUnity() {
if isUnityRunning {
currentUnityController!.applicationWillResignActive(application!)
isUnityRunning = false
}
}
}
Locate the file UnityAppController.h
in the xcode group Unity/Classes/
Find the following function:
inline UnityAppController*GetAppController()
{
return (UnityAppController*)[UIApplication sharedApplication].delegate;
}
Comment that out. You will end up with this:
//inline UnityAppController*GetAppController()
//{
// return (UnityAppController*)[UIApplication sharedApplication].delegate;
//}
Now we need to add a new version of this function:
NS_INLINE UnityAppController* GetAppController()
{
NSObject<UIApplicationDelegate>* delegate = [UIApplication sharedApplication].delegate;
UnityAppController* currentUnityController = (UnityAppController *)[delegate valueForKey:@"currentUnityController"];
return currentUnityController;
}
I happen to do this in a stock, single view application, so xcode generated a ViewController.swift
file for me attached to a storyboard. Here is how I hooked up my little demo:
//
// ViewController.swift
//
// Created by Adam Venturella on 10/28/15.
// Updated by Martin Straub on 15/03/2017.
//
import UIKit
class ViewController: UIViewController {
var unityView: UIView?
@IBAction func startUnity(sender: AnyObject) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.startUnity()
unityView = UnityGetGLView()!
self.view!.addSubview(unityView!)
unityView!.translatesAutoresizingMaskIntoConstraints = false
// look, non-full screen unity content!
let views = ["view": unityView]
let w = NSLayoutConstraint.constraints(withVisualFormat: "|-20-[view]-20-|", options: [], metrics: nil, views: views)
let h = NSLayoutConstraint.constraints(withVisualFormat: "V:|-75-[view]-50-|", options: [], metrics: nil, views: views)
view.addConstraints(w + h)
}
@IBAction func stopUnity(sender: AnyObject) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.stopUnity()
unityView!.removeFromSuperview()
}
}