Interaction between Unity and iOS (2) - access to SDK

【Preface】

Accessing the Android and iOS SDKs has many similarities. It is recommended to read how to access the Android SDK first .

 [Detailed explanation of UnityAppController]

The entry of the whole program is in the main.mm file  under the MainApp file . Unityframework is loaded first, and then runUIApplicationMain is called. The source code is as follows: (These source codes are available in the Xcode project)

#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];//调用bunlde加载接口加载,bundle表示一个包含代码、资源和其他文件的目录或者是一个.framework文件

    //UnityFramework类的头文件是UnityFramework.h,实现文件在Classes文件夹中的main.mm
    UnityFramework* ufw = [bundle.principalClass getInstance];//获取bundle主类示例,主类是一个UnityFramework,bundle 只有一个主类,该类通常是应用程序的控制器类
    if (![ufw appController])  //调用appController方法获取,第一次获取的为空                  
    {
        // unity is not initialized
        [ufw setExecuteHeader: &_mh_execute_header];//设置头信息,初始化unity引擎
    }
    return ufw;
}

int main(int argc, char* argv[])//main是整个应用程序的入口,和什么语言没关系,一般都是这样
{
    @autoreleasepool //创建一个自动释放池
    {
        id ufw = UnityFrameworkLoad();//先加载了UnityFramework
        [ufw runUIApplicationMainWithArgc: argc argv: argv];//runUIApplicationMainWithArgc,这个方式是UnityFramework.h中的方法
        return 0;
    }
}

You can look at the functions defined in the UnityFramework.h file

#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

The function is implemented in the main.mm file under the Classes folder

#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];//获取单例时先调用alloc分配内存,再调用init初始化
    }
    return _gUnityFramework;
}

- (UnityAppController*)appController
{
    return GetAppController(); //调用UnityAppController.mm中的方法
}

- (void)setExecuteHeader:(const MachHeader*)header
{
    UnitySetExecuteMachHeader(header);//一个 Unity 引擎的函数,用于设置当前可执行文件的 Mach-O 头信息,header是一个指向可执行文件 Mach-O 头信息的指针
}//在 macOS 或 iOS 上,可执行文件的 Mach-O 头信息包含有关可执行文件的元数据,例如文件类型、CPU 架构、入口点和段信息。Unity 引擎使用该函数来设置当前可执行文件的 Mach-O 头信息,以确保 Unity 引擎可以正确加载并执行游戏逻辑。

- (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应用程序的入口点,一般在入口main函数中调用,unity在frameworkWarmup后调用,其用于启动应用程序并设置应用程序的主运行循环(main run loop)
    //该方法有4个参数,第三个参数是NSString类型的principalClassName,其是应用程序对象所属的类,该类必须继承自UIApplication类,如果所属类字符串的值为nil, UIKit就缺省使用UIApplication类
    //第四个参数是NSString类型的delegateClassName,其是应用程序类的代理类,该函数跟据delegateClassName创建一个delegate对象,并将UIApplication对象中的delegate属性设置为delegate对象,这里创建了UnityAppController,看其头文件是继承了UIApplicationDelegate
    //该函数创建UIApplication对象和AppDelegate对象,然后将控制权交给主运行循环,等待事件的发生。UIApplicationMain还会创建应用程序的主窗口,并将其显示在屏幕上,从而启动应用程序的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

After calling UIApplicationMain to create UnityAppController, you can receive iOS system event notifications, and the processing of receiving notifications is in the UnityAppController.mm file. The following are the events related to the life cycle

First willFinishLaunchingWithOptions , only a notification is sent

- (BOOL)application:(UIApplication*)application willFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
    AppController_SendNotificationWithArg(kUnityWillFinishLaunchingWithOptions, launchOptions);
    return YES;
}

void AppController_SendNotificationWithArg(NSString* name, id arg) //Unity引擎用于在iOS平台上发送消息通知的方法
{
    [[NSNotificationCenter defaultCenter] postNotificationName: name object: GetAppController() userInfo: arg];
}

