iOS原生接入Unity

因项目中有做与Unity对接相关的需求,完成后在此记录一下。主要记录如何引入、两端如何交互,以及碰到的问题解决。

先说一下原生工程如何引入,目前有两种引入方式

一、项目工程引入

当Unity开发完成后,他们会根据平台导出不同的项目工程文件,iOS就是xcodeproj;

首先,打开你的项目

将Unity工程添加进来workspace来

add完成之后,就会

至此第一步完成,开始编译Unity, Command + B

选中Data文件夹,右侧勾选UnityFramework

找到NativeCallProxy.h文件,改成Public

这里改成UnityFramwork,开始编译(可以在scheme中选择debug和release)

编译成功后,可以在Xcode的DerivedData文件夹下找到,因为此时Unity工程已经被引入到原生项目,所以编译后的文件会在原生项目中

然后就是要到原生项目中引入这个UnityFramework

在原生的项目 找到添加framework的地方

点击加号添加

如果在你的添加的framework中能找到,即成功;之后就可以编译原生项目;

二、编译framework引入

考虑到原生项目需要的就是UnirtyFramework,所以只需要把编译成功的Unityframework导入进来即可;

这种方式好处在于,不影响其他同事,其他人拉取项目,不需要考虑编译Unity工程。

同方式一一样,

点击Data文件夹时右侧勾选framework, 点击NativeCallProxy时右侧选择public ,

配置完成之后,选择Target是Unity-iPhone,然后进行编译

(此时需要选择签名账号,不选会编译报错,bundleID与账号可以与原生工程一样即可)

编译成功后,打开Product文件夹

找到他show in Finder,然后将framework直接拖入到原生项目里去,不过要在导入的framework中检查一下是否导入,然后编译即可。

引入Unity时常见问题就是找不到UnityFrmework,可以编译原生工程,编译后看DerivedData文件夹下是否有UnityFrmework,如果没有,说明没有引入正确。

三、代码部分

可以参考官方的demo : github.com/Unity-Techn...

这里我建了一个管理类统一处理: HCUnityManager

首选在main.m中,保存argc ,argv

objectivec 复制代码
int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    
    NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    [userDefaults setObject:@(argc) forKey:@"argc"];
    [userDefaults setObject:[NSString stringWithFormat:@"%p",argv] forKey:@"argv"];
    [userDefaults synchronize];
    
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

AppDelegate.m中保存launchOptions,除了didFinishLaunchingWithOptions方法外,其他方法需要告诉UnityFramework此时application的状态,否则会出现异常(如退到后台后,Unity页面无响应了等等),

css 复制代码
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
     ...
    [HCUnityManager shareInstance].launchOptions = launchOptions;
    return YES;
}

- (void)applicationWillResignActive:(UIApplication *)application {
    [[[[HCUnityManager shareInstance] unityFramework] appController] applicationWillResignActive: application];
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
    [[[[HCUnityManager shareInstance] unityFramework] appController] applicationDidEnterBackground: application];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
    [[[[HCUnityManager shareInstance] unityFramework] appController] applicationWillEnterForeground: application];
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
    [[[[HCUnityManager shareInstance] unityFramework] appController] applicationDidBecomeActive: application];
}
- (void)applicationWillTerminate:(UIApplication *)application {
    [[[[HCUnityManager shareInstance] unityFramework] appController] applicationWillTerminate: application];
}

在HCUnityManager.h中,包含之前保存的数据,以及可供展示的unityView

objectivec 复制代码
#import <UnityFramework/UnityFramework.h>
#include <UnityFramework/NativeCallProxy.h>

@interface HCUnityManager : NSObject<UnityFrameworkListener,NativeCallsProtocol>
+ (HCUnityManager *)shareInstance;
@property (nonatomic, assign) int gArgc;
@property (nonatomic, assign) char **gArgv;
@property(nonatomic, strong) NSDictionary *launchOptions;
@property (nonatomic, strong) UnityFramework *unityFramework;
///用于展示Unity的View,可添加到自己需要展示页面的控制器上
@property (nonatomic, strong ) UIView *unityView;
///初始化
- (void)initUnity;
///是否已经初始化
- (BOOL)unityIsInitialized;
///发送消息给Unity
- (void)sendMessageToGOWithName:(const char*)goName functionName:(const char*)name message:(const char*)msg;

在HCUnityManager.m中,做一些unity初始化操作,以及原生与Unity交互的方法

objectivec 复制代码
#pragma mark - Unity
//初始化 Unity 加载
- (UnityFramework *)loadUnityFramework {
    NSString* bundlePath = nil;
    bundlePath = [[NSBundle mainBundle] bundlePath];
    bundlePath = [bundlePath stringByAppendingString: @"/Frameworks/UnityFramework.framework"];
    NSBundle* bundle = [NSBundle bundleWithPath: bundlePath];
    if ([bundle isLoaded] == false) {
        [bundle load];
    }
    UnityFramework* ufw = [bundle.principalClass getInstance];
    if (![ufw appController]) {
        // unity is not initialized
        [ufw setExecuteHeader: &_mh_execute_header];
    }
    return ufw;
}


// 判断Unity是否已经初始化
- (BOOL)unityIsInitialized {
  return [self unityFramework] && [[self unityFramework] appController];
}

