En ocasiones nos puede resultar interesante poder ejecutar código nativo de iOS desde nuestra aplicación Unity. Por ejemplo, esto puede ser interesante para mostrar alertas personalizadas de iOS, abrir componentes nativos como el MFMailComposer, crear un UIActivityViewController para compartir archivos en las aplicaciones que tenemos instaladas en nuestro dispositivo, etc.

En esta ocasión vamos a ver como podemos ejecutar código nativo de iOS (en primer lugar Objective-C) desde nuestra aplicación de Unity3D. Y esto lo vamos a hacer

En el siguiente ejemplo vamos a desarrollar un Framework de iOS que servirá para imprimir por consola los mensajes que recibamos desde una aplicación de Unity (wooow! super útil!). Yo te enseño como ejecutar el código y tú luego eres libre de completarlo con todas las super-funcionalidades que se te ocurran.

Desarrollando un Framework en iOS

Este artículo está desarrollado utilizando Xcode 8.2.1 y Unity 5.6.1

En primer lugar creamos un nuevo proyecto con Xcode de tipo Framework.

Creamos un nuevo proyecto de tipo iOS->Cocoa Touch Framework

En las opciones del proyecto elegimos Lenguaje->Objective-C.

Elegimos Objective-C como lenguaje principal

Una vez creado nuestro nuevo proyecto de Framework tendremos algo así:

Vista del proyecto recién creado

A continuación añadimos un nuevo archivo de tipo iOS->Cocoa Touch Class.

Añadimos un archivo de tipo Cocoa Touch Class

Lo llamamos ‘Communicator’ y en el tipo de subclase podemos dejar por defecto UIViewController. No importa el tipo de subclase que elijas aquí ya que vamos a eliminar el código que Xcode nos crea por defecto. Eso sí, recuerda indicar Objective-C como lenguaje de la clase. No marcamos la opción de crear un archivo XIB.

Configuración inicial de la nueva clase.

Esto nos habrá creado dos archivos: la cabecera y la implementación.

Archivos .h y .m creados.

El archivo de cabecera ‘Communicator.h’ tiene que quedar con este código:

1
2
3
4
5
6
7
8
9
#import <Foundation/Foundation.h>

@interface Communicator : NSObject

+ (void)sayHelloWorld;

+ (void)repeatMessage:(NSString *)string;

@end

Y la implementación (Communicator.m) quedará así:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#import "Communicator.h"

@implementation Communicator

+ (void)sayHelloWorld {

NSLog(@"Hello world!");

}

+ (void)repeatMessage:(NSString *)string

{

NSLog(@"Message from framework: %@", string);

}

@end

Como ves, hemos definido dos métodos a los cuales llamaremos desde la aplicación Unity. El primero de ellos es un simple ‘Hola mundo’ que imprimirá ese mismo texto por la consola y el segundo método imprimirá por consola el texto que le pasemos desde la aplicación Unity.

A continuación necesitamos crear otra clase que sea nuestro ‘puente’ entre los scripts de C# en Unity y las clases de nuestro Framework. Este será nuestro ‘Unity bridge’.

Creamos una nueva clase de tipo iOS->Cocoa Touch Class y la llamamos ‘UnityBridge’. Al igual que antes seleccionamos lenguaje->Objective-C y no importa la subclase que elijamos.

Nueva Cocoa Touch Class en Objective-C

¡Perfecto! De momento así es como tenemos nuestro proyecto:

Estado actual de nuestro proyecto

Abrimos el archivo de cabecera ‘UnityBridge.h’ y reemplazamos el código por este:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#import "Communicator.h"

#ifdef __cplusplus

extern “C” {

#endif

void framework_sayHello();

void framework_repeatMessage(const char* message);

#ifdef __cplusplus

}

#endif

La forma de enlazar el código de nuestro Framework con los scripts de Unity será mediante este script que hemos desarrollado en C. Este código es el que nos permitirá utilizar nuestras funciones del Framework directamente desde C#.

El siguiente paso es renombrar el archivo ‘UnityBridge.m’ por ‘UnityBridge.mm’. Al cambiarle la extensión por .mm estamos haciendo que el compilador compile este archivo como Objective-C++ en lugar de Objective-C.

Reemplazamos el contenido del archivo ‘UnityBridge.mm’ por el siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
#import "UnityBridge.h"

void framework_sayHello() {

[Communicator sayHelloWorld];

}

void framework_repeatMessage(const char* message) {

[Communicator repeatMessage:[NSString stringWithUTF8String:message]];

}

Estos métodos son los que llamaremos desde nuestro código C#, y ellos a su vez llamarán a los métodos Objective-C implementados en nuestra clase ‘Communicator’.

Por lo tanto la comunicación queda de la siguiente manera:

Unity C# -> Bridge C -> Framework Objective-C

Y con esto tenemos nuestro Framework listo para ser compilado e importado en nuestro proyecto Unity.