followed by 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()是一个用于获取Unity数据包目录的方法,它返回一个NSString对象,表示Unity数据包的路径
    //随后初始化Unity应用程序,但不会创建图形界面
    UnityInitApplicationNoGraphics(UnityDataBundleDir());

    [self selectRenderingAPI];//在iOS平台上,Unity引擎通常支持OpenGL ES和Metal两种渲染API,该方法会根据具体的设备和系统版本等因素来动态地选择使用哪种渲染API
    [UnityRenderingView InitializeForAPI: self.renderingAPI];//UnityRenderingView是用于呈现Unity引擎场景的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中的一个视图对象,用于展示应用程序的用户界面.它是一个特殊的视图,通常作为应用程序中所有视图的容器。在iOS中,每个应用程序都有一个主窗口,即UIWindow对象,它是整个应用程序界面的根视图。所有其他的视图都是添加到UIWindow对象中的。
//UIWindow对象还可以响应用户的触摸事件和手势,同Android一样,Untiy会自己渲染视图,但需要使用操作系统提供的触摸事件
    _window = [[UIWindow alloc] initWithFrame: [UIScreen mainScreen].bounds];
//创建UnityRenderingView对象,而不是使用iOS系统的视图对象,如UIView或UIWindow,这个视图对象会放入UIWindow中
    _unityView = [self createUnityView];


    [DisplayManager Initialize];//unity的显示器管理类初始化,unity使用DisplayManager来管理显示器并在多个显示器上呈现Unity场景
    _mainDisplay = [DisplayManager Instance].mainDisplay;//设置主显示器
    [_mainDisplay createWithWindow: _window andView: _unityView];//将iOS的窗口UIWindow和Unity的RenderingView同主显示器关联起来

    [self createUI];//用于创建iOS应用程序界面中的常规视图对象,例如按钮、标签、文本框等
    [self preStartUnity];//预启动,在unity启动前可以注册插件

    // if you wont use keyboard you may comment it out at save some memory
    [KeyboardDelegate Initialize];

    return YES;
}

After that is applicationDidBecomeActive

- (void)applicationDidBecomeActive:(UIApplication*)application
{
    ::printf("-> applicationDidBecomeActive()\n");

    [self removeSnapshotViewController];//删除iOS应用程序窗口中的快照视图控制器
    //在iOS应用程序中,当应用程序进入后台时,系统会自动截取当前应用程序的截图,以便在应用程序再次进入前台时快速还原应用程序的状态。\
    //这个截图被称为快照(Snapshot),而用于管理快照的视图控制器被称为快照视图控制器(Snapshot View Controller)
    //unity自己渲染画面,不需要

    if (_unityAppReady)//从后台切换到前台时
    {
        if (UnityIsPaused() && _wasPausedExternal == false)
        {
            UnityWillResume();//会调用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走这里,调用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
}

Then enter the background and enter the foreground

- (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];
    }
}

And finally 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;
}

【Access SDK】

In general, it is no different from Android. Compared with Android, it is better than Jar, because in essence, C# calls C, and C calls OC. Some required compilations will be compiled when the package is released.

In the same way, it depends on whether the life cycle is needed, and you can write and call it yourself if you don't need the life cycle. If you need a life cycle, you also need to inherit it. In Android, you inherit Activiry, and in iOS, you inherit UnityAppController.

Create a CustomAppController.mm​file under the Plugins/iOS path. (The file name must be ___AppController. The prefix is ​​optional, but cannot be omitted; otherwise, it will be moved to the wrong directory when building the project.) The main code of the file is as follows:



@interface CustomAppController : UnityAppController

@end

IMPL_APP_CONTROLLER_SUBCLASS (CustomAppController)
@implementation CustomAppController


//重写生命周期函数
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
    [super application:application didFinishLaunchingWithOptions:launchOptions];
    //自己的代码
    return YES;
}

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    
    [super applicationDidBecomeActive:appliction]
    //自己的代码
}

//其他生命周期函数

@end

When building iOS Project, Unity will automatically copy this file to the Library file, and the original UnityAppController is in the Classes folder.

Know through IMPL_APP_CONTROLLER_SUBCLASS to use our custom CustomAppController instead of the default UnityAppController, which is defined in the UnityAppController.h file. It can be seen that its function is to modify the AppControllerClassName when loading. That is, replace the name when loading UnityFramework, so that our new AppController is created in creating UIApplicationMain.

// 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   

【reference】

Customization of UnityAppController and IL2CPP machine of Unity engine - short book

Guess you like

Origin blog.csdn.net/enternalstar/article/details/131583408