Bridging the Gap – Plugin for Unity and iOS
This tutorial shows how to code a way for Unity and XCode to communicate with each other when developing mobile apps for iOS.
Join the DZone community and get the full member experience.
Join For FreeThere is always a need of a way to communicate between Unity and XCode due to the absence of a direct way. When I faced this problem for the first time, I had to spend almost 3-4 days of my app’s development cycle finding the right way to make this bridge. Through this blog post, I am going to share the method so as to help out any other developer like me. Creating a bridge over Unity and iOS requires coding at both ends, so let’s discuss the XCode side first.
According to Unity's documentation, “Unity iOS supports automated plugin integration in a limited way. All files with extensions .a,.m,.mm,.c,.cpp located in the Assets/Plugins/iOS folder will be merged into the generated Xcode project automatically. However, merging is done by symlinking files from Assets/Plugins/iOS to the final destination, which might affect some workflows. The.h files are not included in the Xcode project tree, but they appear on the destination file system, thus allowing compilation of .m/.mm/.c/.cpp files.”
So, we will create UnityIOSBridge.m and will place this class to the path “Assets/Plugins/iOS.” Unity needs the plugins to be c-named, so it is a good practice to wrap up the methods which need to be called from Unity, inside extern “C.” But there is no hard and fast rule, you can create .m class and write your c-named methods just outside the implementation part and you can call them from Unity. The only constraint is that you can not call these methods if you are building your app on a simulator, as Unity iOS plugins only work on devices.
Let’s do some coding. In the UnityIOSBridge.m class, just write a method that can receive a string and convert it to NSString. Now UnityIOSBridge.m class should look like as follows:
#
import "UnityIOSBridge.h"
void messageFromUnity(char * message) {
NSString * messageFromUnity = [NSString stringWithUTF8String: message];
NSLog(@ "%@", messageFromUnity);
}
@implementation UnityIOSBridge
@end
To call the above method from Unity, we have to write a Unity script, so let’s create the file UnityIOSBridge.cs.
using UnityEngine;
using System.Collections;
using System;
//This is needed to import iOS functions
using System.Runtime.InteropServices;
public class UnityIOSBridge: MonoBehaviour {
/*
* Provide function decalaration of the functions defined in iOS
* and need to be called here.
*/
[System.Runtime.InteropServices.DllImport("__Internal")]
extern static public void messageFromUnity(string message);
//Sends message to iOS
static void SendMessageToIOS() {
messageFromUnity("Hello iOS!");
}
}
It’s really as simple as it looks in the above code. Now, to call a method written in Unity script from iOS code, we can call UnitySendMessage (“UnityObjectName”, “UnityObject’sMethodName”, “Your message”). In response, Unity will look for the UnityObject and then call that UnityObject’sMethod to provide the message you passed. Now the UnityIOSBridge.m class should look like this:
#
import "UnityIOSBridge.h"
void messageFromUnity(char * message) {
NSString * messageFromUnity = [NSString stringWithUTF8String: message];
NSLog(@ "%@", messageFromUnity);
}
@implementation UnityIOSBridge
- (void) sendMessageToUnity {
UnitySendMessage(listenerObject, "messageFromIOS", "Hello Unity!");
}
@end
And the Unity script UnityIOSBridge.cs should look like this:
using UnityEngine;
using System.Collections;
using System;
//This is needed to import iOS functions
using System.Runtime.InteropServices;
public class UnityIOSBridge: MonoBehaviour {
/*
* Provide function decalaration of the functions defined in iOS
* and need to be called here.
*/
[System.Runtime.InteropServices.DllImport("__Internal")]
extern static public void messageFromUnity(string message);
//Sends message to iOS
static void SendMessageToIOS() {
messageFromUnity("Hello iOS!");
}
//Provides messages sent from iOS
static void messageFromIOS(string message) {
Debug.Log(message);
}
}
This was a very simple requirement, but what if we want to do something more? For example, our plugin should be able to notify Unity about the UIApplication delegate calls. There is no need to be worried, as we are going to implement that also, but to do that, we have to do some workaround. Objective-C is a runtime-oriented language, which means that when possible, it defers decisions about what will actually be executed from compile and link time to when it’s actually executing on the runtime.
This gives you a lot of flexibility in that you can redirect messages to appropriate objects as you need to, or you can even intentionally swap method implementations, etc. This requires the use of a runtime which can introspect objects to see what they do and don’t respond to and dispatch methods appropriately. So, we will take a simple example of “application: didFinishLaunchingWithOptions:” delegate method UIApplicationDelegate. We will be creating a category class of UIApplication and will implement a load method. In the load method, we will exchange the setDelegate method implementation of UIApplication with the method setApp42Delegate method of our class, as follows:
+(void) load {
method_exchangeImplementations(class_getInstanceMethod(self, @selector(setDelegate: )), class_getInstanceMethod(self, @selector(setApp42Delegate: )));
}
- (void) setApp42Delegate: (id) delegate {
static Class delegateClass = nil;
if (delegateClass == [delegate class]) {
return;
}
delegateClass = [delegate class];
exchangeMethodImplementations(delegateClass, @selector(application: didFinishLaunchingWithOptions: ), @selector(application: app42didFinishLaunchingWithOptions: ), (IMP)
app42RunTimeDidFinishLaunching, "v@:::");
[self setApp42Delegate: delegate];
}
static void exchangeMethodImplementations(Class class, SEL oldMethod, SEL newMethod, IMP impl,
const char * signature) {
Method method = nil;
//Check whether method exists in the class
method = class_getInstanceMethod(class, oldMethod);
if (method) {
//if method exists add a new method
class_addMethod(class, newMethod, impl, signature);
//and then exchange with original method implementation
method_exchangeImplementations(class_getInstanceMethod(class, oldMethod), class_getInstanceMethod(class, newMethod));
} else {
//if method does not exist, simply add as orignal method
class_addMethod(class, oldMethod, impl, signature);
}
}
BOOL app42RunTimeDidFinishLaunching(id self, SEL _cmd, id application, id launchOptions) {
BOOL result = YES;
if ([self respondsToSelector: @selector(application: app42didFinishLaunchingWithOptions: )]) {
result = (BOOL)[self application: application app42didFinishLaunchingWithOptions: launchOptions];
} else {
[self applicationDidFinishLaunching: application];
result = YES;
}
[
[UIApplication sharedApplication] registerForRemoteNotificationTypes: (UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)
];
return result;
}
Let’s walk through the above code snippets: the method “setApp42Delegate” calls our “exchangeMethodImplementations” that adds the “app42RunTimeDidFinishLaunching” to the UIApplication class and exchanges the implementations of “app42RunTimeDidFinishLaunching” with “application: didFinishLaunchingWithOptions:” if it exists. This way, we can have access to all the UIApplicationDelegate methods, such as “applicationDidEnterBackground:”, “application:didRegisterForRemoteNotificationsWithDeviceToken:”, etc, without making changes directly to the Unity-generated Xcode project. You can download the source code of our Unity plugin for iOS push notifications from this Git Repo.
Published at DZone with permission of Rajeev Ranjan. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments