☰
Unity出海记:小U的跨平台通关秘籍
2026/10/19 7:03:15 网站建设 项目流程

游戏小U在 Unity 里长大,现在要去两个国家定居:
🤖安卓联邦:办事大厅叫Gradle,流程标准化,在哪个城市都能办。
🍎苹果王国:手续由Xcode 工坊办理,规矩严格,只能在王国本土(macOS)办理。


🎭 角色表

角色故事身份真实身份
小U准备出国的主人公你的 Unity 游戏
行李箱场景、贴图、音频level0、sharedassets、resources.assets、.resS
随身器官心脏和大脑引擎原生库libunity.so/libiPhone-lib.a
外语老师把 C# 翻成当地语言IL2CPP(C# → IL → C++)
联络员和当地系统打交道Java 胶水层 / ObjC 胶水层
安卓办事大厅一条龙窗口服务Gradle + AGP
苹果工坊手工精密办理Xcode
护照与签证入境凭证Keystore 签名 / 证书 + 描述文件

第一章:出发前的准备 🎒

1. 收拾行李:序列化

小U先把家当分装进箱子,这部分内容在上一篇已经讲过:

🧳 level0 第一个场景里的家具摆放(GameObject、组件) 🧳 sharedassets0 这个场景用到的物品(贴图、网格、材质) 🧳 resources.assets Resources 文件夹里的东西,不管用不用都带上 📦 .resS / .resource 大件托运:像素、顶点、音频数据

这些行李在两国都一样,是平台无关的。

2. 学外语:IL2CPP 🗣️

小U平时说C#,两国本地的编译器(clang)只认C++。所以出发前要上一门速成课:

C# 源码 → IL 字节码 → IL2CPP 翻译 → C++ 源码 → 当地编译器 → 机器码 母语 速记笔记 外语老师 外语讲稿 当地翻译官 能直接执行
  • 🍎 苹果王国有条法律:禁止临场发挥。运行时不允许生成新的可执行代码(禁止 JIT),所以所有话都得提前写成讲稿,必须用 IL2CPP。
  • 🤖 安卓联邦允许临场发挥(Mono),但 Google Play 要求 64 位(ARM64),而主流 Unity 版本的 Mono 后端只支持 32 位 ARMv7。所以上架时基本也只能用 IL2CPP。

翻译时还会附带一本小词典global-metadata.dat,里面是类名、方法名和字符串,跟行李一起走。

⚠️上课前还会删减讲稿(Managed Stripping):老师会把"看起来没人用"的句子删掉。如果某段代码只通过反射调用,老师看不出来它有用,就可能把它删掉。这时需要用link.xml或[Preserve]标记"这段必须保留"。


第二章:🤖 安卓联邦 · Gradle 办事大厅

2.1 两种办理方式

方式故事实际操作
全程代办Unity 拿着材料替你跑完所有窗口,你直接拿证不勾选Export Project,直接输出 APK/AAB
自己办理Unity 把整套材料交给你,你自己去大厅勾选Export Project,用 Android Studio 打开

两种方式的材料完全相同,区别只在于谁去排队。

2.2 材料袋里有什么:一套"住宅 + 工作室"

ExportedProject/ ├── settings.gradle 📋 登记表:申请人有 launcher 和 unityLibrary 两部分 ├── build.gradle 🏛️ 大厅规章:用哪一版 AGP ├── gradle.properties ⚙️ 办事参数:内存、AndroidX 等 │ ├── launcher/ 🏠 住宅门牌(很薄) │ ├── build.gradle 姓名(applicationId)、证件号(versionCode)、签名 │ └── res/ 门牌照片(图标)、欢迎画面 │ └── unityLibrary/ 🏭 工作室(真正干活的地方) ├── libs/unity-classes.jar 联络员手册 ├── src/main/assets/bin/Data/ 🧳 行李全部放这里 ├── src/main/jniLibs/arm64-v8a/ 🫀 心脏和大脑:libunity.so、libil2cpp.so └── Il2CppOutputProject/ 📜 外语讲稿(较新版本在大厅里现场编译)

为什么住宅和工作室要分开?
因为工作室unityLibrary是一个标准的 Android Library,可以整体搬进别人家。这就是Unity as a Library:一个普通的原生 App,可以在某个页面里嵌入 Unity 内容。

2.3 大厅里的八个窗口 🪟

执行./gradlew :launcher:bundleRelease,排队开始:

