В Unity3d существует возможность подключения нативных плагинов к приложению. На официальном сайте Unity3d есть документация по взаимодействию с нативным кодом на iOS. Данная документация ограничивается описанием того, как вызвать ту или иную функцию из плагина, который вы собрали сами и как сделать обратный вызов. В документации описано каким правилам должна соответствовать описываемая функция и немного о том, как передать в нее параметры. В конце статьи с документацией есть ссылка, по которой можно скачать пример под названием Bonjour.
К сожалению в документации не описано, что делать в случае если вашему плагину необходимо перехватить событие о том, что пользователь подписался на Push Notification или приложение перешло в бэкграунд (AppDidEnterBackgound), тогда как доступ к этим событием бывает необходим для некоторых плагинов, которые вы возможно захотите написать.
Начнем с того, что в документации описано, что можно передавать параметры из C# или js кода в нативный и обратно. Однако явным образом эти правила не описаны, и возможно имеет смысл описать их подробней. Правила о передаче параметров, сформулированные ниже были получены при изучении уже существующих плагинов: Fb Unity SDK и GoogleAnalytics.
Типы параметров, которые можно передавать между нативным кодом и C# (и наоборот):
При работе в нативном коде const char* можно просто конвертировать в более удобный для iOS NSString таким образом:
Строковые значения, возвращаемые из нативного метода должны быть в кодировке UTF–8, кодироваться и выделяться в куче. Сортировочные вызовы Моно бесплатны для строк. Выделение в куче необходимо, потому что Mono компилятор для возвращаемых С строк создает .NET string и вызывает для возвращаемого значения free.
Для того, чтобы избежать проблем можно вызывать метод, который создает копию строки в куче (из пример Bonjour):
Функционал, связанный с изменением состояния приложения или его view не документирован в Unity, для получения информации был собран проект, в который был интегрирован официальный Fb SDK для Unity3d. В проекте использовалась версия Unity3d 5.1.2f1 и Fb Unity SKD версии 6.2 (Можно найти здесь), iOS SDK 8.4. Проект был собран под iOS, в Player Settings в качестве Scripting Backend был выбран IL2CPP.
Для чего может понадобится данная информация? Для разработки нативного iOS плагина, использующего нативные функции и слушающая сообщения об изменениях состояния приложения (The App Life Cycle — Apple Developer), изменениях состояния главного View приложения и рендерера. К примеру данный функционал может потребоваться для интеграции нативных плагинов социальных сетей или плагинов различных аналитик, у которых есть поддерживаемые нативные iOS плагины, но нет версий плагинов для Unity3d.
В таком случае есть 2 выхода:
Так как в текущем проекте использовался FB SDK, а времени было достаточно, было принято решение разобраться в том, что происходит под капотом XCode проекта, который генерирует Unity.
В состав FB плагина для Unity входят два файла, которые интересуют больше всего FbUnityInterface.mm и .h, в которых содержится нативный код для iOS.
В .mm файле бросается в глаза такой участок кода:
Выглядит так, будто FB перехватывает вызовы стандартных методов делегата приложения. В заголовочном файле мы видим следующие стоки:
AppDelegateListener.h находится в директории Classes/PluginBase/ Xcode проекта. Для того, чтобы перехватывать события делегата Unity описывает 4 протокола. Для тех, кто не знаком с понятием протокола Objective C — Английская версия и Русская версия документации.
Основным протоколом является LifeCycleListener. Именно его расширяют два из трех оставшихся протокола. Он позволяет слушать события:
А также объявляет 2 метода:
Первый метод подписывает объект на получение перечисленных событий, второй отписывает. Следующим рассмотрим протокол AppDelegateListener:
Он дает доступ к сообщениям о Push Notifications, открытии ULR приложениям и некоторым другим.Все сообщения, которые доступны классу, реализующему AppDelegateListener можно посмотреть в AppDelegateListener.mm.
Протокол RenderPluginDelegate дает доступ к событиям рендера Unity и также расширяет LifeCycleListener. Исходник содержит хорошие комментарии и информацию обо всех методах можно получить оттуда.
Последний в очереди протокол UnityViewControllerLIstener. Он позволяет получить доступ к основным событиям главного view приложения. Этот протокол не имеет зависимости от LifeCycleListener. Список событий:
Из исходного кода Fb SDK видно, что данные возможности появились в Unity3d только начиная с версии 4.3.
Константа HAS_UNITY_VERSION_DEF, хранящая информацию о том, что в проекте есть информация о версии Unity3d, использованной для сборки проекта объявляется в заголовочном файле RegisterMonoModules.h:
Это все содержимое RigisterMonoModules.h. Если HAS_UNITY_VERSION_DEF объявлена в проекте, тогда за значением версии Unity3d можно обратиться к константе UNITY_VERSION. Таким образом, если вы используете Unity3d версии ниже 4.3, то не стоит расчитывать на наличие четырех описанных выше протоколов.
Для некоторых нативных плагинов может потребоваться наличие того или иного ключа в файле .plist нативного приложения. По факту plist можно принять эквивалентным xml. В состав Fb SDK входят исходники FbPlistParser и PlistDic, которые можно использовать для добавления ключа, который потребуется вашему плагину. К примеру можно добавить в проект скрипт с PostProcessBuildAttribute для того, чтобы по окончании сборки проекта найти в XCode проекте plist файл и добавить к нему необходимые ключи со значениями.
Надеюсь, что данная информация оказалась полезной и помогла кому-нибудь в борьбе с нативными плагинами.
К сожалению в документации не описано, что делать в случае если вашему плагину необходимо перехватить событие о том, что пользователь подписался на Push Notification или приложение перешло в бэкграунд (AppDidEnterBackgound), тогда как доступ к этим событием бывает необходим для некоторых плагинов, которые вы возможно захотите написать.
Начнем с того, что в документации описано, что можно передавать параметры из C# или js кода в нативный и обратно. Однако явным образом эти правила не описаны, и возможно имеет смысл описать их подробней. Правила о передаче параметров, сформулированные ниже были получены при изучении уже существующих плагинов: Fb Unity SDK и GoogleAnalytics.
Типы параметров, которые можно передавать между нативным кодом и C# (и наоборот):
string = const char* //Из Unity в натив
char* = string //Из натива в Unity
bool //(конвертация не нужна)
int //(конвертация не нужна)
float //(конвертация не нужна)
При работе в нативном коде const char* можно просто конвертировать в более удобный для iOS NSString таким образом:
NSString* CreateNSString (const char* string)
{
if (string)
return [NSString stringWithUTF8String: string];
else
return [NSString stringWithUTF8String: ""];
}
Строковые значения, возвращаемые из нативного метода должны быть в кодировке UTF–8, кодироваться и выделяться в куче. Сортировочные вызовы Моно бесплатны для строк. Выделение в куче необходимо, потому что Mono компилятор для возвращаемых С строк создает .NET string и вызывает для возвращаемого значения free.
Для того, чтобы избежать проблем можно вызывать метод, который создает копию строки в куче (из пример Bonjour):
char* MakeStringCopy (const char* string)
{
if (string == NULL)
return NULL;
char* res = (char*)malloc(strlen(string) + 1);
strcpy(res, string);
return res;
}
Функционал, связанный с изменением состояния приложения или его view не документирован в Unity, для получения информации был собран проект, в который был интегрирован официальный Fb SDK для Unity3d. В проекте использовалась версия Unity3d 5.1.2f1 и Fb Unity SKD версии 6.2 (Можно найти здесь), iOS SDK 8.4. Проект был собран под iOS, в Player Settings в качестве Scripting Backend был выбран IL2CPP.
Для чего может понадобится данная информация? Для разработки нативного iOS плагина, использующего нативные функции и слушающая сообщения об изменениях состояния приложения (The App Life Cycle — Apple Developer), изменениях состояния главного View приложения и рендерера. К примеру данный функционал может потребоваться для интеграции нативных плагинов социальных сетей или плагинов различных аналитик, у которых есть поддерживаемые нативные iOS плагины, но нет версий плагинов для Unity3d.
В таком случае есть 2 выхода:
- Сгенерировать XCode проект. Найти исходный код делегата приложения, изменить его и интегрировать плагин. Минус такого подхода в том, что если версия вашего приложения далека от финала и в дальнейшем будут вноситься правки, то XCode проект придется генерировать заново и интегрировать плагин с нуля. Я использовал такой подход только однажды. Издательство настаивало на интеграции собственного трекера инсталлов и покупок, а проекте использовался плагин Unibill, соответственно трекинг платежки был завраплен этим плагином и код, вызывающий трекинг платежки от издателя я вызывал из нативной части исходников Unibill. Это решение использовалось только потому, что времени на то, чтобы написать Unity плагин на основе нативного издатель не давал и сама интеграция заняла полчаса.
- Использовать нативный функционал Unity3d, реализованный в проекте iOS. Минус в том, что этот функционал не документирован. Однако он используется плагинами Google Analytics и Fb SKD для Unity.
Так как в текущем проекте использовался FB SDK, а времени было достаточно, было принято решение разобраться в том, что происходит под капотом XCode проекта, который генерирует Unity.
В состав FB плагина для Unity входят два файла, которые интересуют больше всего FbUnityInterface.mm и .h, в которых содержится нативный код для iOS.
В .mm файле бросается в глаза такой участок кода:
-(void)didBecomeActive: (NSNotification *)notification {
[FBAppCall handleDidBecomeActiveWithSession:self.session];
}
-(void)willTerminate:(NSNotification *)notification {
[self.session close];
}
-(void)didFinishLaunching:(NSNotification *)notification {
Выглядит так, будто FB перехватывает вызовы стандартных методов делегата приложения. В заголовочном файле мы видим следующие стоки:
#if UNITY_VERSION >= 430
#import "AppDelegateListener.h"
@interface FbUnityInterface : NSObject <AppDelegateListener>
AppDelegateListener.h находится в директории Classes/PluginBase/ Xcode проекта. Для того, чтобы перехватывать события делегата Unity описывает 4 протокола. Для тех, кто не знаком с понятием протокола Objective C — Английская версия и Русская версия документации.
Основным протоколом является LifeCycleListener. Именно его расширяют два из трех оставшихся протокола. Он позволяет слушать события:
- (void)didFinishLaunching:(NSNotification*)notification;
- (void)didBecomeActive:(NSNotification*)notification;
- (void)willResignActive:(NSNotification*)notification;
- (void)didEnterBackground:(NSNotification*)notification;
- (void)willEnterForeground:(NSNotification*)notification;
- (void)willTerminate:(NSNotification*)notification;
А также объявляет 2 метода:
void UnityRegisterLifeCycleListener(id<LifeCycleListener> obj);
void UnityUnregisterLifeCycleListener(id<LifeCycleListener> obj);
Первый метод подписывает объект на получение перечисленных событий, второй отписывает. Следующим рассмотрим протокол AppDelegateListener:
@protocol AppDelegateListener<LifeCycleListener>
Он дает доступ к сообщениям о Push Notifications, открытии ULR приложениям и некоторым другим.Все сообщения, которые доступны классу, реализующему AppDelegateListener можно посмотреть в AppDelegateListener.mm.
Протокол RenderPluginDelegate дает доступ к событиям рендера Unity и также расширяет LifeCycleListener. Исходник содержит хорошие комментарии и информацию обо всех методах можно получить оттуда.
@protocol RenderPluginDelegate<LifeCycleListener, NSObject>
Последний в очереди протокол UnityViewControllerLIstener. Он позволяет получить доступ к основным событиям главного view приложения. Этот протокол не имеет зависимости от LifeCycleListener. Список событий:
- (void)viewDidDisappear:
- (void)viewWillDisappear:
- (void)viewDidAppear:
- (void)viewWillAppear:
- (void)interfaceWillChangeOrientation:
- (void)interfaceDidChangeOrientation:
Из исходного кода Fb SDK видно, что данные возможности появились в Unity3d только начиная с версии 4.3.
#if HAS_UNITY_VERSION_DEF
#include "UnityTrampolineConfigure.h"
#endif
#if UNITY_VERSION >= 430
#import "AppDelegateListener.h"
@interface FbUnityInterface : NSObject <AppDelegateListener>
#else
Константа HAS_UNITY_VERSION_DEF, хранящая информацию о том, что в проекте есть информация о версии Unity3d, использованной для сборки проекта объявляется в заголовочном файле RegisterMonoModules.h:
void RegisterMonoModules();
#define HAS_UNITY_VERSION_DEF 1
Это все содержимое RigisterMonoModules.h. Если HAS_UNITY_VERSION_DEF объявлена в проекте, тогда за значением версии Unity3d можно обратиться к константе UNITY_VERSION. Таким образом, если вы используете Unity3d версии ниже 4.3, то не стоит расчитывать на наличие четырех описанных выше протоколов.
Для некоторых нативных плагинов может потребоваться наличие того или иного ключа в файле .plist нативного приложения. По факту plist можно принять эквивалентным xml. В состав Fb SDK входят исходники FbPlistParser и PlistDic, которые можно использовать для добавления ключа, который потребуется вашему плагину. К примеру можно добавить в проект скрипт с PostProcessBuildAttribute для того, чтобы по окончании сборки проекта найти в XCode проекте plist файл и добавить к нему необходимые ключи со значениями.
Надеюсь, что данная информация оказалась полезной и помогла кому-нибудь в борьбе с нативными плагинами.
loreglean
Еще, если нужен более полный контроль над AppController-ом (извиняюсь за каламбур), можно просто отнаследоваться от classes/UnityAppController и переопределить нужные методы (не забывая добавлять [super ...]). Для того, чтобы использовался новый AppController вместо дефолтного, нужно дополнительно поправить main.mm (а конкретно AppControllerClassName поле)