├── LICENSE ├── README.md ├── Unity.xcconfig ├── UnityProjectRefresh.sh └── objc ├── UnityBridge.h ├── UnityUtils.h └── UnityUtils.mm /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 BLITZ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # How to use Unity 3D within an iOS app 2 | 3 | This is going to appear to be complicated based on the length of this article 4 | it's really not. I try to fully show some examples here, and provide some images 5 | for those who may not know where certain things are in xcode. 6 | 7 | 8 | This would not be possible without [www.the-nerd.be], Frederik Jacques. 9 | All of the settings in the xcconfig file, the `UnityProjectRefresh.sh` 10 | script and the project import are directly derieved from his work. The video 11 | he made in the provided link is worth watching. 12 | 13 | This covers Unity 5+. At the time of this writing this has been 14 | successfully used with Unity `5.5.2f1` and `Swift 3.1` under `Xcode 8.3.2`. 15 | 16 | This works with storyboards. 17 | 18 | You only get **ONE** unity view. You **CANNOT** run multiple Unity 19 | Views in your application at once. You will also need a way to 20 | communicate to <-> from your unity content to your iOS app. 21 | I would recommend an event bus in both your Unity code and 22 | your iOS code. AKA one central place on both sides to emit events 23 | to and listen to events on each side. 24 | 25 | In other words you will need 2 busses, 1 on the Unity side that you can 26 | call into to emit events from on the iOS side, and one on the iOS side that 27 | Unity can call into to emit events on. 28 | 29 | You can read more about communication between the 2 worlds from 30 | the following links: 31 | 32 | **More about embedding** 33 | 34 | http://forum.unity3d.com/threads/unity-appcontroller-subclassing.191971/ 35 | 36 | Specifically there is a bit on commuicating here with some sample code. 37 | Note, this is not for UNITY 5, but it shows the samples in OverlayUI related 38 | making functions available to the Objective-C side of things to be called from 39 | your Unity Code. 40 | 41 | http://forum.unity3d.com/threads/unity-appcontroller-subclassing.191971/#post-1341666 42 | 43 | 44 | **Communicating from Unity -> ObjC** 45 | 46 | http://blogs.unity3d.com/2015/07/02/il2cpp-internals-pinvoke-wrappers/ 47 | 48 | http://forum.unity3d.com/threads/unity-5-2-2f1-embed-in-ios-with-extern-dllimport-__internal-methods-fails-to-compile.364809/ 49 | 50 | 51 | **Communicating from Unity <-> ObjC** 52 | 53 | http://alexanderwong.me/post/29861010648/call-objective-c-from-unity-call-unity-from 54 | 55 | 56 | ## Lets get started. 57 | 58 | ### From Unity 59 | 60 | First you need to have a project in unity, and you need to build it for iOS. 61 | 62 | Under Unity 5 the project's scripting backend is already set to `il2cpp` so you 63 | pretty much just have to : 64 | 65 | - `File -> Build Settings` 66 | - Select your scene(s) 67 | - Press the build button 68 | - Remember the folder you built the project too. 69 | 70 | 71 | ### From Xcode 72 | 73 | There is a bit more to do here, but ideally the `Unity.xcconfig` and 74 | the `UnityProjectRefresh.sh` script make this easier. 75 | 76 | Setting expectations, the project import process here takes some time, 77 | it's not instant, Unity generates a lot of files and Xcode has to import them 78 | all. So expect to stare a beachball for a few minuts while it does it's thing. 79 | 80 | Ok! Fire up Xcode and create a new `Swift` project or open an existing 81 | `Swift` project. 82 | 83 | Here is what we will be doing, this will seem like a lot, but it's pretty straight 84 | forward. You will fly through these steps minus the unity project import/cleanup 85 | which is not diffiucilt, it's just time consuming given the number of files. 86 | 87 | - Add the Unity.xcconfig file provided in this repo 88 | - Adjust 1 project dependent setting 89 | - Add a new `run script` build phase 90 | - Import your unity project 91 | - Clean up your unity project 92 | - Add the `objc` folder in this repo with the new custom unity init and obj-c bridging header 93 | - Rename `main` in `main.mm` to anything else 94 | - Wrap the UnityAppController into your application delegate 95 | - Adjust the `GetAppController` function in `UnityAppController.h` 96 | - Go bananas, you did it! Add the unity view wherever you want! 97 | 98 | #### Add the Unity.xcconfig file provided in this repo 99 | 100 | Drag and drop the `Unity.xcconfig` file into your Xcode project. 101 | Set the project to use those settings. 102 | 103 | 104 | 105 | #### Adjust 1 project dependent setting 106 | So that does a lot for you in terms of configuration, now we need to adjust 1 setting in it. 107 | Since we don't know where you decided to export your unity project too, you need to configure that. 108 | 109 | 110 | Open up your project's build settings and scroll all the way to bottom, you will see: 111 | 112 | ``` 113 | UNITY_IOS_EXPORT_PATH 114 | ``` 115 | 116 | Adjust that path to point to your ios unity export path 117 | 118 | 119 | 120 | 121 | You can also adjust your 122 | 123 | ``` 124 | UNITY_RUNTIME_VERSION 125 | ``` 126 | 127 | If you are not using `5.5.2f1`. 128 | 129 | 130 | #### Add a new `run script` build phase 131 | 132 | Now we need to ensure we copy our fresh unity project on each build, so we add a 133 | new run script build phase. 134 | 135 | Select Build Phases from your project settings to add a new build phase. 136 | 137 | Copy the contents of the UnityProjectRefresh.sh script into this phase. 138 | 139 | 140 | 141 | 142 | #### Import your unity project 143 | 144 | 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. 145 | 146 | 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). 147 | 148 | 149 | You will need to open the folder you built your Unity iOS project into. It will be the same folder you 150 | specified for the `UNITY_IOS_EXPORT_PATH` above. 151 | 152 | Do 1 folder at a time, this will take a minute or more to do, there are lots of files. 153 | 154 | We are going to drag in the following folders (You don't need to copy them): 155 | 156 | - `/your/unity/ios/export/path/Classes` 157 | - `/your/unity/ios/export/path/Libraries` 158 | 159 | 160 | #### Clean up your unity project 161 | 162 | This is all in the [www.the-nerd.be] video as well 7:35 - 163 | There is two location we will clean up for convenience. For both of these we 164 | *ONLY WANT TO REMOVE REFERENCES DO NOT MOVE TO TRASH* 165 | 166 | We don't need the `Unity/Classes/Native/*.h` and we don't need `Unity/Libraries/libl2cpp/`. 167 | 168 | The Unity.xcconfig we applied knows where they are for compiling purposes. 169 | 170 | - Remove `Unity/Libraries/libl2cpp/` 7:35 - 7:50 in [www.the-nerd.be] video. 171 | - Remove `Unity/Classes/Native/*.h` 7:55- 8:44 in [www.the-nerd.be] video. 172 | 173 | 174 | #### Add the `objc` folder in this repo 175 | 176 | You can copy these if you want, they are tiny. 177 | 178 | - `UnityBridge.h` is the `SWIFT_OBJC_BRIDGING_HEADER` specified in `Unity.xcconfig` 179 | - `UnityUtils.h/mm` is our new custom init function. 180 | 181 | The new custom unity init function is pulled directly our of the main.mm file in your unity project. 182 | Swift does not have the same initialization convention as an objecitve-c app, so we are going to 183 | tweak things slightly. 184 | 185 | #### Rename `main` in `main.mm` to anything else 186 | 187 | In your xcode project under `Unity/Classses` locate the `main.mm` file. Within that file locate 188 | 189 | ```cpp 190 | int main(int argc, char* argv[]) 191 | ``` 192 | Once you find that you can go ahead and see that `UnityUtils.mm`, which we imported 193 | above, is effectively this function. Should Unity change this initialization you will need 194 | to update your `UnityUtils.mm` file to match their initialization. Note that we don't 195 | copy the `UIApplicationMain` part. Swift will handle that. 196 | 197 | Anyway, we need to rename this function to anything but `main`: 198 | 199 | 200 | ```cpp 201 | int main_unity_default(int argc, char* argv[]) 202 | ``` 203 | 204 | #### Wrap the UnityAppController into your application delegate 205 | 206 | We are taking away control from the unity generated application delegate, we 207 | need to act as a proxy for it in our `AppDelegate`. 208 | 209 | First add the following variable to your `AppDelegate` 210 | 211 | ```swift 212 | var currentUnityController: UnityAppController! 213 | ``` 214 | Now we need to initialize and proxy through the calls to the `UnityAppController`. 215 | 216 | All said and done you will be left with the following: 217 | 218 | ```swift 219 | // 220 | // AppDelegate.swift 221 | // 222 | // Created by Adam Venturella on 10/28/15 223 | // 224 | // Updated by Martin Straub on 15/03/2017. 225 | // Added some stuff to pause unity in order to stop consuming cpu cylces and battery life, when not being displayed. 226 | // Indeed, unity will still sit in memory all the time, but that seems to be a more complex thing to solve. 227 | // Just use `startUnity` and `stopUnity` for running/pausing unity (see also ViewController example below). 228 | // 229 | 230 | import UIKit 231 | 232 | @UIApplicationMain 233 | class AppDelegate: UIResponder, UIApplicationDelegate { 234 | var currentUnityController: UnityAppController? 235 | var application: UIApplication? 236 | var isUnityRunning = false 237 | 238 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool { 239 | self.application = application 240 | unity_init(CommandLine.argc, CommandLine.unsafeArgv) 241 | currentUnityController = UnityAppController() 242 | currentUnityController!.application(application, didFinishLaunchingWithOptions: launchOptions) 243 | 244 | // first call to startUnity will do some init stuff, so just call it here and directly stop it again 245 | startUnity() 246 | stopUnity() 247 | 248 | return true 249 | } 250 | 251 | func applicationWillResignActive(_ application: UIApplication) { 252 | if isUnityRunning { 253 | currentUnityController?.applicationWillResignActive(application) 254 | } 255 | } 256 | 257 | func applicationDidEnterBackground(_ application: UIApplication) { 258 | if isUnityRunning { 259 | currentUnityController?.applicationDidEnterBackground(application) 260 | } 261 | } 262 | 263 | func applicationWillEnterForeground(_ application: UIApplication) { 264 | if isUnityRunning { 265 | currentUnityController?.applicationWillEnterForeground(application) 266 | } 267 | } 268 | 269 | func applicationDidBecomeActive(_ application: UIApplication) { 270 | if isUnityRunning { 271 | currentUnityController?.applicationDidBecomeActive(application) 272 | } 273 | } 274 | 275 | func applicationWillTerminate(_ application: UIApplication) { 276 | if isUnityRunning { 277 | currentUnityController?.applicationWillTerminate(application) 278 | } 279 | } 280 | 281 | func startUnity() { 282 | if !isUnityRunning { 283 | isUnityRunning = true 284 | currentUnityController!.applicationDidBecomeActive(application!) 285 | } 286 | } 287 | 288 | func stopUnity() { 289 | if isUnityRunning { 290 | currentUnityController!.applicationWillResignActive(application!) 291 | isUnityRunning = false 292 | } 293 | } 294 | } 295 | 296 | ``` 297 | 298 | #### Adjust the `GetAppController` function in `UnityAppController.h` 299 | 300 | Locate the file `UnityAppController.h` in the xcode group `Unity/Classes/` 301 | 302 | Find the following function: 303 | 304 | ```objc 305 | inline UnityAppController*GetAppController() 306 | { 307 | return (UnityAppController*)[UIApplication sharedApplication].delegate; 308 | } 309 | ``` 310 | 311 | Comment that out. You will end up with this: 312 | 313 | ```objc 314 | //inline UnityAppController*GetAppController() 315 | //{ 316 | // return (UnityAppController*)[UIApplication sharedApplication].delegate; 317 | //} 318 | ``` 319 | 320 | Now we need to add a new version of this function: 321 | 322 | ```objc 323 | NS_INLINE UnityAppController* GetAppController() 324 | { 325 | NSObject* delegate = [UIApplication sharedApplication].delegate; 326 | UnityAppController* currentUnityController = (UnityAppController *)[delegate valueForKey:@"currentUnityController"]; 327 | return currentUnityController; 328 | } 329 | ``` 330 | 331 | 332 | #### Go bananas, you did it! Add the unity view wherever you want! 333 | 334 | I happen to do this in a stock, single view application, so xcode generated a `ViewController.swift` 335 | file for me attached to a storyboard. Here is how I hooked up my little demo: 336 | 337 | ```swift 338 | // 339 | // ViewController.swift 340 | // 341 | // Created by Adam Venturella on 10/28/15. 342 | // Updated by Martin Straub on 15/03/2017. 343 | // 344 | 345 | import UIKit 346 | 347 | class ViewController: UIViewController { 348 | var unityView: UIView? 349 | 350 | @IBAction func startUnity(sender: AnyObject) { 351 | let appDelegate = UIApplication.shared.delegate as! AppDelegate 352 | appDelegate.startUnity() 353 | 354 | unityView = UnityGetGLView()! 355 | 356 | self.view!.addSubview(unityView!) 357 | unityView!.translatesAutoresizingMaskIntoConstraints = false 358 | 359 | // look, non-full screen unity content! 360 | let views = ["view": unityView] 361 | let w = NSLayoutConstraint.constraints(withVisualFormat: "|-20-[view]-20-|", options: [], metrics: nil, views: views) 362 | let h = NSLayoutConstraint.constraints(withVisualFormat: "V:|-75-[view]-50-|", options: [], metrics: nil, views: views) 363 | view.addConstraints(w + h) 364 | } 365 | 366 | @IBAction func stopUnity(sender: AnyObject) { 367 | let appDelegate = UIApplication.shared.delegate as! AppDelegate 368 | appDelegate.stopUnity() 369 | unityView!.removeFromSuperview() 370 | } 371 | } 372 | 373 | ``` 374 | 375 | [www.the-nerd.be]: http://www.the-nerd.be/2015/08/20/a-better-way-to-integrate-unity3d-within-a-native-ios-application/ "The Nerd" 376 | -------------------------------------------------------------------------------- /Unity.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Unity.xcconfig 3 | // 4 | // Created by Adam Venturella on 10/28/15. 5 | // Settings from http://www.the-nerd.be/2015/08/20/a-better-way-to-integrate-unity3d-within-a-native-ios-application/ 6 | 7 | UNITY_RUNTIME_VERSION = 5.5.2f1; 8 | UNITY_SCRIPTING_BACKEND = il2cpp; 9 | UNITY_IOS_EXPORT_PATH = /Path/To/Your/Unity/iOS/Build; 10 | GCC_PREFIX_HEADER = $(UNITY_IOS_EXPORT_PATH)/Classes/Prefix.pch; 11 | 12 | OTHER_LDFLAGS = -weak-lSystem -weak_framework CoreMotion -weak_framework GameKit -weak_framework iAd -framework CoreGraphics -framework AVFoundation -framework CoreVideo -framework CoreMedia -framework SystemConfiguration -framework CoreLocation -framework MediaPlayer -framework CFNetwork -framework AudioToolbox -framework OpenAL -framework QuartzCore -framework OpenGLES -framework UIKit -framework Foundation -liconv.2 -liPhone-lib; 13 | 14 | HEADER_SEARCH_PATHS = "$(UNITY_IOS_EXPORT_PATH)/Classes" "$(UNITY_IOS_EXPORT_PATH)/Classes/Native" "$(UNITY_IOS_EXPORT_PATH)/Libraries/libil2cpp/include"; 15 | LIBRARY_SEARCH_PATHS = "$(UNITY_IOS_EXPORT_PATH)/Libraries" "$(UNITY_IOS_EXPORT_PATH)/Libraries/libil2cpp/include"; 16 | 17 | ENABLE_BITCODE = NO; 18 | 19 | SWIFT_OBJC_BRIDGING_HEADER = UnityBridge.h; 20 | 21 | OTHER_CFLAGS = -DINIT_SCRIPTING_BACKEND=1; 22 | CLANG_CXX_LANGUAGE_STANDARD = c++11; 23 | CLANG_CXX_LIBRARY = libc++; 24 | CLANG_ENABLE_MODULES = NO; 25 | CLANG_WARN_BOOL_CONVERSION = NO; 26 | CLANG_WARN_CONSTANT_CONVERSION = NO; 27 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES; 28 | CLANG_WARN_EMPTY_BODY = NO; 29 | CLANG_WARN_ENUM_CONVERSION = NO; 30 | CLANG_WARN_INT_CONVERSION = NO; 31 | CLANG_WARN_OBJC_ROOT_CLASS = YES; 32 | CLANG_WARN_UNREACHABLE_CODE = NO; 33 | CLANG_WARN__DUPLICATE_METHOD_MATCH = NO; 34 | GCC_C_LANGUAGE_STANDARD = c99; 35 | GCC_ENABLE_OBJC_EXCEPTIONS = NO; 36 | GCC_ENABLE_CPP_RTTI = NO; 37 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 38 | GCC_THUMB_SUPPORT = NO; 39 | GCC_USE_INDIRECT_FUNCTION_CALLS = NO; 40 | GCC_WARN_64_TO_32_BIT_CONVERSION = NO; 41 | GCC_WARN_64_TO_32_BIT_CONVERSION[arch=*64] = YES; 42 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 43 | GCC_WARN_UNDECLARED_SELECTOR = NO; 44 | GCC_WARN_UNINITIALIZED_AUTOS = NO; 45 | GCC_WARN_UNUSED_FUNCTION = NO; 46 | -------------------------------------------------------------------------------- /UnityProjectRefresh.sh: -------------------------------------------------------------------------------- 1 | rm -rf "$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Data"; 2 | cp -Rf "$UNITY_IOS_EXPORT_PATH/Data" "$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Data"; 3 | -------------------------------------------------------------------------------- /objc/UnityBridge.h: -------------------------------------------------------------------------------- 1 | // 2 | // UnityBridge.h 3 | // 4 | // Created by Adam Venturella on 10/28/15. 5 | // 6 | 7 | #ifndef UnityBridge_h 8 | #define UnityBridge_h 9 | 10 | #import 11 | #import "UnityUtils.h" 12 | #import "UnityAppController.h" 13 | #import "UnityInterface.h" 14 | #endif /* UnityBridge_h */ 15 | -------------------------------------------------------------------------------- /objc/UnityUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // UnityUtils.h 3 | // 4 | // Created by Adam Venturella on 10/28/15. 5 | // 6 | 7 | #ifndef UnityUtils_h 8 | #define UnityUtils_h 9 | 10 | 11 | void unity_init(int argc, char* argv[]); 12 | 13 | #endif /* UnityUtils_h */ 14 | -------------------------------------------------------------------------------- /objc/UnityUtils.mm: -------------------------------------------------------------------------------- 1 | // 2 | // UnityUtils.m 3 | // 4 | // Created by Adam Venturella on 10/28/15. 5 | // 6 | // this is taken directly from the unity generated main.mm file. 7 | // if they change that initialization, this will need to be updated 8 | // as well. 9 | // 10 | // Updated by Martin Straub on 03/15/17. 11 | // 12 | // updated to Unity 5.5.0f3 => working on Xcode 8.2.1 with Swift 3.0.2 13 | 14 | 15 | #include "RegisterMonoModules.h" 16 | #include "RegisterFeatures.h" 17 | #include 18 | 19 | 20 | // Hack to work around iOS SDK 4.3 linker problem 21 | // we need at least one __TEXT, __const section entry in main application .o files 22 | // to get this section emitted at right time and so avoid LC_ENCRYPTION_INFO size miscalculation 23 | static const int constsection = 0; 24 | 25 | void UnityInitTrampoline(); 26 | 27 | 28 | extern "C" void unity_init(int argc, char* argv[]) 29 | { 30 | @autoreleasepool 31 | { 32 | UnityInitTrampoline(); 33 | UnityInitRuntime(argc, argv); 34 | 35 | RegisterMonoModules(); 36 | NSLog(@"-> registered mono modules %p\n", &constsection); 37 | RegisterFeatures(); 38 | 39 | // iOS terminates open sockets when an application enters background mode. 40 | // The next write to any of such socket causes SIGPIPE signal being raised, 41 | // even if the request has been done from scripting side. This disables the 42 | // signal and allows Mono to throw a proper C# exception. 43 | std::signal(SIGPIPE, SIG_IGN); 44 | } 45 | } 46 | --------------------------------------------------------------------------------