🪟1 证件合并(Manifest Merge) 住宅、工作室、每个第三方 SDK 都带着自己的 AndroidManifest, 窗口把它们合成一份。条款冲突时,用 tools:replace / tools:remove 说明以谁为准。 🪟2 制作名片(aapt2) 图标、字符串、布局 → resources.arsc,并发放编号表 R。 🪟3 联络员上岗(javac / kotlinc) Java/Kotlin 胶水代码 → .class 🪟4 精简档案(R8) 删除没人用的类,把长名字改短。 ⚠️ 这个窗口和外语老师一样会"误删":Unity 通过 JNI/反射调用的 Java 类 在它看来没人用 → 删掉 → 运行时 ClassNotFoundException。 解决:在 proguard-user.txt 里写 -keep。 🪟5 方言转换(D8) .class → classes.dex,安卓系统(ART)只认 dex。 🪟6 器官检查(原生库) 编译 IL2CPP 的 C++(如果有),合并 .so,剥离调试符号(strip)。 🪟7 装箱(打包) 行李 + 名片 + dex + .so → APK 或 AAB 🪟8 盖章(对齐 + 签名) 没有 Keystore 签名的包,系统拒绝安装。 ⚠️ Keystore 一旦丢失,同一个应用就无法再发布更新,务必备份。

2.4 海关特别规定:行李不许真空压缩 🧊

APK 本质是个 ZIP。按常理,压缩能省空间,但 Unity 专门叮嘱大厅:

noCompress=['.unity3d','.ress','.resource','.obb','.bundle',...]

原因在于大件托运的取件方式。.resS里的贴图数据,对象中只记录了{offset, size},也就是"第几排、第几格开始,拿多少"。

  • 📦真空压缩:想拿一件衣服,得先拆开整个真空袋。
  • 🧊透明收纳箱(STORED,不压缩):按位置直接拿出来。

而且安卓有个特点:装进手机后,APK 不会被拆开,原样放在/data/app/.../base.apk。小U每次取东西,都是libunity.so通过AAssetManager找到文件在 APK 里的起始偏移,直接 seek 过去读取。

🚨由此产生的经典坑:StreamingAssets里的文件也在这个没拆封的箱子里,所以File.ReadAllBytes读不到。不是路径写错了,而是它根本不是磁盘上的独立文件。应该用UnityWebRequest,或者先复制到persistentDataPath。

2.5 入境方式:APK 还是 AAB?

方式故事说明
APK全家一起搬,不管用不用得上可以直接安装,适合测试和国内渠道
AAB把全部家当交给 Google 物流中心,按每户的情况分装配送只给 ARM64 手机送 ARM64 的 .so,按屏幕密度和语言分发;Google Play 新应用必须使用
Play Asset Delivery必需品随人走,其他的稍后寄、按需寄install-time / fast-follow / on-demand,解决基础包体积上限问题

2.6 写委托书:定制办理流程 ✍️

方式一:自带表格模板
在 Player Settings → Publishing Settings 勾选后,Assets/Plugins/Android/下会出现:

模板对应的材料
mainTemplate.gradle工作室的 build.gradle
launcherTemplate.gradle住宅的 build.gradle
baseProjectTemplate.gradle根 build.gradle
gradleTemplate.properties办事参数
AndroidManifest.xml工作室证件
proguard-user.txt"请勿删除"清单

模板里的**DEPS**、**APPLICATIONID**、**SIGN**是 Unity 要填写的空格,不要删除。

方式二:材料生成后、递交前再改一次

