Unity 侧
ipa 包体构建流程
- 在 Mac 上启动 Unity,在 Unity 的 BuildSettings 界面选择 iOS 平台,点击 Build 按钮后即开始导出 XCode 工程。或调用 Unity 命令行工具完成此步操作。
导出后的 Xcode 工程结构如下图
2.双击 Unity-iPhone.xcodeproj,打开 Xcode,在 Xcode 顶部栏点击 Product/Archive 开始构建档案。或调用 Xcode 命令行工具完成此步操作。
3.在 Xcode 顶部栏点击 Window/Organizer 进行分发,根据出包需求选择不同的证书导出对应 ipa 安装包文件。或调用 Xcode 命令行工具完成此步操作。
最终导出的 ipa 文件和日志文件如下图
总结:对于使用 Unity 开发的游戏,每次打包都相当于重新产生一个 Xcode 工程。当游戏的美术资源,代码文件,配置文件发生变更后,通常需要重新导出一份新的 Xcode 工程。
Unity 工程目录结构简介
不同游戏的 Unity 目录结构不同,其中 Unity 对一些目录做了特殊定义,此类文件夹不能更名,否则 Unity 会识别不到。
以下列举了一个 Unity 游戏工程的大概目录结构(加粗的为 Unity 特殊定义的目录名)
Assets
Library //Unity 针对 Assets 下的文件生成的适合自身读写的中间文件
Packages //存放 Unity Package Manager 扩展包文件
ProjectSettings //存放 Unity 项目设置文件
Temp //存放临时文件,随 Unity 进程关闭被清空
创建桥接文件
1.在 Assets/Plugins/iOS 目录下创建以下文件(文件名称随意)
2.编辑 MyAppController.h(属于各 Unity 项目的通用做法,网上有很多示例)
注意点:
(1)必须继承自 UnityAppController
1 | // |
3.编辑 MyAppController.mm(也属于各 Unity 项目的通用做法,网上有很多示例)
注意点:
(1)最后一行必须写 IMPL_APP_CONTROLLER_SUBCLASS,这是 Unity 提供的宏。Unity 通过这个宏知道要使用我们定制的 MyAppController 而不是使用默认的 UnityAppController。
(2)新建的函数必须在对应的头文件里提前声明
1 | // |
4.编辑 NativeCallProxy.h
1 | // |
5.编辑 NativeCallProxy.mm
注意点:
(1)被 C#调用的代码必须用 extern “C”包围
1 | // |
6.在 Assets/Scripts 目录下创建以下文件(文件名称随意)
SdkBridge_iOS.cs //调用 C 代码的 C#文件
IosMessageReceiver.cs //接收 iOS 代码发送的消息
7.编辑 SdkBridge_iOS.cs
注意点:
(1)invokeMethod 必须用 UNITY_IOS 宏包围,并且与 NativeCallProxy.mm 内的 invokeMethod 完全同名,参数一致(C#的 string 对应 C 的 char*)
(2)invokeMethod 必须加 extern 关键字,并且加[DllImport(“__Internal”)]特性标签
1 | using System; |
8.编辑 IosMessageReceiver.cs
注意点:
(1)Call 方法名必须与 MyAppController.mm 的 Call 函数名一致,参数也必须一致。
(2)IosMessageReceiver.cs 被实例化至 Unity 的场景中的哪个 GameObject 节点下,就要把这个 GameObject 的全路径填到 MyAppController.mm 的 UnitySendMessage 函数的第一个参数内。
1 | using System.Collections; |
总结:
(1)每次 Unity 导出 Xcode 工程后 UnityAppController 都会被复位成模版自动生成的代码,Unity 通过 IMPL_APP_CONTROLLER_SUBCLASS 帮我们解决了想要扩展 UnityAppController 代码的需求。
(2)在 C#侧使用 string invokeMethod(string arg0)可以完成大部分情况下的 C#到 C 的通信,在 C 侧使用 UnitySendMessage 可以实现 C 到 C#的通信。
在 Unity 设置 Xcode 工程参数
可直接在 Unity 编辑器内设置一部分 Xcodeproj 参数,剩下的部分只能通过代码设置。
- Unity 的 ProjectSettings 可以设置项目层面的参数。
- 在 Unity 内选中*.framework 文件,可以设置依赖的其它 framework。
- 选中*.mm 文件,可以填写编译选项
以上是能通过 Unity 编辑器直接设置的部分,接下来是使用代码挂接 Unity 的打包后处理钩子函数实现更多参数注入。
在 Assets/Scripts/Editor 文件夹下创建 XcodeBuildPostprocess.cs(名字随意)
下面的代码示例了如何使用 UnityAPI 对 Xcode 工程进行修改。
注意点:
(1)方法必须声明为 static 类型,且添加 PostProcessBuildAttribute 特性。Unity 会在打 Xcode 工程的尾声阶段(Xcode 工程已被完整导出)调用所有带该特性标签的方法。
(2)该代码文件必须放在 Editor 文件夹下,或者其路径中必须包含 Editor,例如 Assets/Scripts/Editor。
1 | using UnityEngine; |
Xcode 侧
在打 ipa 包的流程中,Xcode 侧无需进行任何 plist,xcodeproj,工程资源文件/代码的修改。
一般在接入 sdk 的过程中,我们仍会使用 Xcode 进行调试,例如在接入某 sdk 时,要求在 plist 中添加参数,那么可以先用 Unity 导出一个 Xcode 工程,在此 Xcode 工程上进行 plist 修改,再打真机包调试,无需重复从 Unity 重新导出工程。
需要注意的是,在 Xcode 调试期间作出的任何 plist 改动,必须转化为 XcodeBuildPostprocess 的代码。在 Xcode 调试期间新增或修改的任何代码,也要拷贝回 Unity 的工程目录下。
总结
从本文可以看出,ipa 包体构建的绝大部分的配置操作都是在 Unity 侧实现的,这也就降低了不熟悉 iOS 开发的 Unity 游戏开发人员打 ipa 包的难度。虽然接入一些特殊功能的 sdk 无可避免地要写 native 端的代码,但通常 sdk 方会提供完整的接入代码甚至工程示例。为了方便管理,将 native 端的代码放在 Unity 工程内利于整体维护和项目组成员 review。在 Xcode 侧进行构建和分发只需要少量的点击操作,这个过程越简单,越方便以命令的形式集成至 CI/CD 流水线。
例如按照本文介绍的方法接入 sdk,从 Unity 构建 Xcode 工程,再到 Xcode 打出 ipa 包只需要依次执行以下命令:
- Unity 打 Xcode 工程
/Applications/Unity/Unity.app/Contents/MacOS/Unity -batchmode -quit -nographics -executeMethod ProjectBuild.ExportXcodeProject
- 归档
xcodebuild archive -archivePath /Users/user/build/mygame -project /Users/user/Documents/build/Unity-iPhone.xcodeproj -scheme Unity-iPhone
- 分发 adhoc
xcodebuild -exportArchive -archivePath /Users/user/build/mygame.xcarchive -exportPath /Users/user/build -exportOptionsPlist /Users/user/User/ExportOptions_adhoc.plist
- 分发 dis
xcodebuild -exportArchive -archivePath /Users/user/build/mygame.xcarchive -exportPath /Users/user/build -exportOptionsPlist /Users/user/User/ExportOptions_appstore.plist
- 清理 Xcode 工程
xcodebuild clean -project /Users/user/Documents/build/Unity-iPhone.xcodeproj -scheme Unity-iPhone
受项目复杂度和计算机性能影响,从 Unity 构建一个 ipa 包花费的时间有很大不同。例如一个普通的中型手游项目 + 高性能打包机,完整的出包时间会在 1 小时左右,其中 Unity 的游戏资源打包花费 30 分钟左右,导出 Xcode 工程花费 10 分钟左右,从 Xcode 构建出最终 ipa 包花费 10 分钟左右,杂项(进程启动,git 操作,日志归档,发送报告…)花费 10 分钟左右。