// 初始化Unity
- (void)initUnity {
  // 判断Unity 是否已经初始化
  if ([self unityIsInitialized]) return;

  // 初始化Unity
  self.unityFramework = [self loadUnityFramework];
  [self.unityFramework setDataBundleId:"com.unity3d.framework"];
  [self.unityFramework registerFrameworkListener:self];

  //用于桥接使用
  [NSClassFromString(@"FrameworkLibAPI") registerAPIforNativeCalls:self];

  NSString *argvStr = [[NSUserDefaults standardUserDefaults] valueForKey:@"argv"];
  char **argv;
  sscanf([argvStr cStringUsingEncoding:NSUTF8StringEncoding], "%p",&argv);
  int argc = [[[NSUserDefaults standardUserDefaults] valueForKey:@"argc"] intValue];
  [self.unityFramework runEmbeddedWithArgc:argc argv:argv appLaunchOpts:self.launchOptions];
  self.unityView = [[[self unityFramework] appController] rootView];
  //隐藏 unity 自己的 window,否则会挡住 UIApplication 的 window
  [[self unityFramework] appController].window.hidden = YES;
}

///展示Unity的view
- (void)showUnityView {
  if (![self unityIsInitialized]){
    NSLog(@"Unity 还未初始化");
  }
  [self.unityFramework showUnityWindow];
}

#pragma mark - UnityFrameworkListener
- (void)unityDidUnload:(NSNotification *)notification {
  [[UIApplication sharedApplication].keyWindow makeKeyAndVisible];
  [[self unityFramework] unregisterFrameworkListener: self];
  self.unityFramework = nil;
}

- (void)unityDidQuit:(NSNotification *)notification {
    NSLog(@"========== %s ============",__func__);
}

- (void)retryRegisterListener {
    [self.unityFramework registerFrameworkListener:self];
}

///原生调用unity方法
- (void)sendMessageToGOWithName:(const char*)goName functionName:(const char*)name message:(const char*)msg {
    [self.unityFramework sendMessageToGOWithName:goName functionName:name message:msg];
}

//MARK: -- NativeCallsProtocol
- (void) showHostMainWindow:(NSString*)color {
    if(![self unityIsInitialized]) {
        NSLog(@"Unity is not initialized, Initialize Unity first");
    } else {
        [self.unityFramework showUnityWindow];
    }
}

///获取token
- (char *)getToken {
    // 获取之前保存的token
    NSString *token = [[HCUserManager sharedInstance] token];
    char *cString = [token cStringUsingEncoding:NSUTF8StringEncoding];
    return cString;
}

总结一下就是:

unity调用原生的方法,统一通过NativeCallsProtocol协议实现;

原生调用unity方法,即统一是[self.unityFramework sendMessageToGOWithName:goName functionName:name message:msg];

NativeCallsProtocol协议里面通常用C编写,unity那边的C#可以调用的到

如果与unity有交互,需要在unity工程内增加代码后再编译引入;

NativeCallsProtocol.h中增加供unity调用的方法,showHostMainWindow是Unity给的,仿照写即可

objectivec 复制代码
#import <Foundation/Foundation.h>
// NativeCallsProtocol defines protocol with methods you want to be called from managed
@protocol NativeCallsProtocol
@required
- (void) showHostMainWindow:(NSString*)color;

// other methods
- (char *)getToken;
@end

NativeCallProxy.mm中加入方法的实现

arduino 复制代码
extern "C" {
 void showHostMainWindow(const char* color) {
        return [api showHostMainWindow:[NSString stringWithUTF8String:color]];
    }

    char* getToken(){
        return strdup([api getToken]);
    }
}

在此补充一下,如果要返回字符串给unity,需要加上strdup函数,否则会报内存问题;

添加方法后,因为HCUnityManager是遵守了NativeCallsProtocol,在HCUnityManager中具体实现即可;

四:存在问题

1.内存问题

当unity初始化完成,我们用自己的控制器加载unityView时,内存会暴增,目前我的是300M;

优化点:UnityFramework 提供了unloadApplicationquitApplicationpause方法

调用unloadApplication,会使内存减少50M左右,还是未能完全回收;重新进入页面时,需要重新init;

调用quitApplication,会使应用直接退出,不建议使用;

调用pause,仅暂停unity页面,无法继续动画,但内存未减少;

2.安装包大小问题,

目前我的增加200M左右,这里只能靠unity开发同事优化;

其他问题待发现后再续写吧。

以上若有错误,欢迎指正。转载请注明出处。
相关推荐
吾名招财4 天前
unity3d入门教程五
游戏引擎·unity3d
吾名招财4 天前
unity3d入门教程六
游戏引擎·unity3d
吾名招财4 天前
unity3d入门教程七
游戏引擎·unity3d
Thomas_YXQ6 天前
Unity3D 实现水体交互详解
开发语言·unity·编辑器·交互·unity3d·游戏开发
Coding小宇8 天前
静下心来我还是可以的-unitywebgl 滑动条增加
经验分享·unity·unity3d
吾名招财9 天前
unity3d入门教程二
游戏·unity3d
Doing1 个月前
Vue3+TS+Threejs封装一个3D动画框架
前端·unity3d·webgl
code梦想佳2 个月前
Unity 桥接 sdk 交互实现
前端·unity3d
Thomas_YXQ2 个月前
Unity3D 性能分析工具原理介绍及代码实现详解
游戏·unity·架构·unity3d·游戏开发
洞窝技术2 个月前
开发Blender全自动烘焙工具的挑战与解决方案
unity3d