在有的应用场景下,需要把 unity 项目导出作为 lib 运行在终端平台上,此文记录把 unity 项目导出到 iOS 平台,打包成 framework 提供给应用使用的过程,以及实践中遇到的问题。

导出方法

项目导出

导出的步骤可以参考官方论坛的一个帖子 https://forum.unity.com/threads/integration-unity-as-a-library-in-native-ios-app.685219/,里边也有一个 demo 工程。

image.png

将平台切换到 iOS ,Build 即可。

image.png

打开项目,可以看到两个 Build Targets,一个用于启动应用,一个用于打包 framework。可以把 framework 名字修改为自己的包名。

项目编译设置

data 目录包含应用程序的序列化资源以及 .NET 程序集(.dll 或 .dat 文件),其形式为完整代码或元数据。将 data 目录的 target membership 由 app 目标设置为 framework 目标。

image.png

若有 iOS Plugin 代码,需要将其头文件暴露为 public,供三方应用使用。

image.png

在 iOS 应用中显示 unity 渲染视图

启动渲染

在初始化 unity 打包成的 framework 时,需要指定框架路径,并通过 setDataBundleId 指定从哪里读取 data 目录。

1
NSString *message = [options toJSONString];
NSString* bundlePath = nil;
bundlePath = [[NSBundle mainBundle] bundlePath];
bundlePath = [bundlePath stringByAppendingString: @"/Frameworks/ivhsdk.framework"];

NSBundle* bundle = [NSBundle bundleWithPath: bundlePath];
if ([bundle isLoaded] == false) [bundle load];
UnityFramework* ufw = [bundle.principalClass getInstance];
[ufw setDataBundleId: "com.tencent.ivhsdk"];
[IvhLibAPI setUfw:ufw];

然后启动渲染

1
[ufw runEmbeddedWithArgc: 1 argv:    (char *[]){""} appLaunchOpts: @{}];

生命周期绑定

在 UIApplicationDelegate 的实现中绑定 unity framework 的生命周期相关的函数。

1
- (void)applicationWillResignActive:(UIApplication *)application { [[[self ufw] appController] applicationWillResignActive: application]; }
- (void)applicationDidEnterBackground:(UIApplication *)application { [[[self ufw] appController] applicationDidEnterBackground: application]; }
- (void)applicationWillEnterForeground:(UIApplication *)application { [[[self ufw] appController] applicationWillEnterForeground: application]; }
- (void)applicationDidBecomeActive:(UIApplication *)application { [[[self ufw] appController] applicationDidBecomeActive: application]; }
- (void)applicationWillTerminate:(UIApplication *)application { [[[self ufw] appController] applicationWillTerminate: application]; }

通信

unity 中调用 iOS 方法

在 unity 项目的 Plugins/iOS 目录下,创建 plugin 需要的 oc文件 xxx.mm,在其中定义 c 函数

1
extern "C" {
    void unityCallIOS(string params){
    }
}

在 unity 的 c# 代码中,通过 [DllImport("__Internal")] 引入方法并调用。

1
[DllImport("__Internal")]
public static extern void unityCallIOS(string params);

在 c# 中调用 unityCallIOS 即可。

iOS 中调用 unity 方法

参考 “启动渲染” 的步骤,初始化并得到 UnityFramework 对象后,通过对象的 sendMessageToGOWithName 方法调用 unity 场景节点上挂载的 MonoBehaviour 组件暴露的方法。

1
[ufw sendMessageToGOWithName: [@"gameobject name" UTF8String] functionName: @"method name" message: [@"params" UTF8String]];

其它问题和注意事项

包名问题引起的 unity ios Thread 1: EXC_BAD_ACCESS

如 “启动渲染” 步骤中的描述,如果没有正确的设置 data path,会报这个错误而 crash 掉。

很奇怪的报错,从堆栈中看不出任何问题,各种试,最后发现 unityframework 设置的包名,必须和 framework打包的包名一致。

image.png

应用播放的声音非常小

导出后发现应用的声音非常小,原因是声音是从听筒播放的而不是扬声器播放的。

image.png

导出时取消勾选 Prepare iOS for Recording 可修复。

无法修改渲染区域的大小和位置

https://docs.unity3d.com/Manual/UnityasaLibrary-iOS.html

官方文档描述,只能全屏。

虽然可以通过 unityFramework.appController.unityView,并修改它的 frame,但是实际上并不会生效,渲染仍然是全屏的。

这里只能考虑暴露一些 unity 内部 camera 的 transform 调整的方法,通过 sendMessageToGOWithName 封装成一些位置大小调整 API 给用户调用。

因为只能全屏,unity view 可能遮挡其它view

需要注意view的加载顺序,如果没有其它需求,可以考虑将 view 放到最下层。

1
// 至少得传一个参数,否则core
//  [[self ufw] runEmbeddedWithArgc: 1 argv:    (char *[]){"ailing"} appLaunchOpts: @{}];
// add other view

透明背景在 iOS 的 view 上会失效

和作为 android library 运行不一样,unity 自己创建的 unityView 会丢失透明的特性,但是通过 window 添加 subview 到 unity 生成的 view 中,这样可以修改渲染的背景,如:

1
UIView* backview = [[UIView alloc] initWithFrame:[UIScreen.mainScreen bounds]];
backview.backgroundColor = [UIColor redColor];
[self.ufw.appController.window addSubview:backview];
[self.ufw.appController.window sendSubviewToBack:backview];

☞ 参与评论