Unity项目Git版本管理实战:忽略规则、LFS、冲突合并与自动化构建 接手别人的 Unity 工程我第一件事往往不是打开场景而是先看它的版本库。这个习惯是被坑出来的凡是仓库里直接躺着 Library 目录的项目几乎没有一个是干净的——有人 clone 下来二十几个 G有人一拉代码就冲突有人只改了一个材质参数结果整个场景文件的 diff 全红。使用 GitHub 托管 Unity 项目难点从来不是git init和git push这两条命令而是要先想明白一件事一个 Unity 工程里到底哪些是资产哪些只是这台机器上的临时缓存。这篇内容讲的就是这一整套东西从编辑器里那几个必须改的开关到.gitignore与.gitattributes怎么逐行取舍从 Git LFS 该圈哪些文件、不该圈哪些文件到场景与预制体冲突为什么几乎没法手工解决、Smart Merge 又该怎么配再到分支模型、提交规范、自动化构建和一堆踩过的坑。适合两类人看刚把项目从压缩包传来传去迁到版本库的新手以及团队已经用了 Git 但每次合并都想砸键盘的老手。基础命令我默认你会不会的地方我也顺手提一句。1. 先搞清楚:Unity 工程目录里哪些东西真的该进版本库1.1 从 Library 目录说起,为什么它是编译缓存而不是代码Unity 的 Library 目录里存的是资源导入后的中间产物贴图被压成平台格式、模型被拆成网格与骨架、场景被序列化成内部结构还有一整套 GUID 到实际资源的索引表。这些东西全部可以从 Assets 和 ProjectSettings 重新生成属于派生物就像 C# 项目里的 bin 和 obj。把它提交上去的代价极其直接一是仓库体积按周膨胀二是每次不同机器上的导入结果都可能不同三是团队里两个人同时改一个场景时Library 里的缓存文件会先打起来而你根本不知道冲突的那几百行二进制是什么。我在一个外包项目里见过一个极端案例仓库 3.2G其中 2.9G 是 Library实际资产不到 200M。后来清理完重新推上去clone 时间从四十多分钟降到两分钟。这不是优化体验级别的收益而是能不能正常协作级别的差异。同样该被排除的还有几个常被忽略的目录目录 / 文件性质处理方式Library/导入缓存、GUID 索引忽略,可随时重建Temp/、Obj/编译中间产物忽略Logs/编辑器日志忽略UserSettings/每个开发者本机的窗口布局、偏好忽略Build/、Builds/构建输出忽略,产物走发布页MemoryCaptures/、Recordings/性能抓取与录制忽略Packages/包清单(manifest)必须提交ProjectSettings/项目级设置必须提交Assets/全部资产与脚本必须提交,含 .meta表格最后一行加粗的部分是新手最容易搞错的地方Unity 工程真正的源码其实只有 Assets、ProjectSettings、Packages 三块。其余的都可以在一次 Open Project 里原地复活。1.2 .meta 文件:Unity 用 GUID 认人,不是用路径.meta文件这件事必须单独说因为它是 Unity 接管 Git 之后最反直觉的一点。Unity 给每一个资产分配一个 GUID场景、预制体、材质里引用别的资产时记录的是 GUID 而不是路径。而 GUID 存在哪儿就存在同名的.meta文件里。这意味着两件事。第一.meta必须和资产一起提交缺一个就等于告诉 Unity这个资产是新来的于是所有引用它的地方一起变成 Missing。第二重命名或移动资产必须用编辑器里做或者至少保证.meta跟着一起移动在资源管理器里直接拖拽、用命令行mv却没带上 meta后果就是引用全断——而且这种断法有时候当时看不出来等到打包才报一堆空引用。我见过最隐蔽的一次事故一位同事在系统文件管理器里把一个贴图文件夹改了名字.meta没跟着走。编辑器里看着一切正常因为项目里还残留着缓存结果第二天 CI 拉干净环境构建UI 里三十多个图标全变白块。查了两个小时才定位到是两个字母的目录名差异。提示在 Unity 的 Preferences 里把版本控制模式设为可见 Meta 文件让.meta在工程面板里显式显示出来。眼不见心不烦的结果往往是出事时找不到原因。1.3 用文本序列化换取可读 diff,这是整套方案的地基Unity 默认允许把场景和预制体序列化成二进制。二进制对 Git 来说是黑盒改一个数字整个文件都变diff 没有任何信息量合并更不可能。所以只要涉及多人协作这一步必须先改在 Edit → Project Settings → Editor 里把Asset Serialization改成Force Text把Version Control Mode改成Visible Meta Files。改完 Unity 会提示重新序列化让它跑完然后提交一次全量变更——这次提交的 diff 会非常大但它是一次性的之后所有的场景与预制体都变成 YAML 文本至少能看出谁把哪个组件的哪个字段改了。这一步没有商量的余地。我试过在二进制模式下强行协作最后的解决办法是谁要改场景先群里喊一声其他人不准开效率低到不如用 U 盘拷。文本化之后才有了后面所有讨论的基础Smart Merge 能工作、代码评审能看清、出问题能git diff定位。1.4 整体方案长什么样:一个可以照抄的决策框架把这套东西摊开思路其实是一条主线把能自动重建的全部排除把人手工做的全部纳入把体积大且难合并的交给专门的大文件通道把容易冲突的用配置和流程提前化解。文件类型举例交给谁管理由脚本、配置、Shader.cs.json.shader普通 Git纯文本,需要逐行 diff 与合并场景、预制体、材质.unity.prefab.mat普通 Git Smart Merge文本化后可合并,但需专用工具美术源文件、音视频.psd.fbx.wav.mp4Git LFS体积大、二进制、几乎不需要合并导入缓存、IDE 配置Library/.csproj忽略本机生成,无共享价值构建产物.apk.exe发布页 / 制品库与源码版本解耦按照这张表落地一个中型 Unity 项目几十个场景、几千个资产的仓库可以稳定控制在几百兆这个量级日常提交的 diff 保持可读合并冲突从每天几次降到一周一两次且能解决。2. 开库第一步:编辑器设置与 .gitignore 的逐行取舍2.1 仓库初始化的顺序,别搞反我建议的顺序是先在 Unity 里改好序列化模式和 Meta 可见然后关掉编辑器再git init接着把.gitignore和.gitattributes写好最后才第一次git add。顺序反过来也能救但要多敲几条git rm -r --cached而且容易漏。# 在 Unity 工程根目录有 Assets 和 ProjectSettings 的那一层 git init # 先把忽略规则和属性规则写好再添加文件 curl -o .gitignore https://raw.githubusercontent.com/github/gitignore/main/Unity.gitignore # 上面这条只是拉官方模板后面我们还要手改 git add .gitignore .gitattributes git commit -m chore: 初始化版本库与忽略规则 git add . git status最后那句git status是关键的自检动作。正常的结果应该是几百个.cs、.meta、.unity、.asset文件加上ProjectSettings/、Packages/下的 json。如果列表里出现了Library/xxx或者.csproj说明忽略规则还没生效先别 commit。2.2 .gitignore 里每一段在防什么官方模板已经能覆盖八成场景但它是给所有 Unity 项目通用的实际用起来我一般会做几处调整。下面是我常用的版本附上每段的意图# ---------- Unity 自动生成 ---------- /[Ll]ibrary/ /[Tt]emp/ /[Oo]bj/ /[Bb]uild/ /[Bb]uilds/ /[Ll]ogs/ /[Uu]ser[Ss]ettings/ /[Mm]emoryCaptures/ /[Rr]ecordings/ # ---------- IDE 与工程文件 ---------- .vs/ .idea/ .vscode/ *.csproj *.unityproj *.sln *.suo *.user *.userprefs *.pidb *.booproj *.svd *.pdb *.mdb *.opendb *.VC.db # ---------- 构建与导出产物 ---------- *.apk *.aab *.ipa *.unitypackage *.app /[Ee]xported[Oo]bj/ crashlytics-build.properties sysinfo.txt # ---------- 系统与临时文件 ---------- .DS_Store Thumbs.db *~ *.tmp几点值得解释。[Ll]ibrary 这种大小写双写是历史遗留早期不同平台上 Unity 生成的目录名大小写不一致虽然现在基本统一成小写但写上不亏成本是零。.csproj和.sln必须忽略因为它们由 Unity 按当前机器上的包和插件动态生成A 机器提交上去B 机器拉下来会出现一堆幽灵引用错误——这类报错特别迷惑人看着像代码问题其实是工程文件不匹配。*.unitypackage也建议忽略那是一次性导入包不该长期躺在源码库。至于.vscode/如果团队里确实共享调试配置比如 launch.json 里定义了附加到编辑器的配置可以改成忽略.vscode/*但保留!.vscode/launch.json这个取舍看团队习惯我倾向于共享一份省得每个人自己配。2.3 已经提交了垃圾目录,怎么干净地救回来这是最高频的问题项目跑了半年才发现 Library 被提交了。很多人第一反应是删掉 Library 再提交结果只是新增了一次删除记录历史里的那几个 G 依然在clone 依旧慢。正确的做法分两步。第一步让文件脱离跟踪但保留在磁盘上git rm -r --cached Library Temp Obj Logs UserSettings git rm --cached -r *.csproj git commit -m chore: 移除误提交的生成目录第二步是重写历史把大文件从所有提交里彻底抹掉。这一步是破坏性的必须全组同步、所有人重新 clone不能只是 pull。工具上社区有成熟的仓库重写方案具体选哪个不重要重要的是流程注意重写历史前先在群里通知并让所有人把手上的分支推上去。重写完成后每个人必须重新 clone 一份不要试图在旧仓库上 pull否则会把旧的大对象再合并回来等于白干。做完之后可以用git count-objects -vH看体积正常会看到 size-pack 掉到原来的几分之一。我一般还会顺手跑一次git gc --aggressive --prunenow虽然慢但效果实在。3. Git LFS:大文件走专用通道,但别什么都往里塞3.1 判断一个文件该不该进 LFS 的三个问题LFS 的原理是把大文件的实体存到单独的存储区仓库里只留一个几百字节的指针文件。它的好处很明显clone 变快、普通 diff 不受干扰、仓库体积可控。但它不是万能的判断标准我总结成三个问题第一这个文件是二进制吗如果是纯文本进 LFS 反而有害因为你失去了逐行 diff 和自动合并的能力。第二它的体积会显著增长吗一张 200K 的 png 图标进不进 LFS 都无所谓一张 80M 的 psd 分层图必须进。第三它需要被合并吗场景、预制体、材质虽然是文本但它们是协作热点必须留在普通 Git 里配合 Smart Merge 处理冲突。把.unity塞进 LFS 是新手常见的反向操作后果是所有场景冲突从能救变成完全没救。3.2 .gitattributes 的实际写法与几处反直觉# 让 Git 正确识别文本文件统一换行处理 * textauto # 明确指定文本类型diff 才好看 *.cs text diffcsharp *.shader text *.cginc text *.hlsl text *.json text *.xml text *.md text *.txt text # Unity 的协作热点保留在普通 Git指定专用合并驱动 *.unity mergeunityyamlmerge *.prefab mergeunityyamlmerge *.asset mergeunityyamlmerge # 大体积二进制资产交给 LFS *.psd filterlfs difflfs mergelfs -text *.tga filterlfs difflfs mergelfs -text *.png filterlfs difflfs mergelfs -text *.jpg filterlfs difflfs mergelfs -text *.fbx filterlfs difflfs mergelfs -text *.wav filterlfs difflfs mergelfs -text *.mp3 filterlfs difflfs mergelfs -text *.mp4 filterlfs difflfs mergelfs -text *.zip filterlfs difflfs mergelfs -text几个细节值得说。*.png进 LFS 这件事在小项目里其实可以省因为 UI 图集动辄几百张小图全部走 LFS 会让git lfs ls-files长到没法看而且每张图都要走一次网络请求clone 时反而更慢。我的经验线是单文件超过 2M 的贴图走 LFS小图标走普通 Git。但这条线在团队里必须统一不能各写各的否则同一张图在不同分支上一个是指针一个是实体合并时必然出乱子。-text这个标记容易被忽略。它的作用是告诉 Git别把这个文件当文本处理不要做换行转换。对二进制文件来说这很重要少了它Windows 上的自动换行转换可能悄悄改坏文件——症状是图片打不开、模型导入报错而 diff 上什么都看不出来。3.3 配额、迁移与几个真实的坑LFS 的存储和流量通常是分开计费的免费额度对个人项目够用团队项目很容易顶到上限。这不是配置问题而是策略问题我一般从三个方向控制一是把构建产物彻底排除在外它们体积最大且完全不需要版本管理二是对美术源文件做约定比如 psd 只提交定稿版本中间稿放共享盘三是定期清理已删除但仍被历史引用的对象。已经提交过的大文件要转 LFS用迁移命令# 把历史中的 psd 全部转成 LFS 指针 git lfs migrate import --include*.psd,*.fbx,*.wav --everything # 查看当前被 LFS 跟踪的文件 git lfs ls-files # 检查哪些大文件还在普通 Git 里按体积排序 git rev-list --objects --all \ | git cat-file --batch-check%(objecttype) %(objectname) %(objectsize) %(rest) \ | awk /^blob/ {print $3, $4} | sort -rn | head -20最后那条命令我强烈建议每个接手老项目的人都跑一遍它会把仓库里最占地方的二十个对象按体积列出来谁提交的、是什么文件一目了然。我第一次跑的时候发现一个 400M 的测试视频躺在三年前的一次提交里从来没人用过。还有两个高频坑。一是 clone 时忘了装 LFS 客户端拉下来的全是几十字节的指针文件Unity 打开一看所有贴图都是空的报错信息还完全不提 LFS。二是 CI 环境里 checkout 默认不拉 LFS 内容需要在 checkout 步骤显式打开lfs: true。这两点后面还会提到。4. 场景与预制体的冲突:为什么手工合并基本没戏4.1 一次典型的场景冲突长什么样文本化之后一个.unity文件是一份 YAML里面有成千上万个对象每个对象带一个fileID还有一串m_LocalPosition、m_Color、m_Enabled之类的字段。两个人同时改同一个场景冲突的表现是几十处冲突标记散落在文件各处每一处都夹在几百行的对象定义中间你完全不知道这两坨 YAML 对应的是场景里哪个物体。硬着头皮手工解决的结果通常有两种要么把某个组件删多了打开场景发现一个灯不见了要么 fileID 引用对不上Unity 直接拒绝打开并提示文件损坏。所以结论很明确——场景冲突不靠手工靠工具加流程。4.2 配好 UnityYAMLMerge,让合并有个基本盘Unity 安装目录里带了一个命令行合并工具专门理解 YAML 结构能按对象粒度合并遇到真正冲突的字段才留标记。配置方式是在 git 配置里加一段# 放在 ~/.gitconfig全局或项目 .git/config仅本项目 [merge] tool unityyamlmerge [mergetool unityyamlmerge] trustExitCode false cmd C:/Program Files/Unity/Hub/Editor/2021.3.20f1/Editor/Data/Tools/UnityYAMLMerge.exe merge -p $BASE $REMOTE $LOCAL $MERGEDmacOS 下路径类似/Applications/Unity/Hub/Editor/2021.3.20f1/Unity.app/Contents/Tools/UnityYAMLMerge。注意版本号要换成团队实际用的编辑器版本而且这个路径在换机器、升级编辑器后都会失效——这是它最烦人的地方建议写进仓库的 README 或者用一个初始化脚本统一下发。配好之后冲突时执行git mergetool工具会尝试三方合并把能自动解决的解决掉剩下真正冲突的字段写成带标记的文本你用编辑器打开再看。它解决不了两个人同时改同一个物体的同一个属性这种本质冲突但能把 90% 的你改 A 物体、我改 B 物体的假冲突消化掉。提示.gitattributes里那三行mergeunityyamlmerge是让这个工具自动生效的关键。很多人只配了 gitconfig 却没写 attributes结果git merge时根本不会调用它以为工具没用。4.3 从源头减少冲突:三条被验证过的团队约定工具只能兜底真正有效的是拆分和约定。以下三条我在几个团队里都推行过效果最好。第一条场景按功能拆小。一个几千物体的大场景谁都可能改到它。正确的做法是按玩法或关卡区域拆成多个场景用 Additive 方式加载主场景只保留全局的灯光、相机和环境。这样改 UI 的人和调关卡的人压根不在同一个文件上工作。拆分时要注意公共资源的引用方式别为了拆而拆出一堆重复预制体。第二条预制体优先于场景编辑。场景里应该尽量是预制体实例调整细节时进入预制体编辑模式改而不是在场景里对实例做覆盖。这样做的好处是修改集中在预制体文件上而且覆盖属性以 override 的形式存在场景里冲突面积小得多。我见过一个项目场景里全是散装物体改一次布局冲突三天后来强制预制体化同样的工作量冲突降到几乎没有。第三条约定场景锁。这不是技术手段是流程手段谁要在共享场景上做大改先在协作频道里说一声改完立刻提交并推送不要攒着。听起来原始但成本最低、见效最快。规模再大一点可以上文件级的锁机制不过对十几人的团队来说一条群消息足够。4.4 一个实操场景:冲突已经发生了,按这个顺序处理假设你现在git merge完git status里一堆both modified。我的处理顺序是先别慌着开 Unity。关掉编辑器Unity 在打开状态下会持续写文件边合并边被写会出各种诡异问题。对代码文件.cs手工合并这类冲突最好处理直接看逻辑。对.unity/.prefab/.asset逐个git mergetool让 Smart Merge 先跑一遍。跑完检查剩余冲突标记grep -rn Assets/。有残留就说明工具也没辙这时候判断哪边的改动更重要直接取一边git checkout --ours或--theirs通常比硬凑更安全。全部解决后打开 Unity让它重新导入逐个场景点开看一眼确认没有物体丢失、引用没断。确认无误再git addgit commit提交信息写清楚合并 X 分支场景 A 采用远端版本。第 5 步不能省。我吃过一次亏合并完直接提交两天后美术反馈某个特效不见了翻回去看是合并时把一个粒子系统的对象块整段吞掉了而当时的 diff 看起来很干净。5. 分支模型、提交规范与自动化卡口5.1 中小团队够用的分支模型Unity 项目的分支不需要搞太复杂但也不能只有一条 main。我推荐的结构是三层分支用途生命周期谁可以推main随时可出包长期只接受 PR 合并dev日常集成分支长期组内成员feat/xxx、fix/xxx单个功能或修复几天到两周个人规则就三条功能分支从dev切出完成后 PR 回devdev稳定后合并到main并打 tagmain上不接受直接推送。这样做的核心目的是让可能坏的东西隔离在功能分支里——Unity 项目最怕的就是有人把一个半成品场景推上主干别人一拉就编译不过。有个细节要提醒Unity 项目合并分支后必须重新打开编辑器让它导入不能指望命令行git merge完就万事大吉。所以合并到dev的动作最好由一个人统一做做完在群里说一句dev 已更新大家拉一下避免几个人同时合并互相打架。5.2 提交粒度与提交信息:别让三个月后的自己骂人Unity 项目有一种特有的连坐现象你只改了一个脚本git status里却出现十几个.meta的变更你只挪了一个文件夹结果几十个文件的路径全变了。这些是正常的但提交时必须拆开说清楚。我的习惯是三类提交严格分开纯资产变更美术资源、贴图、纯代码变更、场景/预制体变更。理由很实际出问题时可以快速git revert某一类而不影响其他。全混在一个提交里回滚就是灾难。提交信息我要求包含三样做了什么、为什么、影响面。看两个例子对比就很清楚。# 不推荐 update # 推荐 fix: 修复关卡2传送点重复触发 - 触发判定从 OnTriggerEnter 改为带冷却的一次性触发 - 影响场景: Level_02已本地验证 - 关联问题: 反复进出传送门会叠加位移第二种写法半年后再看依然能读懂。而且它在做代码评审时特别有用——评审的人一眼就知道该重点看哪个文件。5.3 用钩子和分支保护卡住低级错误有一类错误反复出现靠人自觉很难杜绝忘了保存场景就提交、提交了没带 meta 的新文件、把Library又加回来、提交信息写1。这类问题适合用自动化卡口拦住。一个简单的提交前钩子检查暂存区里有没有不该出现的路径#!/bin/sh # .git/hooks/pre-commit blockedLibrary/ Temp/ Obj/ Logs/ UserSettings/ for p in $blocked; do if git diff --cached --name-only | grep -q ^$p; then echo 拒绝提交暂存区包含生成目录 $p echo 请执行 git rm -r --cached $p 后重试 exit 1 fi done # 检查新增的资产是否带了 meta git diff --cached --name-only --diff-filterA | grep -E ^Assets/.*[^.]$ | while read f; do [ -f $f.meta ] || { echo 缺少 meta 文件$f; exit 1; } done exit 0这个脚本不完美比如目录名匹配、子模块场景没覆盖但它拦住的问题足够多。仓库端的卡口则是分支保护main要求 PR 至少一人评审 状态检查通过才能合并。评审对 Unity 项目尤其有价值因为场景文件的 diff 虽然丑但谁改了哪个组件的哪个字段是能看出来的很多低级错误在评审阶段就能发现。注意钩子脚本放在.git/hooks里不会随仓库分发。要团队共享就把脚本放进仓库目录比如Tools/hooks/再让每个人执行一次git config core.hooksPath Tools/hooks。这一步写进项目 README 的新人引导清单里。6. 自动化构建:在 GitHub 上跑一条能用的流水线6.1 流水线该做什么,不该做什么先说定位。自动化构建要解决的是每次合并后代码能不能过、能不能出包而不是替代本地调试。所以它不需要跑得飞快但必须稳定、日志可查。我一般配三个触发PR 到dev时跑编译与单元测试合并到main时出包并归档手动触发用来出特定分支的测试包。有一个地方要提前想清楚Unity 的授权。命令行构建需要激活这是流水线里最容易卡住的一步通常的做法是把授权文件作为加密密钥存在仓库设置里构建时还原。这一步的细节各团队情况不同配置一次之后基本不用再动但第一次配的时候务必留足时间。6.2 一份结构清晰的构建配置下面这份配置省略了授权细节重点在流程骨架特别标注了几个 Unity 项目特有的关键点name: build on: push: branches: [main] pull_request: branches: [dev] workflow_dispatch: jobs: build: runs-on: ubuntu-latest steps: - name: 拉取代码必须开启 LFS uses: actions/checkoutv4 with: lfs: true fetch-depth: 0 - name: 校验 LFS 内容完整性 run: git lfs pull git lfs fsck - name: 缓存 Library 目录 uses: actions/cachev4 with: path: Library key: Library-${{ hashFiles(Packages/packages-lock.json, ProjectSettings/ProjectVersion.txt) }} restore-keys: | Library- - name: 运行编辑模式测试 run: | unity-editor -batchmode -nographics -quit \ -projectPath . \ -runTests -testPlatform EditMode \ -testResults ./test-results.xml \ -logFile ./logs/editmode.log - name: 构建播放器 run: | unity-editor -batchmode -nographics -quit \ -projectPath . \ -executeMethod BuildScript.PerformBuild \ -logFile ./logs/build.log - name: 上传日志与产物 if: always() uses: actions/upload-artifactv4 with: name: build-output path: | logs/ test-results.xml Build/几处值得单独说明。fetch-depth: 0是为了让构建脚本能读到完整的提交历史方便把提交号写进版本号。如果只用默认的浅克隆取版本信息会失败。缓存 Library 是收益最大也最容易出错的一步。缓存键必须绑定packages-lock.json和ProjectVersion.txt否则依赖变了但缓存没失效会出现本地能编译、流水线报找不到类型的鬼故事。我一开始图省事只用了固定 key结果升级了一个包之后流水线连续失败七次第八次才想起来清缓存。用restore-keys做前缀回退是个稳妥的折中完全匹配没有时退回最近的旧缓存虽然会重新导入一部分但比从零开始快得多。-nographics这个参数在无头环境里很关键不加会因为缺图形设备直接崩。但同时要注意某些依赖 GPU 的功能比如烘焙光照、部分后处理在无头环境里跑不了这类步骤要么单独拆一个带显卡的构建机要么挪到本地做。失败也要上传日志if: always()就是干这个的。Unity 的批处理日志会把错误藏在几千行输出里没有日志文件根本查不动。我还会在构建脚本里主动判断EditorApplication.Exit(1)让失败真的变成失败——默认情况下批处理模式的异常不一定会让退出码非零流水线会显示绿色的成功这种假绿最坑人。6.3 构建脚本里该写什么-executeMethod指向的静态方法要注意两件事一是必须在Editor目录下或者被#if UNITY_EDITOR包起来否则打包时会报找不到二是要自己处理版本号和环境变量。// Assets/Editor/BuildScript.cs using UnityEditor; using UnityEngine; public static class BuildScript { public static void PerformBuild() { // 版本号从环境变量拼装没有就用默认值 string buildNumber System.Environment.GetEnvironmentVariable(GITHUB_RUN_NUMBER) ?? 0; string commit System.Environment.GetEnvironmentVariable(GITHUB_SHA) ?? local; PlayerSettings.bundleVersion $1.0.{buildNumber}; PlayerSettings.SetApplicationIdentifier( BuildTargetGroup.Android, com.example.prototype); var options new BuildPlayerOptions { scenes new[] { Assets/Scenes/Boot.unity }, locationPathName Build/android/app.aab, target BuildTarget.Android, options BuildOptions.None }; var report BuildPipeline.BuildPlayer(options); if (report.summary.result ! UnityEditor.Build.Reporting.BuildResult.Succeeded) { Debug.LogError($构建失败commit{commit}错误数{report.summary.totalErrors}); EditorApplication.Exit(1); // 让流水线正确失败 } } }这段的重点不在 API而在最后那个EditorApplication.Exit(1)。少了它构建失败在流水线上可能显示为成功等到发版时才发现包是空的。7. 排查速查表与几条踩出来的经验7.1 高频问题速查现象最可能的原因处理方式clone 后贴图全是空白没装 LFS 或没拉取内容装 LFS 后git lfs pull打开场景提示文件损坏场景合并时结构被破坏从上一个正常提交取回.unity,重做改动引用大面积显示 Missing.meta丢失或被改动从历史恢复对应.meta,不要新建流水线报找不到某类型Library 缓存过期换packages-lock.json参与缓存键,清缓存重跑提交后 diff 里全是换行变更缺.gitattributes的textauto补上后重新规范化一次提交仓库突然涨了几百兆误提交构建产物或大文件用体积排序命令定位,重写历史清理两人改同一场景冲突几十处场景未拆分上 Smart Merge 应急,同时推动场景拆小CI 拉取后构建失败但本地正常本地有未提交的资产养成提交前git status清零的习惯7.2 几条只有踩过才知道的细节第一条改文件夹名一定要在编辑器里改。这句话值得写在文档第一行。命令行mv不会带着.meta走而元数据一旦对不上症状可能是几小时后才出现的——某个材质变粉、某个预制体引用空。粉红色在 Unity 里基本等于Shader 找不到而 Shader 丢了往往就是路径或 GUID 的事。第二条合并前先让所有人清空工作区。Unity 在后台会不停地写文件导入、刷新、自动保存。如果合并时编辑器开着很容易出现合并完成了但文件又被覆盖回去的情况。我们后来的做法是合并前群里喊一声大家关编辑器、git stash或提交然后由一个人统一做合并。第三条别在.gitignore里和.csproj较劲。有人为了让 IDE 索引更好用硬要把 csproj 提交上去结果每次拉代码都有一堆冲突。正解是让 Unity 自己生成在 IDE 里重新打开一次工程文件即可。真觉得每次重新生成麻烦就写个小脚本一键调用 Unity 的-executeMethod重新生成工程。第四条资产命名尽量别用中文和空格。这不是 Git 的问题而是跨平台和工具链的兼容问题。空格在命令行里要转义、中文在某些 CI 环境的编码下会变乱码最后表现为某个文件在别人机器上路径不同。我一般要求资产目录全部用英文加下划线纯本地调试用的临时资源单独放一个被忽略的目录里。第五条小步提交比什么都重要。Unity 项目的回滚成本很高因为一次提交里往往同时包含代码、资产、场景。如果这个提交有三百个文件变更出问题时你根本没法判断该回滚哪一部分。我给自己定的线是单个提交不超过三十个文件超了就拆。拆不动说明这次改动本身就该再切一次。7.3 我个人的收尾习惯最后分享一个我坚持了很久的小动作每次从主干拉下新代码打开 Unity 之后不要立刻开始干活先做三件事——看一眼 Console 有没有红色的资源警告、随便点开一个主要场景确认物体齐全、跑一次空构建。这三件事加起来不到五分钟但能拦住绝大多数拉下来就是坏的的情况。等我开始改代码才发现环境有问题成本就完全不一样了因为那时候已经分不清是自己的改动引起的还是拉下来的代码本来就有问题。还有一条关于纪律的体会。这套东西配起来其实不难.gitignore、.gitattributes、Smart Merge、流水线半天能弄完。真正难的是让所有人持续遵守改文件夹要在编辑器里改、场景要拆小、提交要分类。我见过太多项目配置文档写得漂漂亮亮三个月后又回到 Library 被提交、场景冲突靠喊话的状态。所以我的建议是把关键规则做成自动化卡口——钩子能拦的绝不靠提醒能在 CI 里检查的绝不靠评审记忆。剩下的那部分靠人才靠得住。