Compilamos nuestro código mediante el menú Product > Build y tendremos el archivo .framework dentro de la carpeta ‘Products’ listo para ser copiado.

Nuestro Framework compilado y listo para ser utilizado.

Botón derecho y ‘Mostrar en finder’ para localizar el archivo.

Más adelante necesitaremos este archivo.

 

Importando el Framework iOS en nuestro juego Unity3d

Sólo nos queda la última parte, abrimos Unity y creamos un nuevo proyecto vacío. La escena la puedes configurar con un simple cubo. Este proyecto representa tu juego en Unity3d.

El siguiente paso es fundamental entenderlo. Dentro de la carpeta ‘Assets’ necesitamos crear una nueva carpeta llamada ‘Plugins’. Y dentro de la carpeta ‘Plugins’ crearemos otra carpeta llamada ‘iOS’.

Assets > Plugins > iOS

Finalmente, seleccionamos nuestro archivo .framework que nos ha generado Xcode y lo arrastramos dentro de la carpeta iOS que acabamos de crear en nuestro proyecto Unity.

Carpetas creadas y el framework importado

Esta estructura de carpetas nos asegura que  el archivo *.framework será añadido automáticamente a nuestro compilado iOS, y no será añadido a ningún otro compilado (como un compilado Android).

A partir de este momento ya podemos utilizar los métodos de nuestro framework. Veamos cómo…

Creamos un nuevo script en C# y lo llamamos ‘FrameworkBridge.cs’. Este script lo puedes colocar en cualquier carpeta excepto en ‘Plugins/iOS’.

Dentro del archivo ‘FrameworkBridge.cs’ introducimos el siguiente código:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using UnityEngine;
using System.Collections;
using System.Runtime.InteropServices;
using AOT;

public class FrameworkBridge : MonoBehaviour {
#if UNITY_IOS
[DllImport ("__Internal")]
private static extern void framework_sayHello();

[DllImport ("__Internal")]
private static extern void framework_repeatMessage (string message);
#endif

public static void hello() {
#if UNITY_IOS
if (Application.platform == RuntimePlatform.IPhonePlayer) {
framework_sayHello ();
}
#endif
}

public static void message(string message) {
#if UNITY_IOS
if (Application.platform == RuntimePlatform.IPhonePlayer) {
framework_repeatMessage(message);
}
#endif
}
}

Mediante la condición #if UNITY_IOS nos aseguramos de que este código sólo compile cuando hagamos un build para iOS y mediante el if (Application.platform == RuntimePlatform.IPhonePlayer) nos aseguramos de que este código sólo se ejecuta en iOS.

Los DllImport se encargan de importar el código de nuestro framework externo y las funciones que hemos definido como ‘private static extern’ son los funciones prototipo que se encuentran definidas dentro del framework. Deben de tener el mismo nombre, número de parámetros, etc. La única diferencia es que aquí utilizamos string en lugar de const char* porque Mono es lo suficientemente inteligente para hacer la conversión.

Ahora ya podemos utilizar estas funciones como si fuera código nativo.

Creamos un nuevo script en C# con el nombre ‘CubeHandler’ e introducimos el siguiente código:

1
2
3
4
5
6
7
8
9
10
11
12
13
 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CubeHandler : MonoBehaviour {

void Start () {
FrameworkBridge.hello();
FrameworkBridge.message("Hello from Unity!");
}
}

Añadimos este script al Cubo que hemos creado en la escena y salvamos el proyecto. Estamos listos para compilar el proyecto.

File > Build Settings > iOS

Recuerda establecer la versión mínima de iOS a 8, ya que no funcionará en versiones anteriores. Click en Build, seleccionamos una ruta de destino de nuestro proyecto y generamos el proyecto de Xcode.

Compilando el proyecto final de Xcode

Una vez que Unity ha generado el proyecto de Xcode procedemos a abrirlo. Sólo nos falta hacer unos pequeños ajustes.

En el explorador de archivos de Xcode expandimos la carpeta Frameworks > Plugins > iOS y encontraremos nuestro PMCommunicator.framework.

Tenemos que arrastrar nuestro framework dentro de la sección Project Settings > General > Embedded Binaries:

Arrastramos nuestro framework a Embedded Binaries

 

Eso es todo, ya podemos compilar el proyecto y ejecutarlo en nuestro dispositivo.

Si te fijas en la consola de Xcode, cuando ejecutes la aplicación podrás ver los mensajes:

Hello World!

Message from framework: Hello from Unity!

Esta entrada está inspirada en el siguiente artículo:

Integrating native iOS code into Unity – Andrej Rolih (Original en inglés)

About Pablo Marcos

Soy ingeniero informático y apasionado de la programación y la tecnología. Actualmente viviendo en Alemania y trabajando como desarrollador interactivo en Stoll Von Gáti GmbH. Mi twitter @pablo_marcos