There is always a need of finding out a way of communication from unity to XCode or XCode to unity due to the absence of a direct way. For the first time when I faced this problem, had to spend almost 3-4 days of my app’s development cycle on 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 XCode side first.
Siempre hay una necesidad de encontrar una manera de comunicación desde Unity a xcode o xcode a Unity debido a la ausencia de una manera directa. La primera vez que me encontré con este problema, tuve que pasar casi de 3 a 4 días del ciclo de desarrollo de mi aplicación, en tratar de encontrar la manera indicada de hacer este puente de conexión. A través de este Blog, voy a compartir el método con el fin de ayudar otros desarrolladores como yo. Creando un puente sobre Unity y iOS requiere codificación en ambos extremos. Así que hablemos de Xcode primero.
According to Unity 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 simulator as unity iOS plugins only work on device.
Let’s do some coding e.g. in 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:
[code java]#import “UnityIOSBridge.h”
void messageFromUnity(char *message)
{
NSString *messageFromUnity = [NSString stringWithUTF8String:message];
NSLog(@”%@”,messageFromUnity);
}
@implementation UnityIOSBridge
@end[/code]
To call the above written method from Unity, we have to write a Unity script, so let’s create a file UnityIOSBridge.cs.
[code java]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!”);
}
}[/code]
And 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:
[code java]#import “UnityIOSBridge.h”
void messageFromUnity(char *message)
{
NSString *messageFromUnity = [NSString stringWithUTF8String:message];
NSLog(@”%@”,messageFromUnity);
}
@implementation UnityIOSBridge
-(void)sendMessageToUnity
{
UnitySendMessage(listenerObject, “messageFromIOS”, “Hello Unity!”);
}
@end[/code]
and the Unity script UnityIOSBridge.cs should look like:
[code java]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);
}
}[/code]
Well this was a very simple requirement but what if we want to do something more e.g. 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 work around.Objective-C is a runtime oriented language, which means that when it’s possible it defers decisions about what will actually be executed from compile & link time to when it’s actually executing on the runtime.
Quick Tip: Download the readymade Push Notification Widget for Unity3D here.
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 & don’t respond to and dispatch methods appropriately. So, we will take a simple example of “application: didFinishLaunchingWithOptions:” delegate method UIApplicationDelagate. We will be creating a category class of UIApplication and will implement load method. In load method, we will exchange the setDelagete method implementation of UIApplication with the method setApp42Delegate method of our class as follows:
[code java]+(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;
}[/code]
Let’s walk through the above code snippets, the method “setApp42Delegate” calls our “exchangeMethodImplementations” that adds the “app42RunTimeDidFinishLaunching” to UIApllication class and exchanges the implementations of “app42RunTimeDidFinishLaunching” with “application: didFinishLaunchingWithOptions:” if it exists.This way we can have the access of all the UIApplicationDelagate 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 Git Repo.
Get started with Unity3D backend with App42 here. If you have any questions or need any further assistance, please feel free to write us at support@shephertz.com
De acuerdo con documentación de Unity, “Unity iOS suporta integración de plugin automatizada en una manera limitada. Todos archivos con extensiones .a,.m,.mm,.c,.cpp situada en la carpeta Assets/Plugins/iOS se fusionará en el proyecto Xcode generado automáticamente. Sin embargo, la fusión se realiza por archivos de enlaces simbólicos desde Assets/Plugins/iOS al destino final, el cual podría afectar algunos flujos de trabajo. Los archivos .h no están incluidos en el árbol de proyecto Xcode, pero ellos aparecen en el sistema de archivo de destino, permitiendo así la compilación de archivos de .m/.mm/.c/.cpp”.
Así, crearemos UnityIOSBridge.m y pondrá este class para la ruta “Assets/Plugins/iOS”. Unity necesita los plugins para ser llamados c, por lo que es una buena práctica para concluir los métodos que necesitan ser llamados desde Unity, dentro “C” extern. Pero no hay una regla dura o rápida, puede crear .m class y escribir sus métodos c-named afuera de la parte de implementación y puede llamarlo desde Unity. La única restricción es que no puede llamar estos métodos si está construyendo su aplicación en simulador como Unity iOS plugins solo funciona en dispositivos.
Hagamos un poco de codificación, por ejemplo: en UnityIOSBridge.m class escriba un método que puede recibir una cadena y convertirla a NSString, ahora UnitylOSBridge.m class debería verse como lo siguiente:
#import "UnityIOSBridge.h"
void messageFromUnity(char *message)
{
NSString *messageFromUnity = [NSString stringWithUTF8String:message];
NSLog(@"%@",messageFromUnity);
}
@implementation UnityIOSBridge
@end
Para llamar el método escrito anterior desde Unity, tenemos que escribir un Unity script, así que creamos un archivo 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!");
}
}
Y es realmente tan simple como parece en el código anterior. Ahora para llamar un método escrito en Unity script desde código iOS, podemos llamar UnitySendMessage(“UnityObjectName, “UnityObject’sMethodName”, “Your message”). En respuesta, Unity se buscará por el UnityObject y luego llama ese UnityObject’sMethod para proveer el mensaje que pasó. Ahora el UnityIOSBridge.m class debería verse como:
#import "UnityIOSBridge.h"
void messageFromUnity(char *message)
{
NSString *messageFromUnity = [NSString stringWithUTF8String:message];
NSLog(@"%@",messageFromUnity);
}
@implementation UnityIOSBridge
-(void)sendMessageToUnity
{
UnitySendMessage(listenerObject, "messageFromIOS", "Hello Unity!");
}
@end
Y el Unity script UnityIOSBridge.cs debería verse como:
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);
}
}
Bueno este fue un requerimiento muy simple pero ahora si queremos hacer algo más, por ejemplo: nuestro plugin debería ser capaz de notificar a Unity acerca de las llamadas delegadas UIApplication. No hay necesidad de preocuparse, ya que vamos a poner en práctica esto también, aunque para hacer esto tenemos que hacer algo de trabajo previo. Objetive-C es un lenguaje orientado al tiempo de ejecución, que significa que cuando es posible esto difiere decisiones acerca de lo que actualmente será ejecutado, desde la compilación y tiempo de enlace hasta cuando se está ejecutando en el tiempo de ejecución.
Esto le da mucha flexibilidad, puede redirigir mensajes a objetos apropiados cuando lo necesite o incluso puede cambiar intencionalmente el método de implementación, etc. Esto requiere el uso de un tiempo de ejecución el cual puede inspeccionar objetos para ver lo que hacen y por qué no responden, y métodos de despacho apropiadamente. Así, tomaremos un simple ejemplo de “aplicación: didFinishLaunchingWithOptions:”. Método delegado UIApplicationDelagate. Crearemos una categoría class de UIApplication y pondrá en práctica el método de carga. En método de carga, intercambiaremos el setDelagete método de implementación de UIApplication con el método setApp42Delegate, método de nuestra clase como a continuación:
+(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;
}
Démosle un vistazo a los fragmentos de los códigos anteriores, el método “setApp42Delegate” llama nuestro “exchangeMethodImplementations” que agrega el “app42RunTimeDidFinishLaunching” a UIApplication class y intercambia las implementaciones de “app42RunTimeDidFinishLaunching” con “application: didFinishLaunchingWithOptions:” si existe. De esta manera podemos tener acceso a todos los métodos de UIApplicationDelegate como “applicationDidEnterBackground:”, “application:didRegisterForRemoteNotificationsWithDeviceToken:” etc, sin hacer cambios directamente a Unity generada proyecto Xcode. Puede descargar el código de fuente de nuestro Unity plugin para iOS notificaciones Push dese Git Repo.
Si tiene alguna pregunta o necesita asistencia, por favor contáctenos a support@shephertz.com
Leave A Reply