Unity自动化构建实战:从命令行到脚本实现轻量级CI/CD

Unity自动化构建实战:从命令行到脚本实现轻量级CI/CD
1. 项目概述为什么我们需要一个“简单的构建机”如果你是一个独立开发者或者在一个小团队里负责Unity项目的打包工作可能觉得每次在编辑器里点一下“Build”按钮等上十几分钟甚至更久然后手动把生成的包复制到测试服务器或者上传到分发平台并不是什么大问题。但当你需要每天构建十几个不同分支的版本或者需要在凌晨自动为QA团队生成最新的测试包时这种重复、耗时且容易出错的手动操作就会迅速变成一个效率瓶颈和潜在的风险点。这就是“构建机”要解决的问题。它本质上是一个自动化脚本或程序能够代替人工完成从代码拉取、依赖解析、项目编译、资源打包到最终产物归档、分发的全过程。一个“简单”的构建机核心目标就是把这套流程自动化、标准化把开发者从重复劳动中解放出来并确保每次构建的环境和过程都是一致的从而提升开发效率和构建产物的可靠性。我经历过从手动打包到搭建自动化构建管道的完整过程深知其中的痛点比如本地环境配置不同导致的“在我机器上是好的”问题比如忘记切换场景列表导致测试包缺少关键关卡又比如深夜被叫起来手动打个热更包。基于这些实际需求我将分享如何用最直接、最轻量的方式为你的Unity项目搭建一个可用的构建机。这个方案不依赖复杂昂贵的商业CI/CD服务而是基于命令行和脚本让你能快速上手理解其核心原理并能根据自己项目的实际情况进行定制。2. 构建机核心设计思路与方案选型在动手写代码之前我们需要明确这个“简单构建机”的边界和能力。它不应该试图一开始就做成Jenkins或GitLab CI那样功能庞杂的系统而是聚焦于解决Unity项目构建自动化中最核心、最通用的几个环节。2.1 核心需求拆解一个最小可用的Unity构建机至少需要完成以下任务环境准备确保构建服务器或本地机器上安装了正确版本的Unity Editor以及目标平台所需的SDK如Android SDK/NDK、iOS Xcode。获取最新代码从版本控制系统如Git拉取指定分支的最新代码。执行Unity构建命令这是核心通过命令行调用Unity Editor以“批处理模式”执行构建生成最终的应用程序包APK、IPA、EXE等。处理构建结果将构建产物可能包含多个文件复制到指定的归档目录并可按需进行重命名、版本号注入等操作。通知与日志构建完成后无论成功与否都需要有明确的日志输出并最好能通过某种方式如邮件、钉钉/飞书机器人通知相关人员。2.2 技术方案选型为什么选择命令行 脚本市面上有成熟的CI/CD方案比如Jenkins、GitLab CI/CD、GitHub Actions、Azure DevOps等。它们功能强大有丰富的插件和可视化界面。但对于一个“简单”的构建机来说它们可能显得过于重型学习和配置成本较高。我们的目标是快速实现、易于理解和维护。因此我推荐的核心方案是Unity命令行接口 Shell/Python脚本。Unity命令行Unity Editor本身提供了强大的命令行参数允许我们在无图形界面的情况下执行构建、批量处理资源等操作。这是实现自动化的基石。Shell/Batch脚本在Windows上可以用.bat或.ps1在macOS/Linux上可以用.sh。脚本负责串联整个流程拉代码、调用Unity、处理文件。它的优势是轻量、直接与操作系统结合紧密。Python脚本如果你需要更复杂的逻辑比如解析JSON配置文件、进行HTTP请求发送通知、更精细的文件操作等Python是更佳选择。它跨平台库丰富可读性强。对于大多数中小项目一个精心编写的Shell脚本配合Unity命令行就完全够用了。我们将以这个组合为例进行展开。选择这个方案你不仅能快速搭建起来还能透彻理解自动化构建的每一个步骤未来无论是迁移到更专业的CI平台还是扩展功能都会心中有数。3. 构建环境准备与Unity命令行基础在编写自动化脚本之前我们必须确保构建环境是正确且可重复的。这是避免“构建环境差异”导致问题的关键。3.1 安装与定位Unity Editor构建机上需要安装Unity Hub和指定版本的Unity Editor。不建议直接使用安装程序默认路径最好通过命令行能准确定位到Unity可执行文件。Windows通常路径为C:\Program Files\Unity\Hub\Editor\Version\Editor\Unity.exemacOS通常路径为/Applications/Unity/Hub/Editor/Version/Unity.app/Contents/MacOS/UnityLinux路径类似/opt/unity/Editor/Version/Unity在脚本中我们应该将Unity路径定义为一个变量方便修改和移植。# 示例在Shell脚本中定义Unity路径 (macOS/Linux) UNITY_PATH/Applications/Unity/Hub/Editor/2022.3.20f1/Unity.app/Contents/MacOS/Unity # 示例在Batch脚本中定义Unity路径 (Windows) SET UNITY_PATHC:\Program Files\Unity\Hub\Editor\2022.3.20f1\Editor\Unity.exe注意构建机使用的Unity版本必须与项目设置中指定的版本严格一致。否则可能会遇到API不兼容、资源格式错误等问题。建议在团队内部明确项目使用的Unity LTS版本并在构建机上固定安装此版本。3.2 理解核心Unity命令行参数Unity命令行的魔力在于一系列参数。以下是构建机最常用的几个-batchmode这是最重要的参数。它让Unity以批处理模式运行不显示图形界面不弹出对话框执行完毕后自动关闭。这是自动化构建的前提。-quit在执行完其他命令后退出Unity编辑器。通常与-batchmode联用。-projectPath path指定要打开的Unity项目路径。必须是绝对路径。-executeMethod ClassName.MethodName指定一个在编辑器模式下执行的静态方法。这是我们构建逻辑的入口点。-buildTarget target指定构建目标平台例如Android,iOS,StandaloneWindows64,WebGL等。-logFile path将Unity的日志输出到指定文件。对于排查构建失败原因至关重要。如果不指定默认输出到控制台。一个最简单的构建命令看起来像这样${UNITY_PATH} -batchmode -quit -projectPath /path/to/your/project -executeMethod BuildScript.PerformBuild -logFile build.log这条命令会以无界面模式打开指定项目执行BuildScript类中的PerformBuild静态方法然后将日志写入build.log文件最后退出。3.3 目标平台SDK配置Android需要安装JDK注意Unity版本对JDK版本有要求如2022.3通常需要JDK 11-17、Android SDK和NDK。环境变量JAVA_HOME,ANDROID_SDK_ROOT,ANDROID_NDK_HOME必须正确设置。Unity安装时通常会捆绑安装这些但构建机上最好独立安装并确认版本兼容性。iOS需要在macOS系统上安装Xcode及命令行工具。构建过程会调用xcodebuild。其他平台如Windows、Linux、WebGL通常Unity自带运行时无需额外配置。实操心得建议将构建环境包括Unity版本、SDK版本、环境变量配置通过Docker容器或虚拟机镜像进行固化。这样可以在任何机器上快速复现完全一致的构建环境是走向“持续集成”的重要一步。对于小型团队至少应该有一份详细的《构建环境配置手册》。4. 编写核心构建脚本C#编辑器脚本Unity命令行通过-executeMethod调用的方法需要写在一个编辑器脚本中。这个脚本需要放在项目的Assets/Editor目录下或其子目录。4.1 构建脚本基本结构让我们创建一个BuildScript.cs文件。using UnityEditor; using UnityEngine; using System.IO; using System.Linq; public static class BuildScript { // 这是一个可以被命令行调用的方法 public static void PerformBuild() { // 1. 获取命令行参数可选 string[] args System.Environment.GetCommandLineArgs(); string buildTargetStr GetArgument(args, buildTarget); string outputPath GetArgument(args, outputPath); // 2. 设置构建选项 BuildPlayerOptions buildOptions new BuildPlayerOptions(); // 2.1 设置需要构建的场景 // 注意这里获取的是在Build Settings窗口中勾选的场景这是最可靠的方式。 buildOptions.scenes EditorBuildSettings.scenes .Where(s s.enabled) .Select(s s.path) .ToArray(); if (buildOptions.scenes.Length 0) { Debug.LogError(No scenes added to Build Settings!); EditorApplication.Exit(1); // 非零退出码表示失败 return; } // 2.2 设置构建目标 BuildTarget target BuildTarget.NoTarget; if (!System.Enum.TryParse(buildTargetStr, true, out target)) { // 如果命令行没指定可以设置一个默认值比如打Android包 target BuildTarget.Android; } buildOptions.target target; // 2.3 设置输出路径 if (string.IsNullOrEmpty(outputPath)) { // 生成一个带时间戳的默认路径 string projectName PlayerSettings.productName.Replace( , _); string dateTime System.DateTime.Now.ToString(yyyyMMdd_HHmmss); outputPath Path.Combine(Builds, target.ToString(), ${projectName}_{dateTime}); } // 确保输出目录存在 Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); buildOptions.locationPathName outputPath; // 2.4 设置其他选项 buildOptions.options BuildOptions.None; // 正式发布 // buildOptions.options BuildOptions.Development | BuildOptions.AllowDebugging; // 开发调试包 // 3. 执行构建 BuildReport report BuildPipeline.BuildPlayer(buildOptions); // 4. 处理构建结果 if (report.summary.result BuildResult.Succeeded) { Debug.Log($Build succeeded! Output path: {outputPath}); Debug.Log($Build size: {report.summary.totalSize / (1024 * 1024):F2} MB); EditorApplication.Exit(0); // 退出码0表示成功 } else { Debug.LogError($Build failed with {report.summary.totalErrors} error(s).); // 错误信息已经在之前的日志中打印了 EditorApplication.Exit(1); } } // 一个简单的辅助函数用于从命令行参数中提取值 // 例如-buildTarget Android -outputPath “C:\Builds” private static string GetArgument(string[] args, string name) { for (int i 0; i args.Length; i) { if (args[i] $-{name} i 1 args.Length) { return args[i 1]; } } return null; } }4.2 关键点解析与注意事项场景列表来源代码中通过EditorBuildSettings.scenes获取场景列表。这是最佳实践。它直接读取你在Unity编辑器菜单File - Build Settings中勾选的场景顺序。这确保了自动化构建和你在编辑器里手动构建的内容完全一致。永远不要在脚本里硬编码场景路径。输出路径管理脚本中包含了简单的路径生成逻辑。在实际使用中你可能希望从命令行参数-outputPath传入或者根据公司规范生成固定的目录结构如Builds/Android/Prod/。构建选项BuildOptions枚举非常重要。BuildOptions.None用于发布正式包。BuildOptions.Development生成开发包包含调试符号允许连接Profiler和Debugger。BuildOptions.AllowDebugging允许脚本调试。BuildOptions.CompressWithLz4HC使用LZ4HC高压缩率压缩资源能有效减小包体。 你可以根据构建目的测试/发布进行组合。退出码EditorApplication.Exit(0或1)是告诉调用脚本如Shell构建成功与否的关键。0代表成功非0代表失败。外层的Shell脚本可以根据这个退出码决定后续操作如发送成功通知或失败告警。踩坑记录曾经遇到过构建脚本在本地运行成功但在构建机上失败的情况。排查后发现是因为构建机上的项目Build Settings里一个场景都没勾选。因此脚本开头对场景数量的检查非常必要。另外确保Editor脚本中没有使用任何仅在编辑器运行时才存在的API如某些GUI调用因为在批处理模式下这些API可能行为异常。5. 外层驱动脚本串联整个自动化流程有了核心的C#构建脚本我们还需要一个外层的“驱动”脚本来组织整个流程。这个脚本可以用Shellbash/powershell或Python来写。这里以Bash脚本为例因为它非常直观。5.1 一个完整的Bash构建脚本示例假设我们的项目在/home/ci/unity-project 我们要构建Android版本。#!/bin/bash # 文件名build_android.sh # 严格模式遇到错误即停止 set -e echo Unity项目自动化构建开始 echo 时间: $(date) # 1. 定义变量 PROJECT_PATH/home/ci/unity-project UNITY_PATH/Applications/Unity/Hub/Editor/2022.3.20f1/Unity.app/Contents/MacOS/Unity BUILD_TARGETAndroid # 构建产物输出到带时间戳的目录 OUTPUT_DIR${PROJECT_PATH}/Builds/Android/$(date %Y%m%d_%H%M%S) LOG_FILE${OUTPUT_DIR}/build.log echo 项目路径: ${PROJECT_PATH} echo Unity路径: ${UNITY_PATH} echo 构建目标: ${BUILD_TARGET} echo 输出目录: ${OUTPUT_DIR} # 2. 创建输出目录 mkdir -p ${OUTPUT_DIR} # 3. 执行Unity构建命令 echo 开始执行Unity构建... ${UNITY_PATH} \ -batchmode \ -quit \ -projectPath ${PROJECT_PATH} \ -executeMethod BuildScript.PerformBuild \ -buildTarget ${BUILD_TARGET} \ -outputPath ${OUTPUT_DIR}/MyGame.apk \ -logFile ${LOG_FILE} \ -nographics # 对于无显示服务器的环境如纯命令行Linux可能需要此参数 # 检查Unity进程退出码 UNITY_EXIT_CODE$? echo Unity进程退出码: ${UNITY_EXIT_CODE} # 4. 检查构建结果 if [ ${UNITY_EXIT_CODE} -eq 0 ]; then echo ✅ 构建成功 echo 构建日志已保存至: ${LOG_FILE} echo APK文件位于: ${OUTPUT_DIR} # 5. 成功后的操作可选 # 例如复制APK到共享目录、上传到内网测试平台、发送成功通知等 # cp ${OUTPUT_DIR}/MyGame.apk /shared_drive/latest_test.apk # send_notification success Android构建成功 else echo ❌ 构建失败退出码: ${UNITY_EXIT_CODE} echo 构建日志最后50行 tail -n 50 ${LOG_FILE} echo 日志结束 # 6. 失败后的操作可选 # 例如发送失败告警邮件/消息包含错误日志片段 # send_notification failure Android构建失败请查看日志: ${LOG_FILE} exit 1 # 整个脚本以失败状态退出 fi echo 构建流程结束 5.2 脚本关键环节详解set -e这是一个好习惯。它使得脚本中任何一条命令失败返回非零状态时整个脚本立即停止。这能防止错误被忽略导致后续操作在错误的状态下进行。变量定义将所有路径、版本号等配置抽成变量放在脚本开头便于管理和修改。目录创建在调用Unity之前先创建好输出目录。避免Unity因路径不存在而报错。Unity命令行参数-nographics在Linux服务器等没有图形界面的环境下是必须的否则Unity可能会启动失败。我们将-outputPath参数传递给了C#脚本。C#脚本中的GetArgument函数会解析它。-logFile指定了日志路径。一定要保存日志这是事后排查问题的唯一依据。退出码检查$?获取上一条命令Unity进程的退出码。我们根据C#脚本中EditorApplication.Exit()传入的值来判断构建成功与否。日志处理构建失败时脚本使用tail -n 50打印日志的最后50行。通常错误信息就在末尾。你也可以将整个日志文件作为附件发送。5.3 扩展集成版本控制Git一个完整的构建机通常需要从代码仓库拉取最新代码。我们可以在执行Unity构建前加入Git操作。# 在Bash脚本中Unity构建命令之前加入 BRANCHdevelop # 或通过参数传入 echo 切换到分支: ${BRANCH} cd ${PROJECT_PATH} git fetch origin git checkout ${BRANCH} git pull origin ${BRANCH} echo 最新提交: $(git log -1 --oneline) # 可选获取本次构建的提交哈希用于标记版本 COMMIT_HASH$(git rev-parse --short HEAD) # 可以将这个哈希值通过某种方式如写入一个文本文件包含到构建产物中 echo ${COMMIT_HASH} ${OUTPUT_DIR}/build_info.txt注意事项确保构建机上的Git用户有仓库的拉取权限并且配置好了SSH密钥或账号密码。对于需要认证的私有仓库这是常见的配置点。6. 高级功能与定制化基础构建流程跑通后可以根据项目需求添加更多功能。6.1 版本号自动递增在打包时自动递增版本号如1.0.123中的123是常见需求。这可以通过修改PlayerSettings来实现。在BuildScript.cs中可以在构建前添加如下代码[MenuItem(Build/Increment Version)] public static void IncrementVersionAndBuild() { // 读取当前版本号例如 1.0.123 string currentVersion PlayerSettings.bundleVersion; // 简单的递增逻辑假设版本格式为 Major.Minor.Build string[] parts currentVersion.Split(.); if (parts.Length 3 int.TryParse(parts[2], out int buildNumber)) { buildNumber; string newVersion ${parts[0]}.{parts[1]}.{buildNumber}; PlayerSettings.bundleVersion newVersion; PlayerSettings.Android.bundleVersionCode buildNumber; // Android版本码也递增 // iOS的CFBundleVersion也需要设置但更复杂涉及Info.plist Debug.Log($Version updated to: {newVersion}); // 保存项目设置 AssetDatabase.SaveAssets(); } else { Debug.LogWarning($Could not parse version: {currentVersion}. Using default.); } // 然后调用构建方法 PerformBuild(); }你可以修改这个函数或者从外部文件如version.txt读取和写入版本号实现更复杂的版本管理策略。6.2 多平台构建与参数化我们的脚本目前写死了构建Android。我们可以改造它使其能通过命令行参数构建任意平台。修改Bash脚本从命令行接收参数#!/bin/bash # build.sh # 用法./build.sh -p Android -b develop while getopts p:b: opt; do case $opt in p) BUILD_TARGET$OPTARG ;; b) GIT_BRANCH$OPTARG ;; ?) echo Usage: $0 -p Platform -b Branch; exit 1 ;; esac done # 设置默认值 BUILD_TARGET${BUILD_TARGET:-Android} GIT_BRANCH${GIT_BRANCH:-main} # ... 后续流程使用这些变量然后在调用Unity时将-buildTarget ${BUILD_TARGET}参数传入即可。6.3 构建后处理自动上传与通知构建成功后的APK/IPA需要分发给测试人员。可以集成简单的文件上传和消息通知。上传到内网服务器/FTP使用scp,rsync或curl命令。# 示例使用scp上传到测试服务器 REMOTE_USERtester REMOTE_HOSTtest-server.com REMOTE_PATH/var/www/html/downloads/latest.apk scp ${OUTPUT_DIR}/MyGame.apk ${REMOTE_USER}${REMOTE_HOST}:${REMOTE_PATH}发送通知到钉钉/飞书/企业微信这些平台都提供了Webhook接口。可以使用curl发送一个HTTP POST请求。# 示例发送钉钉机器人通知 WEBHOOK_URLhttps://oapi.dingtalk.com/robot/send?access_tokenYOUR_TOKEN JSON_DATA$(cat EOF { msgtype: text, text: { content: Unity构建通知项目${PROJECT_NAME} ${BUILD_TARGET}平台构建${BUILD_RESULT}\n版本${NEW_VERSION}\n提交${COMMIT_HASH}\n日志${LOG_URL} } } EOF ) curl -H Content-Type: application/json -X POST -d ${JSON_DATA} ${WEBHOOK_URL}将${BUILD_RESULT}成功/失败、版本号、提交哈希等信息填入消息内容。7. 常见问题排查与实战技巧即使脚本写得再完善在实际运行中也会遇到各种问题。这里记录一些典型的“坑”和解决方法。7.1 Unity构建失败常见原因速查表现象/错误信息可能原因排查步骤与解决方案UnityException: BuildPipeline.BuildPlayer:...后跟模糊错误项目配置问题场景问题脚本编译错误。1. 查看详细日志这是最重要的。检查-logFile指定的文件搜索error或Exception。2. 检查场景确认Build Settings中至少有一个场景被勾选且路径有效。3. 在编辑器中试构建先在Unity编辑器里手动构建一次看是否有错误提示。构建成功但APK安装后闪退缺少依赖库如Android ARM64支持脚本存在运行时错误资源加载失败。1. 检查Player Settings确保目标架构如ARMv7, ARM64已勾选。2. 打开发布包使用BuildOptions.Development和BuildOptions.AllowDebugging选项构建一个开发包通过ADB或Xcode查看运行时日志。3. 检查资源确认所有动态加载的资源如AssetBundle路径正确且已打包。批处理模式卡住不退出脚本中有弹出窗口如EditorUtility.DisplayDialog或存在无限循环。1. 避免交互确保在Editor脚本中批处理模式下绝不使用任何会弹出对话框的API。2. 超时处理在外层Shell脚本中设置超时例如使用timeout命令timeout 1800s unity_command设置30分钟超时。CommandNotFoundException: unityUnity可执行文件路径错误或没有执行权限。1. 检查路径使用ls -la “${UNITY_PATH}”确认文件存在。2. 赋予执行权限chmod x “${UNITY_PATH}”(Unix系统)。3. 使用绝对路径。Android构建失败提示JDK、SDK找不到环境变量未正确设置或Unity版本与JDK版本不兼容。1. 确认环境变量在构建机上执行echo $JAVA_HOME,echo $ANDROID_SDK_ROOT。2. 指定路径可以在Unity安装目录下的Editor/Data/PlaybackEngines/AndroidPlayer找到SDK,NDK,JDK目录并在Unity命令行中通过-androidSdkPath,-androidNdkPath,-androidJavaSdkPath参数指定。这是最可靠的方式。构建产物巨大未启用压缩包含了不必要的资源如高清纹理所有Mipmap。1. 检查压缩设置在BuildPlayerOptions.options中加入BuildOptions.CompressWithLz4HC。2. 检查Player Settings在Player Settings - Other Settings中可以关闭Prebake Collision Meshes如果不需服务器物理调整Strip Engine Code等。3. 使用Asset Bundle将资源分离动态下载。7.2 实战心得与技巧日志是你的生命线一定要保存并定期查看构建日志。建议将每次构建的日志文件都归档文件名包含时间戳和构建结果成功/失败。可以使用tee命令同时输出到屏幕和文件unity_command 21 | tee “${LOG_FILE}”。从小处着手逐步迭代不要试图一次性实现一个功能完备的构建机。先实现最基本的命令行构建成功。然后加上Git拉取代码。再加上版本号管理。最后处理通知和上传。每完成一步都进行测试。使用版本控制管理构建脚本你的构建脚本本身也应该放入Git仓库。这样构建逻辑的变更可以被追踪和回滚。准备一个“干净”的构建代理如果条件允许使用一台独立的、只安装必要软件的机器或Docker容器作为构建机。避免因本地开发环境安装的各种插件、工具影响构建的稳定性和可重复性。处理Unity许可证无图形界面的服务器上运行Unity需要激活许可证。可以使用-manualLicenseFile参数指定一个已有的许可证文件或者使用-batchmode -quit -logFile -createManualActivationFile生成许可证请求文件然后在有图形界面的机器上激活后将许可证文件拷贝回来。对于团队考虑使用浮动许可证服务器。性能考量Unity项目首次构建会很慢因为要导入和预处理所有资源。可以利用Unity的BuildOptions.AcceptExternalModificationsToPlayer选项进行增量构建但需谨慎有时会引入奇怪问题。更稳妥的方式是定期清理Library文件夹进行全量构建以保证一致性。搭建一个简单的Unity构建机核心在于理解“自动化”的本质用确定的脚本代替不确定的人工操作。通过本文拆解的步骤——从环境准备、命令行参数理解到核心C#构建脚本、外层驱动脚本的编写再到高级功能的扩展和问题排查——你应该已经掌握了搭建一个满足基本需求的自动化构建流程的能力。这个流程将成为你项目研发的基础设施它的稳定运行意味着更快的迭代速度、更少的低级错误和更高的团队协作效率。

最新新闻

日新闻

周新闻

月新闻