【前言】
接入Android和iOS SDK有很多相同的地方,建議先看下Android SDK如何接入。
?【UnityAppController詳解】
?整個(gè)程序的入口在MainApp文件下的main.mm文件中,先加載了unityframework,然后調(diào)用runUIApplicationMain。源碼如下:(這些源碼在Xcode工程里都有)
#include <UnityFramework/UnityFramework.h>
UnityFramework* UnityFrameworkLoad()
{
NSString* bundlePath = nil;
bundlePath = [[NSBundle mainBundle] bundlePath];
bundlePath = [bundlePath stringByAppendingString: @"/Frameworks/UnityFramework.framework"];//獲取完整的UnityFramework的路徑
NSBundle* bundle = [NSBundle bundleWithPath: bundlePath];
if ([bundle isLoaded] == false) [bundle load];//調(diào)用bunlde加載接口加載,bundle表示一個(gè)包含代碼、資源和其他文件的目錄或者是一個(gè).framework文件
//UnityFramework類的頭文件是UnityFramework.h,實(shí)現(xiàn)文件在Classes文件夾中的main.mm
UnityFramework* ufw = [bundle.principalClass getInstance];//獲取bundle主類示例,主類是一個(gè)UnityFramework,bundle 只有一個(gè)主類,該類通常是應(yīng)用程序的控制器類
if (![ufw appController]) //調(diào)用appController方法獲取,第一次獲取的為空
{
// unity is not initialized
[ufw setExecuteHeader: &_mh_execute_header];//設(shè)置頭信息,初始化unity引擎
}
return ufw;
}
int main(int argc, char* argv[])//main是整個(gè)應(yīng)用程序的入口,和什么語言沒關(guān)系,一般都是這樣
{
@autoreleasepool //創(chuàng)建一個(gè)自動(dòng)釋放池
{
id ufw = UnityFrameworkLoad();//先加載了UnityFramework
[ufw runUIApplicationMainWithArgc: argc argv: argv];//runUIApplicationMainWithArgc,這個(gè)方式是UnityFramework.h中的方法
return 0;
}
}
可以看看UnityFramework.h文件中定義的函數(shù)
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import "UnityAppController.h"
#include "UndefinePlatforms.h"
#include <mach-o/ldsyms.h>
typedef struct
#ifdef __LP64__
mach_header_64
#else
mach_header
#endif
MachHeader;
#include "RedefinePlatforms.h"
//! Project version number for UnityFramework.
FOUNDATION_EXPORT double UnityFrameworkVersionNumber;
//! Project version string for UnityFramework.
FOUNDATION_EXPORT const unsigned char UnityFrameworkVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <UnityFramework/PublicHeader.h>
#pragma once
// important app life-cycle events
__attribute__ ((visibility("default")))
@protocol UnityFrameworkListener<NSObject>
@optional
- (void)unityDidUnload:(NSNotification*)notification;
- (void)unityDidQuit:(NSNotification*)notification;
@end
__attribute__ ((visibility("default")))
@interface UnityFramework : NSObject
{
}
- (UnityAppController*)appController;
+ (UnityFramework*)getInstance;
- (void)setDataBundleId:(const char*)bundleId;
- (void)runUIApplicationMainWithArgc:(int)argc argv:(char*[])argv;
- (void)runEmbeddedWithArgc:(int)argc argv:(char*[])argv appLaunchOpts:(NSDictionary*)appLaunchOpts;
- (void)unloadApplication;
- (void)quitApplication:(int)exitCode;
- (void)registerFrameworkListener:(id<UnityFrameworkListener>)obj;
- (void)unregisterFrameworkListener:(id<UnityFrameworkListener>)obj;
- (void)showUnityWindow;
- (void)pause:(bool)pause;
- (void)setExecuteHeader:(const MachHeader*)header;
- (void)sendMessageToGOWithName:(const char*)goName functionName:(const char*)name message:(const char*)msg;
@end
函數(shù)實(shí)現(xiàn)在Classes文件夾下的main.mm文件中
#include "RegisterFeatures.h"
#include <csignal>
#include "UnityInterface.h"
#include "../UnityFramework/UnityFramework.h"
void UnityInitTrampoline();
// WARNING: this MUST be c decl (NSString ctor will be called after +load, so we cant really change its value)
const char* AppControllerClassName = "UnityAppController";
#if UNITY_USES_DYNAMIC_PLAYER_LIB
extern "C" void SetAllUnityFunctionsForDynamicPlayerLib();
#endif
extern "C" void UnitySetExecuteMachHeader(const MachHeader* header);
extern "C" __attribute__((visibility("default"))) NSString* const kUnityDidUnload;
extern "C" __attribute__((visibility("default"))) NSString* const kUnityDidQuit;
@implementation UnityFramework
{
int runCount;
}
UnityFramework* _gUnityFramework = nil;
+ (UnityFramework*)getInstance
{
if (_gUnityFramework == nil)
{
_gUnityFramework = [[UnityFramework alloc] init];//獲取單例時(shí)先調(diào)用alloc分配內(nèi)存,再調(diào)用init初始化
}
return _gUnityFramework;
}
- (UnityAppController*)appController
{
return GetAppController(); //調(diào)用UnityAppController.mm中的方法
}
- (void)setExecuteHeader:(const MachHeader*)header
{
UnitySetExecuteMachHeader(header);//一個(gè) Unity 引擎的函數(shù),用于設(shè)置當(dāng)前可執(zhí)行文件的 Mach-O 頭信息,header是一個(gè)指向可執(zhí)行文件 Mach-O 頭信息的指針
}//在 macOS 或 iOS 上,可執(zhí)行文件的 Mach-O 頭信息包含有關(guān)可執(zhí)行文件的元數(shù)據(jù),例如文件類型、CPU 架構(gòu)、入口點(diǎn)和段信息。Unity 引擎使用該函數(shù)來設(shè)置當(dāng)前可執(zhí)行文件的 Mach-O 頭信息,以確保 Unity 引擎可以正確加載并執(zhí)行游戲邏輯。
- (void)sendMessageToGOWithName:(const char*)goName functionName:(const char*)name message:(const char*)msg
{
UnitySendMessage(goName, name, msg);
}
- (void)registerFrameworkListener:(id<UnityFrameworkListener>)obj
{
#define REGISTER_SELECTOR(sel, notif_name) \
if([obj respondsToSelector:sel]) \
[[NSNotificationCenter defaultCenter] addObserver:obj selector:sel name:notif_name object:nil];
REGISTER_SELECTOR(@selector(unityDidUnload:), kUnityDidUnload);
REGISTER_SELECTOR(@selector(unityDidQuit:), kUnityDidQuit);
#undef REGISTER_SELECTOR
}
- (void)unregisterFrameworkListener:(id<UnityFrameworkListener>)obj
{
[[NSNotificationCenter defaultCenter] removeObserver: obj name: kUnityDidUnload object: nil];
[[NSNotificationCenter defaultCenter] removeObserver: obj name: kUnityDidQuit object: nil];
}
- (void)frameworkWarmup:(int)argc argv:(char*[])argv
{
#if UNITY_USES_DYNAMIC_PLAYER_LIB
SetAllUnityFunctionsForDynamicPlayerLib();
#endif
UnityInitTrampoline();
UnityInitRuntime(argc, argv);
RegisterFeatures();
// iOS terminates open sockets when an application enters background mode.
// The next write to any of such socket causes SIGPIPE signal being raised,
// even if the request has been done from scripting side. This disables the
// signal and allows Mono to throw a proper C# exception.
std::signal(SIGPIPE, SIG_IGN);
}
- (void)setDataBundleId:(const char*)bundleId
{
UnitySetDataBundleDirWithBundleId(bundleId);
}
- (void)runUIApplicationMainWithArgc:(int)argc argv:(char*[])argv
{
self->runCount += 1;
[self frameworkWarmup: argc argv: argv];
UIApplicationMain(argc, argv, nil, [NSString stringWithUTF8String: AppControllerClassName]);
//UIApplicationMain是iOS應(yīng)用程序的入口點(diǎn),一般在入口main函數(shù)中調(diào)用,unity在frameworkWarmup后調(diào)用,其用于啟動(dòng)應(yīng)用程序并設(shè)置應(yīng)用程序的主運(yùn)行循環(huán)(main run loop)
//該方法有4個(gè)參數(shù),第三個(gè)參數(shù)是NSString類型的principalClassName,其是應(yīng)用程序?qū)ο笏鶎俚念悾擃惐仨毨^承自UIApplication類,如果所屬類字符串的值為nil, UIKit就缺省使用UIApplication類
//第四個(gè)參數(shù)是NSString類型的delegateClassName,其是應(yīng)用程序類的代理類,該函數(shù)跟據(jù)delegateClassName創(chuàng)建一個(gè)delegate對(duì)象,并將UIApplication對(duì)象中的delegate屬性設(shè)置為delegate對(duì)象,這里創(chuàng)建了UnityAppController,看其頭文件是繼承了UIApplicationDelegate
//該函數(shù)創(chuàng)建UIApplication對(duì)象和AppDelegate對(duì)象,然后將控制權(quán)交給主運(yùn)行循環(huán),等待事件的發(fā)生。UIApplicationMain還會(huì)創(chuàng)建應(yīng)用程序的主窗口,并將其顯示在屏幕上,從而啟動(dòng)應(yīng)用程序的UI界面。
}
- (void)runEmbeddedWithArgc:(int)argc argv:(char*[])argv appLaunchOpts:(NSDictionary*)appLaunchOpts
{
if (self->runCount)
{
// initialize from partial unload ( sceneLessMode & onPause )
UnityLoadApplicationFromSceneLessState();
UnitySuppressPauseMessage();
[self pause: false];
[self showUnityWindow];
// Send Unity start event
UnitySendEmbeddedLaunchEvent(0);
}
else
{
// full initialization from ground up
[self frameworkWarmup: argc argv: argv];
id app = [UIApplication sharedApplication];
id appCtrl = [[NSClassFromString([NSString stringWithUTF8String: AppControllerClassName]) alloc] init];
[appCtrl application: app didFinishLaunchingWithOptions: appLaunchOpts];
[appCtrl applicationWillEnterForeground: app];
[appCtrl applicationDidBecomeActive: app];
// Send Unity start (first time) event
UnitySendEmbeddedLaunchEvent(1);
}
self->runCount += 1;
}
- (void)unloadApplication
{
UnityUnloadApplication();
}
- (void)quitApplication:(int)exitCode
{
UnityQuitApplication(exitCode);
}
- (void)showUnityWindow
{
[[[self appController] window] makeKeyAndVisible];
}
- (void)pause:(bool)pause
{
UnityPause(pause);
}
@end
#if TARGET_IPHONE_SIMULATOR && TARGET_TVOS_SIMULATOR
#include <pthread.h>
extern "C" int pthread_cond_init$UNIX2003(pthread_cond_t *cond, const pthread_condattr_t *attr)
{ return pthread_cond_init(cond, attr); }
extern "C" int pthread_cond_destroy$UNIX2003(pthread_cond_t *cond)
{ return pthread_cond_destroy(cond); }
extern "C" int pthread_cond_wait$UNIX2003(pthread_cond_t *cond, pthread_mutex_t *mutex)
{ return pthread_cond_wait(cond, mutex); }
extern "C" int pthread_cond_timedwait$UNIX2003(pthread_cond_t *cond, pthread_mutex_t *mutex,
const struct timespec *abstime)
{ return pthread_cond_timedwait(cond, mutex, abstime); }
#endif // TARGET_IPHONE_SIMULATOR && TARGET_TVOS_SIMULATOR
調(diào)用UIApplicationMain創(chuàng)建了UnityAppController之后,接收iOS系統(tǒng)事件通知即可,接收通知的處理在?UnityAppController.mm文件中。下面是與生命周期相關(guān)的事件
先是willFinishLaunchingWithOptions,只發(fā)了個(gè)通知
- (BOOL)application:(UIApplication*)application willFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
AppController_SendNotificationWithArg(kUnityWillFinishLaunchingWithOptions, launchOptions);
return YES;
}
void AppController_SendNotificationWithArg(NSString* name, id arg) //Unity引擎用于在iOS平臺(tái)上發(fā)送消息通知的方法
{
[[NSNotificationCenter defaultCenter] postNotificationName: name object: GetAppController() userInfo: arg];
}
隨后是didFinishLaunchingWithOptions
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
::printf("-> applicationDidFinishLaunching()\n");
// send notfications
#if !PLATFORM_TVOS
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
if (UILocalNotification* notification = [launchOptions objectForKey: UIApplicationLaunchOptionsLocalNotificationKey])
UnitySendLocalNotification(notification);
if ([UIDevice currentDevice].generatesDeviceOrientationNotifications == NO)
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
#pragma clang diagnostic pop
#endif
//UnityDataBundleDir()是一個(gè)用于獲取Unity數(shù)據(jù)包目錄的方法,它返回一個(gè)NSString對(duì)象,表示Unity數(shù)據(jù)包的路徑
//隨后初始化Unity應(yīng)用程序,但不會(huì)創(chuàng)建圖形界面
UnityInitApplicationNoGraphics(UnityDataBundleDir());
[self selectRenderingAPI];//在iOS平臺(tái)上,Unity引擎通常支持OpenGL ES和Metal兩種渲染API,該方法會(huì)根據(jù)具體的設(shè)備和系統(tǒng)版本等因素來動(dòng)態(tài)地選擇使用哪種渲染API
[UnityRenderingView InitializeForAPI: self.renderingAPI];//UnityRenderingView是用于呈現(xiàn)Unity引擎場(chǎng)景的iOS視圖,InitializeForAPI是UnityRenderingView的初始化方法,self.renderingAPI表示Unity引擎所選的渲染API
#if (PLATFORM_IOS && defined(__IPHONE_13_0)) || (PLATFORM_TVOS && defined(__TVOS_13_0))
if (@available(iOS 13, tvOS 13, *))
_window = [[UIWindow alloc] initWithWindowScene: [self pickStartupWindowScene: application.connectedScenes]];
else
#endif
//UIWindow是iOS中的一個(gè)視圖對(duì)象,用于展示應(yīng)用程序的用戶界面.它是一個(gè)特殊的視圖,通常作為應(yīng)用程序中所有視圖的容器。在iOS中,每個(gè)應(yīng)用程序都有一個(gè)主窗口,即UIWindow對(duì)象,它是整個(gè)應(yīng)用程序界面的根視圖。所有其他的視圖都是添加到UIWindow對(duì)象中的。
//UIWindow對(duì)象還可以響應(yīng)用戶的觸摸事件和手勢(shì),同Android一樣,Untiy會(huì)自己渲染視圖,但需要使用操作系統(tǒng)提供的觸摸事件
_window = [[UIWindow alloc] initWithFrame: [UIScreen mainScreen].bounds];
//創(chuàng)建UnityRenderingView對(duì)象,而不是使用iOS系統(tǒng)的視圖對(duì)象,如UIView或UIWindow,這個(gè)視圖對(duì)象會(huì)放入U(xiǎn)IWindow中
_unityView = [self createUnityView];
[DisplayManager Initialize];//unity的顯示器管理類初始化,unity使用DisplayManager來管理顯示器并在多個(gè)顯示器上呈現(xiàn)Unity場(chǎng)景
_mainDisplay = [DisplayManager Instance].mainDisplay;//設(shè)置主顯示器
[_mainDisplay createWithWindow: _window andView: _unityView];//將iOS的窗口UIWindow和Unity的RenderingView同主顯示器關(guān)聯(lián)起來
[self createUI];//用于創(chuàng)建iOS應(yīng)用程序界面中的常規(guī)視圖對(duì)象,例如按鈕、標(biāo)簽、文本框等
[self preStartUnity];//預(yù)啟動(dòng),在unity啟動(dòng)前可以注冊(cè)插件
// if you wont use keyboard you may comment it out at save some memory
[KeyboardDelegate Initialize];
return YES;
}
之后是applicationDidBecomeActive
- (void)applicationDidBecomeActive:(UIApplication*)application
{
::printf("-> applicationDidBecomeActive()\n");
[self removeSnapshotViewController];//刪除iOS應(yīng)用程序窗口中的快照視圖控制器
//在iOS應(yīng)用程序中,當(dāng)應(yīng)用程序進(jìn)入后臺(tái)時(shí),系統(tǒng)會(huì)自動(dòng)截取當(dāng)前應(yīng)用程序的截圖,以便在應(yīng)用程序再次進(jìn)入前臺(tái)時(shí)快速還原應(yīng)用程序的狀態(tài)。\
//這個(gè)截圖被稱為快照(Snapshot),而用于管理快照的視圖控制器被稱為快照視圖控制器(Snapshot View Controller)
//unity自己渲染畫面,不需要
if (_unityAppReady)//從后臺(tái)切換到前臺(tái)時(shí)
{
if (UnityIsPaused() && _wasPausedExternal == false)
{
UnityWillResume();//會(huì)調(diào)用OnApplicaionFous
UnityPause(0);
}
if (_wasPausedExternal)
{
if (UnityIsFullScreenPlaying())
TryResumeFullScreenVideo();
}
// need to do this with delay because FMOD restarts audio in AVAudioSessionInterruptionNotification handler
[self performSelector: @selector(updateUnityAudioOutput) withObject: nil afterDelay: 0.1];
UnitySetPlayerFocus(1);
}
else if (!_startUnityScheduled)//剛打開游戲app走這里,調(diào)用startUnity
{
_startUnityScheduled = true;
[self performSelector: @selector(startUnity:) withObject: application afterDelay: 0];
}
_didResignActive = false;
}
- (void)startUnity:(UIApplication*)application
{
NSAssert(_unityAppReady == NO, @"[UnityAppController startUnity:] called after Unity has been initialized");
UnityInitApplicationGraphics();//初始化Unity引擎的圖形渲染
// we make sure that first level gets correct display list and orientation
[[DisplayManager Instance] updateDisplayListCacheInUnity];
UnityLoadApplication();
Profiler_InitProfiler();//初始化Profiler
[self showGameUI];
[self createDisplayLink];
UnitySetPlayerFocus(1);
AVAudioSession* audioSession = [AVAudioSession sharedInstance];
[audioSession setCategory: AVAudioSessionCategoryAmbient error: nil];
if (UnityIsAudioManagerAvailableAndEnabled())
{
if (UnityShouldPrepareForIOSRecording())
{
[audioSession setCategory: AVAudioSessionCategoryPlayAndRecord error: nil];
}
else if (UnityShouldMuteOtherAudioSources())
{
[audioSession setCategory: AVAudioSessionCategorySoloAmbient error: nil];
}
}
[audioSession setActive: YES error: nil];
[audioSession addObserver: self forKeyPath: @"outputVolume" options: 0 context: nil];
UnityUpdateMuteState([audioSession outputVolume] < 0.01f ? 1 : 0);
#if UNITY_REPLAY_KIT_AVAILABLE
void InitUnityReplayKit(); // Classes/Unity/UnityReplayKit.mm
InitUnityReplayKit();
#endif
}
再后是進(jìn)入后臺(tái)進(jìn)入前臺(tái)
- (void)applicationDidEnterBackground:(UIApplication*)application
{
::printf("-> applicationDidEnterBackground()\n");
}
- (void)applicationWillEnterForeground:(UIApplication*)application
{
::printf("-> applicationWillEnterForeground()\n");
// applicationWillEnterForeground: might sometimes arrive *before* actually initing unity (e.g. locking on startup)
if (_unityAppReady)
{
// if we were showing video before going to background - the view size may be changed while we are in background
[GetAppController().unityView recreateRenderingSurfaceIfNeeded];
}
}
最后是applicationWillResignActive
- (void)applicationWillResignActive:(UIApplication*)application
{
::printf("-> applicationWillResignActive()\n");
if (_unityAppReady)
{
UnitySetPlayerFocus(0);
// signal unity that the frame rendering have ended
// as we will not get the callback from the display link current frame
UnityDisplayLinkCallback(0);
_wasPausedExternal = UnityIsPaused();
if (_wasPausedExternal == false)
{
// Pause Unity only if we don't need special background processing
// otherwise batched player loop can be called to run user scripts.
if (!UnityGetUseCustomAppBackgroundBehavior())
{
#if UNITY_SNAPSHOT_VIEW_ON_APPLICATION_PAUSE
// Force player to do one more frame, so scripts get a chance to render custom screen for minimized app in task manager.
// NB: UnityWillPause will schedule OnApplicationPause message, which will be sent normally inside repaint (unity player loop)
// NB: We will actually pause after the loop (when calling UnityPause).
UnityWillPause();
[self repaint];
UnityWaitForFrame();
[self addSnapshotViewController];
#endif
UnityPause(1);
}
}
}
_didResignActive = true;
}
【接入SDK】
總體上和Android沒什么區(qū)別,相比Andorid好點(diǎn)是不用做Jar,因?yàn)楸举|(zhì)上是C#調(diào)用C,C調(diào)用OC,一些需要的編譯在出包的時(shí)候都會(huì)編譯好。
同樣的看是否需要生命周期,不需要生命周期的自己寫調(diào)用即可。如果需要生命周期,同樣是需要繼承的,在Android中繼承的是Activiry,在iOS就是要繼承UnityAppController。
在Plugins/iOS路徑下創(chuàng)建CustomAppController.mm?文件。(文件名必須是 ___AppController,前綴可自選,但不能省略;否則在 Build 項(xiàng)目的時(shí)候,會(huì)被移動(dòng)到錯(cuò)誤的目錄中去。)文件主要代碼如下:
@interface CustomAppController : UnityAppController
@end
IMPL_APP_CONTROLLER_SUBCLASS (CustomAppController)
@implementation CustomAppController
//重寫生命周期函數(shù)
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
[super application:application didFinishLaunchingWithOptions:launchOptions];
//自己的代碼
return YES;
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
[super applicationDidBecomeActive:appliction]
//自己的代碼
}
//其他生命周期函數(shù)
@end
在Build iOS Project時(shí),Unity會(huì)自動(dòng)把該文件復(fù)制到Library文件里,原來的 UnityAppController在Classes文件夾里。
通過IMPL_APP_CONTROLLER_SUBCLASS知道要使用我們定制的 CustomAppController 而不是使用默認(rèn)的 UnityAppController,其定義在UnityAppController.h文件中??梢钥吹狡渥饔檬窃趌oad的時(shí)候修改了AppControllerClassName。也即在加載UnityFramework時(shí)替換名字,這樣在創(chuàng)建UIApplicationMain中創(chuàng)建的是我們新建的AppController。文章來源:http://www.zghlxwxcb.cn/news/detail-548191.html
// Put this into mm file with your subclass implementation
// pass subclass name to define
#define IMPL_APP_CONTROLLER_SUBCLASS(ClassName) \
@interface ClassName(OverrideAppDelegate) \
{ \
} \
+(void)load; \
@end \
@implementation ClassName(OverrideAppDelegate) \
+(void)load \
{ \
extern const char* AppControllerClassName; \
AppControllerClassName = #ClassName; \
} \
@end
【參考】
UnityAppController的定制以及Unity引擎的IL2CPP機(jī) - 簡書文章來源地址http://www.zghlxwxcb.cn/news/detail-548191.html
到了這里,關(guān)于Unity與iOS交互(2)——接入SDK的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!