usingSystem.IO;usingUnityEditor.Android;publicclassGradleTweak:IPostGenerateGradleAndroidProject{publicintcallbackOrder=>999;// path 指向 unityLibrary 目录publicvoidOnPostGenerateGradleAndroidProject(stringpath){stringroot=Path.GetFullPath(Path.Combine(path,".."));File.AppendAllText(Path.Combine(root,"gradle.properties"),"\norg.gradle.jvmargs=-Xmx4096m\n");}}

Unity 2023.1+ / Unity 6 提供了AndroidProjectFilesModifier,可以按结构修改 build.gradle 和清单,比拼接字符串更可靠。


第三章:🍎 苹果王国 · Xcode 工坊

3.1 王国的三条规矩 📜

  1. 只能在本土办理:必须在macOS + Xcode上完成。Windows 上的 Unity 只能准备材料(导出工程),拿不到最终的 IPA。
  2. 不提供代办:Unity 对 iOS永远只输出 Xcode 工程,后续手续必须进工坊完成。
  3. 禁止临场发挥:只能用 IL2CPP(第一章讲过)。

3.2 第二次递交材料时:重办还是补办?

选项故事适用场景
Replace作废旧档案,全部重新办CI 自动构建,保证每次结果一致
Append只更新 Unity 负责的部分,保留你手写的批注本地调试,节省时间

⚠️ 在 Xcode 里手动改的设置没有记录,换台电脑就没了。所有修改最终都应该写进后处理脚本。

3.3 工坊里的档案柜

XcodeProject/ ├── Unity-iPhone.xcodeproj 📐 档案总目录 ├── Unity-iPhone.xcworkspace 🗂️ 用了 CocoaPods 后生成,必须打开这个! ├── Podfile 📦 第三方 SDK 订货单 ├── Info.plist 🪪 身份信息:Bundle ID、版本、权限说明 ├── MainApp/main.mm 🚪 入口:启动后加载 UnityFramework ├── Classes/ 🧑‍💼 联络员(ObjC/C++ 胶水层) │ └── UnityAppController.mm 负责启动、切后台、恢复等生命周期 ├── Libraries/ │ ├── libiPhone-lib.a 🫀 引擎本体(静态库) │ └── Plugins/iOS/ 你带来的原生插件 ├── Il2CppOutputProject/ 📜 外语讲稿(成百上千个 .cpp) └── Data/ 🧳 行李 └── Raw/ StreamingAssets

3.4 又见"住宅 + 工作室"

和安卓几乎一模一样:

Target身份内容
Unity-iPhone🏠 住宅(App 本体)main.mm、图标、启动画面、Data 行李
UnityFramework🏭 工作室(Framework)引擎库、IL2CPP 代码、Classes、插件

🚨经典坑:插件的源码和库要加到UnityFramework,而不是 Unity-iPhone,否则链接时会报Undefined symbol。后处理脚本里应该用GetUnityFrameworkTargetGuid()。

3.5 签证三件套 🛂

想进入苹果王国,必须同时持有:

证件故事作用
证书(Certificate)护照证明"开发者是谁"
描述文件(Provisioning Profile)签证规定"这个 App 能装在哪些设备上、用于开发还是上架"
Entitlements工作许可推送、内购、iCloud、Sign in with Apple 等特殊能力

三者之间必须一一对应:Bundle ID 对不上、能力没在开发者后台开通、证书过期,都会导致签名失败。

3.6 办理流程

① 🔥 编译 clang 把 IL2CPP 的 C++ 和胶水层 ObjC++ 编译成 .o 这是最慢的一步,大项目十几分钟很常见 ② 🔗 链接 所有 .o + libiPhone-lib.a → UnityFramework + App 可执行文件 同时剥离没用到的函数 ③ 🛂 签名 使用证书 + 描述文件 + Entitlements ④ 🗃️ 归档 Product → Archive → .xcarchive(包含 App 和 dSYM) ⑤ 📤 导出 选择 App Store / Ad Hoc / Development / Enterprise → IPA ⑥ 🛃 海关 上传 App Store Connect → 人工审核 → 上架

两件不能忘的事:

  • 🗃️dSYM 是行车记录仪:崩溃时手机只记得一串内存地址,有了 dSYM 才能还原成"哪个文件第几行"。每次归档都要保存并上传到崩溃分析平台。
  • 📝使用隐私权限必须说明理由:相机、麦克风、相册、定位,都要在Info.plist里写明用途(如NSCameraUsageDescription)。漏写的话,调用时直接崩溃,审核也会被拒。

3.7 和安卓的一个关键区别:入境后箱子会被打开 📂

iOS 安装时会解压 IPA,Game.app在手机上就是一个普通文件夹。所以在 iOS 上,StreamingAssets(即Game.app/Data/Raw)可以用FileAPI 直接读取(只读)。

同样的代码,iOS 能用,安卓不能用,原因就在这里。

3.8 请一位常驻代理人:PostProcessBuild 🤵

每次重新生成工程都手动改设置太麻烦,写个脚本自动处理:

#ifUNITY_IOSusingSystem.IO;usingUnityEditor;usingUnityEditor.Callbacks;usingUnityEditor.iOS.Xcode;publicstaticclassIOSPostBuild{[PostProcessBuild(999)]publicstaticvoidOnPostprocessBuild(BuildTargettarget,stringpath){if(target!=BuildTarget.iOS)return;// ① 修改工程设置stringprojPath=PBXProject.GetPBXProjectPath(path);varproj=newPBXProject();proj.ReadFromFile(projPath);stringappTarget=proj.GetUnityMainTargetGuid();// 住宅stringfwTarget=proj.GetUnityFrameworkTargetGuid();// 工作室proj.AddFrameworkToProject(fwTarget,"StoreKit.framework",false);proj.SetBuildProperty(appTarget,"ENABLE_BITCODE","NO");proj.SetBuildProperty(fwTarget,"ENABLE_BITCODE","NO");proj.WriteToFile(projPath);// ② 修改身份信息stringplistPath=Path.Combine(path,"Info.plist");varplist=newPlistDocument();plist.ReadFromFile(plistPath);plist.root.SetString("NSCameraUsageDescription","用于扫描二维码");plist.root.SetBoolean("ITSAppUsesNonExemptEncryption",false);plist.WriteToFile(plistPath);// ③ 申请工作许可varcap=newProjectCapabilityManager(projPath,"Unity-iPhone.entitlements",null,appTarget);cap.AddPushNotifications(false);cap.AddInAppPurchase();cap.WriteToFile();}}#endif

3.9 命令行办理(CI 用)

# 1. Unity 准备材料Unity-batchmode-quit-projectPath.-buildTargetiOS\-executeMethodIOSBuilder.Export-logFilebuild.log# 2. 安装第三方依赖(如果使用 CocoaPods)cdXcodeProject&&podinstall&&cd..# 3. 归档xcodebuild-workspaceXcodeProject/Unity-iPhone.xcworkspace\-schemeUnity-iPhone-configurationRelease\-archivePathbuild/Game.xcarchive archive# 4. 导出 IPAxcodebuild-exportArchive-archivePathbuild/Game.xcarchive\-exportOptionsPlistExportOptions.plist-exportPathbuild/ipa

第四章:旅途见闻(常见问题) 🚑

遇到的状况故事里发生了什么怎么解决
安卓调用 SDK 闪退,ClassNotFoundExceptionR8 窗口把 JNI 调用的类当成没用的删了proguard-user.txt加-keep
Manifest merger failed两份证件条款冲突tools:replace/tools:remove
Duplicate class两个 SDK 带了同一个库统一版本或exclude
安卓读不到 StreamingAssets想用 File 去读一个没拆封的箱子UnityWebRequest
反射找不到类型 / AOT 泛型报错外语老师删了讲稿,或没见过这个泛型组合link.xml、[Preserve]、显式调用一次
iOS 报Undefined symbol插件放进了住宅,没放进工作室加到UnityFramework
Xcode 找不到 Pods 里的库打开了.xcodeproj打开.xcworkspace
一调用相机就崩没写隐私权限说明Info.plist补NS...UsageDescription
签名失败护照、签证、工作许可对不上核对 Bundle ID、描述文件和 Capability
崩溃日志全是地址记录仪没保存上传 dSYM / symbols.zip
商店拒收新版本版本号没递增versionCode/CFBundleVersion由 CI 自动递增

尾声:两国对照 🆚

对比项🤖 安卓联邦(Gradle)🍎 苹果王国(Xcode)
在哪办理Win / Mac / Linux 都可以只能在 macOS
Unity 能否代办到底✅ 直接出 APK/AAB❌ 只给 Xcode 工程
住宅 / 工作室launcher/unityLibraryUnity-iPhone/UnityFramework
引擎本体libunity.so(动态库)libiPhone-lib.a(静态库)
语言IL2CPP(上架基本必选)IL2CPP(强制)
证件AndroidManifest.xmlInfo.plist+ Entitlements
签名Keystore证书 + 描述文件
安装后的箱子不拆封,按偏移直接读取拆开,变成普通文件夹
行车记录仪symbols.zipdSYM
定制方式Gradle 模板 /IPostGenerateGradleAndroidProject[PostProcessBuild]+PBXProject
最终证件APK / AAB → Google PlayIPA → App Store 审核

🎬 一句话总结

小U 在 Unity 里收拾好行李(序列化)、学好外语(IL2CPP), 然后把材料交给两国的办事机构: 🤖 Gradle 大厅:合并证件 → 制作名片 → 精简档案 → 转换方言 → 装箱 → 盖章 🍎 Xcode 工坊:编译 → 链接 → 签证三件套 → 归档 → 海关审核

Unity 负责准备材料,Gradle 和 Xcode 负责办理入境。
遇到打包问题时,先判断它发生在哪一段:
是行李和外语出了问题(资源、序列化、IL2CPP,属于 Unity 侧),
还是证件和手续出了问题(清单、依赖、编译链接、签名,属于 Gradle / Xcode 侧)。
定位到哪一段,问题就已经解决了一半。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询