news 2026/10/26 15:44:58

Unity项目GitHub托管实战:.meta、Git LFS与场景冲突

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Unity项目GitHub托管实战:.meta、Git LFS与场景冲突

Unity 项目挂到 GitHub 上托管,听上去就是把文件夹往仓库里一推的事,真动手的人都知道这活儿坑有多深。我自己第一次把工程推上去,git status刷出来两万多个待提交文件,仓库体积十分钟内涨到 3 个 G,队友 clone 下来打开工程,贴图全变成粉色,预制体引用集体丢失。后来折腾了几年,从个人小 Demo 到七八个人同时开工的中型项目都踩过一遍,才摸清一套相对稳妥的做法。这篇东西讲的就是 Unity 工程交给 Git 托管这件事本身:哪些文件该进版本库、.meta文件为什么不能丢、二进制资源怎么处理、场景冲突怎么救、自动构建怎么配。内容偏实操,适合刚把工程从本地搬到远程仓库的独立开发者,也适合团队里负责搭基础设施的那位。不管你用的是哪种 Git 托管服务,规则是通用的,下面统一用 GitHub 举例。

1. Unity 仓库失控的根源:源码与产物的边界在哪

Unity 的工程目录结构和普通代码项目差别很大。一个纯代码项目里,几乎所有文件都是人手写的,改一行 diff 就一行。Unity 不是这样,它的目录里混着三类完全不同的东西:你手写的 C# 脚本和配置、引擎自动生成的各种缓存、以及美术同学丢进来的二进制资源。这三类东西的版本控制策略完全相反,混在一起提交,仓库必然失控。搞清楚这条边界,后面所有的忽略规则、LFS 规则、合并策略才有落脚点。

1.1 第一次git status就冒出两万个文件是怎么回事

新建的 Unity 工程,打开一次编辑器之后,根目录下会多出Library、Temp、Logs、obj、UserSettings这几个文件夹。Library是重点,它存放的是引擎对资源的导入结果:贴图压缩后的格式、模型的网格数据、脚本编译出来的程序集、着色器变体、资源依赖索引,全在里面。它的体积通常是Assets目录的好几倍,一个中等项目轻松几十个 G。

关键在于,Library是可再生的。它由Assets里的原始资源和ProjectSettings里的导入设置共同推导出来,删掉之后重新打开工程,Unity 会花几分钟到几十分钟重新生成一份。这份东西对版本控制毫无价值,因为它是产物不是输入。更麻烦的是它由引擎版本、平台设置、机器环境共同决定,A 同学电脑上的Library和 B 同学的根本不该一样,强行同步反而会引发一堆莫名其妙的导入错误。

同理,Temp是编译和构建的临时目录,obj是 C# 编译中间产物,Logs是日志,UserSettings存的是你这台机器上的编辑器布局、上次打开的场景、面板位置。这些全都不该进仓库。UserSettings尤其容易被误提交,因为它里面有个Layouts文件夹,看起来像是团队共享的界面配置,实际上每个人习惯不同,提交上去只会天天冲突。

判断一个目录该不该提交,问自己一句:删掉它,重新打开工程能不能自动恢复?能恢复的,一律忽略。

1.2.meta文件不是垃圾,它是整个工程的引用索引

这是我见过最多人栽跟头的地方。Assets目录下每个资源旁边都有一个同名的.meta文件,比如Player.prefab旁边有Player.prefab.meta。很多人第一眼看到觉得是冗余垃圾,顺手加进忽略列表,结果就是灾难开始。

.meta里存的是一串 GUID 和这个资源的导入设置。Unity 内部所有的引用,都不是按路径记的,而是按 GUID 记的。场景里一个物体引用了一个材质,存的是那个材质的 GUID;预制体里挂了一个脚本,存的是脚本的 GUID。只要.meta在,你把资源从Assets/A挪到Assets/B/C,所有引用照样有效,因为 GUID 没变。删掉.meta重新导入,Unity 会生成一个新的 GUID,全工程所有指向旧 GUID 的引用瞬间变成Missing,界面上就是粉色材质、空脚本槽。

所以规则很硬:.meta必须和资源一起提交,一个都不能少。.gitignore里那条!/[Aa]ssets/**/*.meta的感叹号是反选,作用是在整体忽略某些资源的同时,保证Assets下的.meta不被误伤。这个细节很多人抄模板时根本没注意。

还有一种情况更隐蔽:多人协作时,A 同学在本地新建了一个文件夹并放了资源,git add的时候只加了资源文件没加.meta,因为.meta在文件夹里默认被折叠了没看见。推上去之后,B 同学拉下来,Unity 给这个资源生成新 GUID,A 同学本地是老 GUID,两边一合并,引用全断。表现就是"我这里好好的,他那里全是粉色"。

1.3 二进制资源撞上 Git 的增量存储模型

Git 最初是为纯文本设计的。它存文件的方式是存整个文件快照,但压缩时对文本极其高效——两个版本的 C# 脚本只差一行,压缩后几乎不占额外空间。二进制文件没这个待遇,一张 4K 的 PSD 改了一个图层,在 Git 眼里就是两个完全不同的 800MB 对象,各存一份。改十次,就是 8 个 G。

Unity 项目里这种文件遍地都是:.psd、.png、.fbx、.wav、.mp3、.tga、.exr、.blend。一个美术同学正常迭代一周,仓库能涨几个 G,然后就到了推送超时、clone 半小时的地步。

解法是 Git LFS(Large File Storage)。它的原理不复杂:大文件本身不进 Git 的对象库,而是上传到单独的存储区,仓库里只留一个几百字节的文本指针文件,内容大概是版本号、文件哈希、大小。检出的时候 Git 根据指针自动把真文件拉下来。这样仓库主体保持轻量,克隆速度不受影响。

LFS 有几个必须提前知道的限制。第一,它不是免费的午餐,各家托管服务对 LFS 流量和存储都有配额,超了要么限速要么付费。第二,LFS 只对配置之后新增的提交生效,如果大文件已经进了历史,光加.gitattributes没用,得重写历史。第三,LFS 指针文件本质是文本,冲突时 Git 不能自动合并,只能选一边。这三点后面都会展开讲。

搞清楚这三层——产物该忽略、.meta必须留、二进制要走 LFS——Unity 仓库的地基才算打对。

2. 开工前先定规矩:忽略表与属性表的落地写法

很多人的做法是先把工程推上去,出问题了再补.gitignore。这个顺序是错的。.gitignore只对尚未被跟踪的文件生效,一旦Library已经被提交过,后面再加忽略规则,Git 依然会继续跟踪它们,你得手动git rm -r --cached清一遍。所以建仓库的第一件事不是git init,而是把规则文件写好。

2.1 用白名单思维反推忽略表

写.gitignore的时候,新手习惯一条条列"不要什么",写着写着总有漏网之鱼。更稳的思路是反过来:先明确要提交的东西只有四类,其余全部忽略。

提交内容作用说明
Assets/全部游戏资源和脚本含每个资源的.meta,一个都不能少
ProjectSettings/工程级配置标签层、物理、图形、输入、包管理设置
Packages/manifest.json依赖包清单声明用了哪些包及版本
Packages/packages-lock.json依赖锁定保证所有人装到完全相同的版本

四类之外的东西,除非有明确理由,一律不进仓库。这套白名单思路的好处是,就算以后 Unity 新增了某个临时目录,默认它也是被忽略的,不会因为规则没更新而误提交。

Packages/packages-lock.json这条值得单独说。它记录了每个包解析后的精确版本和来源,类似package-lock.json。没有它,manifest.json里写的"com.unity.textmeshpro": "3.0.6"在不同机器上可能解析出不同的间接依赖,导致"我这儿能编译他那儿报错"。这个文件一定要提交,而且尽量别手动改。

2.2 逐行拆解忽略表里那些容易被误删的条目

下面这份是实践中最常用的一份,我按块拆开讲为什么:

# ---------- 引擎生成的目录 ---------- [Ll]ibrary/ [Tt]emp/ [Oo]bj/ [Bb]uild/ [Bb]uilds/ [Ll]ogs/ [Uu]ser[Ss]ettings/ [Mm]emoryCaptures/ [Rr]ecordings/ # ---------- 自动生成的工程文件 ---------- *.csproj *.unityproj *.sln *.suo *.user *.userprefs *.pidb *.booproj *.svd *.pdb *.mdb *.opendb *.VC.db # ---------- 崩溃与构建产物 ---------- sysinfo.txt *.apk *.aab *.unitypackage *.app # ---------- 关键反选 ---------- !/[Aa]ssets/**/*.meta

[Ll]ibrary/这个写法是大小写兼容。Windows 上 Unity 生成的是Library,某些 Linux 环境下可能是library,方括号匹配能同时覆盖。Build/和Builds/都要写,因为不同人导出时习惯不同。

*.csproj和*.sln这一组,很多人不理解为什么要忽略,明明是工程文件。原因是 Unity 会根据Assets下的脚本自动重新生成它们,而且里面包含大量绝对路径和包引用。两个人如果 IDE 不同、装的插件不同,这些文件天天冲突,且冲突了也没法手工合并。忽略掉,让 Unity 每次自己生成,反而干净。

*.apk、*.aab、*.unitypackage、*.app这些是构建产物,体积巨大且可以随时重新打出来。特别是.unitypackage,有人喜欢把第三方资源包整个提交,一个包几百兆,非常划不来——导进来之后的东西本来就进Assets了,原始包没有保留价值。

那条反选!/[Aa]ssets/**/*.meta是保命的。它的含义是:即使上面的规则命中了某个文件,只要它在Assets下且是.meta结尾,就不要忽略。安全网性质的一条。

还有一类需要视情况处理:如果你用了 Addressables,Assets/AddressableAssetsData下会生成.bin缓存文件,这些是可重建的,可以忽略;但AddressableAssetsData里的.asset配置必须提交。类似地,Assets/StreamingAssets下的内容如果是构建时拷贝进去的,注意别把中间产物一起提交了。

2.3.gitattributes:LFS 追踪和合并驱动都挂在这

.gitignore决定什么不进仓库,.gitattributes决定进了仓库的文件怎么被对待。Unity 项目里它承担两件事。

第一件是 LFS 追踪。写法有两种风格,按扩展名和按目录。按扩展名更直观:

# ---------- 文本规范化 ---------- * text=auto # ---------- 二进制资源走 LFS ---------- *.psd filter=lfs diff=lfs merge=lfs -text *.png filter=lfs diff=lfs merge=lfs -text *.jpg filter=lfs diff=lfs merge=lfs -text *.tga filter=lfs diff=lfs merge=lfs -text *.exr filter=lfs diff=lfs merge=lfs -text *.fbx filter=lfs diff=lfs merge=lfs -text *.blend filter=lfs diff=lfs merge=lfs -text *.wav filter=lfs diff=lfs merge=lfs -text *.mp3 filter=lfs diff=lfs merge=lfs -text *.mp4 filter=lfs diff=lfs merge=lfs -text *.dll filter=lfs diff=lfs merge=lfs -text # ---------- Unity YAML 走智能合并 ---------- *.unity merge=unityyamlmerge eol=lf *.prefab merge=unityyamlmerge eol=lf *.asset merge=unityyamlmerge eol=lf *.mat merge=unityyamlmerge eol=lf *.anim merge=unityyamlmerge eol=lf *.controller merge=unityyamlmerge eol=lf *.meta merge=unityyamlmerge eol=lf

-text这个标记容易被忽略但很重要,它告诉 Git 不要对这个文件做换行符转换和文本 diff,避免把二进制内容当文本处理导致损坏。.dll走 LFS 是个取舍,插件 dll 通常几百 K 到几 M,如果插件很少可以不追,如果项目依赖大量第三方 SDK 建议追。

第二件是给 Unity 的 YAML 文件绑定合并驱动unityyamlmerge,这就是第 4 节要讲的智能合并,先在这里埋好伏笔。

规则文件写完之后,务必用git check-ignore -v <路径>验一下某个具体文件到底被哪条规则命中。这个命令排查规则冲突特别有效,比肉眼读几十行规则快得多。

2.4 序列化模式:Force Text 必须在第一次提交之前打开

这一步经常被跳过,但它决定了整个团队以后处理冲突的难度上限。

打开Edit > Project Settings > Editor,找到Asset Serialization,把Mode设成Force Text。新版 Unity 默认就是这个,但老工程升级上来的不一定。这个设置的效果是,场景、预制体、材质、动画控制器这些资源,存成可读的 YAML 文本而不是二进制。

差别有多大?二进制模式下,两个人同时改一个场景,冲突了只能选"我的"或"他的",另一边的改动全丢,没有第三条路。文本模式下,Unity 提供了UnityYAMLMerge工具,能按对象和字段粒度做三方合并,冲突范围能从"整个文件"缩小到"某个 GameObject 的某个属性"。

同一个面板里还有个Version Control选项,设置成Visible Meta Files(新版可能叫Visible Meta Files或已经是默认)。它的作用是让.meta文件在文件系统里可见,否则 Unity 会把它藏在内部,Git 根本拿不到。

这两项设置改完要提交ProjectSettings/EditorSettings.asset,让整个团队统一。如果有人本地是二进制模式,他提交的场景会把文本格式覆盖回去,导致其他人下次打开发现格式变了,一串无意义的 diff。

配置好这两项,地基就算彻底完工,可以动手建仓库了。

3. 半年老工程怎么搬进仓库:一次完整的迁移实录

新项目从空文件夹开始是最省事的。但现实中更常见的情况是:工程已经做了大半年,本地文件夹里堆着Library、Builds、各种备份文件,现在才想起来要做版本控制。这种情况坑更多,得按步骤来。

3.1 备份、清空、断网验证三件套

动手之前第一件事:整个工程文件夹复制一份到别的盘。这不是客套话,迁移过程中一不小心git clean -fdx就能把没提交的资源全删了,我在一个项目上亲眼见过美术一下午的工作量蒸发。

备份完,按顺序做这几步:

  1. 关闭 Unity 编辑器。开着的时候Library里的文件被占用,Git 操作可能失败,而且编辑器在后台持续写入,你根本不知道它什么时候改了东西。
  2. 删掉Library、Temp、obj、Logs、UserSettings、Builds。删完确认工程目录下只剩Assets、ProjectSettings、Packages三个核心文件夹,加上可能存在的.vs、.idea之类的 IDE 目录。
  3. 把第 2 节写好的.gitignore和.gitattributes放到工程根目录。
  4. git init,然后git add .,先不要 commit。
  5. 执行git status,看暂存区里到底有多少文件、总体积多大。

第 5 步是关键检查点。正常情况下,一个中小项目的首次提交应该在几百 M 到 1 G 之间。如果显示好几千个文件、体积几个 G,说明有东西没被忽略掉,回头检查规则。可以用这条命令看暂存区各目录的体积分布:

git ls-files -s | awk '{print $4}' | xargs -I{} du -k {} 2>/dev/null | sort -rn | head -30

它会列出暂存区里体积最大的 30 个文件,一眼就能看出是哪类资源在撑体积。如果全是大图,说明 LFS 没配好或者配置了但没生效。

最后一步验证:git commit之后,把整个工程文件夹改名(不是删除),然后从远程 clone 一份下来,打开 Unity,看是否报错、资源是否完整。这个"断网验证"能提前暴露所有.meta缺失、LFS 指针没拉全的问题,比等到队友拉下来才发现要好得多。

3.2 历史里已经塞进Library的补救手段

更麻烦的情况是:仓库已经用了一段时间,Library早就被提交过好几版,仓库体积已经失控。这时候加.gitignore是没用的,因为 Git 依然跟踪着这些文件。

第一步是让 Git 停止跟踪,但保留本地文件:

git rm -r --cached Library Temp obj Logs UserSettings git commit -m "chore: 停止跟踪引擎生成目录"

这样以后的提交干净了,但历史里的那些大对象还在。.git文件夹依然臃肿,clone 依然要拉几个 G。要彻底清理得重写历史。

工具上,官方推荐的git filter-repo比老的filter-branch快几十倍也安全得多:

# 安装(需要 Python 环境) pip install git-filter-repo # 从全部历史中移除指定目录 git filter-repo --path Library --path Temp --path obj --invert-paths

--invert-paths的含义是"删除匹配到的路径",配合多个--path就能一次性清掉多个目录。执行完之后本地历史被重写,所有 commit hash 都会变,必须用git push --force覆盖远程,并且通知所有协作者重新 clone,不能直接pull。这一步在多人项目里要挑个大家都不干活的时间段做,不然有人推了旧历史上去,一切白费。

重写历史前建议先打个标签备份:git tag backup-before-rewrite,万一搞砸了还能从 reflog 或者备份标签找回来。

3.3 把已经进历史的大文件迁到 LFS

LFS 的坑在于它只对配置之后的提交生效。.gitattributes写好、git lfs track执行完,之前提交的.psd依然是普通 Git 对象,依然占着仓库体积。

git filter-repo也能处理这个,它有一个专门的--to-vcs或者配合git lfs migrate使用更直接:

# 把历史中所有匹配的文件转成 LFS 对象 git lfs migrate import --include="*.psd,*.png,*.fbx,*.wav" --everything

--everything表示遍历所有分支和标签。执行完会重写这些文件的存储方式,仓库里的普通对象变成指针,真文件迁到 LFS 存储。之后再git lfs push --all origin把 LFS 对象推上去。

这一步的风险点是:如果团队里有人的本地仓库没同步这次重写,他推上来的还是老格式,会造成指针和实体混杂,检出时报错smudge filter lfs failed。所以迁移前后一定要在群里明确通知,所有人删掉本地仓库重新 clone 是最省心的做法。

3.4 换台机器拉下来打不开,通常是这几处没对上

迁移完成后,新同事 clone 下来打不开工程,报错信息往往很含糊。按这个顺序排查,命中率很高:

现象可能原因处理方式
贴图全粉、脚本槽为空.meta没提交或被误忽略`git ls-files
拉下来的大文件只有几百字节LFS 没装或没拉取git lfs install后git lfs pull
打开就提示版本不一致团队 Unity 版本不统一ProjectSettings/ProjectVersion.txt提交,约定统一版本
缺包、报package not foundpackages-lock.json没提交补提交并统一manifest.json
输入系统/渲染管线报错ProjectSettings某个资产没提交检查ProjectSettings目录是否完整

第一行那条命令特别实用,能快速比对。正常情况Assets下的.meta数量和需要它的资源数量应当匹配(每个文件夹也各有一个.meta)。差得越多,说明漏提交越严重。

4. 场景与预制体冲突:从满地狼藉到可控

多人协作里最让人头疼的不是代码冲突,代码冲突至少还能一行行看。场景冲突是真的会让人崩溃——两个人在同一个.unity文件里各改各的,合并之后场景里冒出一堆重复物体、组件丢失、光照参数错乱,排查起来比重做还慢。

4.1 一个.unity文件的冲突为什么能废掉半天

根源在于场景文件的组织方式。一个场景是单个 YAML 文件,里面按--- !u!<类型ID> &<对象ID>分段,每段描述一个对象。文件里的对象 ID 是本地生成的,两个人各自新增物体,ID 极可能撞上。Git 的三方合并只看文本行,不懂 YAML 语义,它会把两边的改动硬拼在一起,拼出来的结果语法可能是合法的,但逻辑完全错乱。

更隐蔽的是"删除 + 新增"的交叉情况。A 删了一个物体,B 在同位置改了这个物体的参数。文本合并的结果是物体既被删又被改,最终表现可能是引用指向一个不存在的对象,Unity 打开时报Missing Reference,但你根本定位不到是哪个。

所以对场景文件的处理原则很简单:尽量避免冲突,而不是解决了冲突。这听起来像废话,但确实是最有效的手段。

4.2UnityYAMLMerge的配置和它不生效的常见原因

.gitattributes里那一行*.unity merge=unityyamlmerge只是声明"这个类型的文件用名为unityyamlmerge的驱动来合并",驱动本身得单独配置。

打开全局或仓库级 Git 配置,加上:

git config merge.unityyamlmerge.name "Unity SmartMerge" git config merge.unityyamlmerge.driver "\"Unity安装路径/Editor/Data/Tools/UnityYAMLMerge.exe\" merge -p %O %B %A %A"

路径按操作系统不同:

  • Windows:C:/Program Files/Unity/Hub/Editor/2022.3.xx/Editor/Data/Tools/UnityYAMLMerge.exe
  • macOS:/Applications/Unity/Hub/Editor/2022.3.xx/Unity.app/Contents/Tools/UnityYAMLMerge
  • Linux:<Unity安装目录>/Editor/Data/Tools/UnityYAMLMerge

四个参数的顺序%O %B %A %A分别代表 base(共同祖先)、ours(当前分支)、theirs(被合并分支)。注意最后写的是%A %A,这是官方推荐写法,表示合并结果输出到我们的文件里。

配置完用一次真实冲突验证:随便开两个分支各改一个场景里的不同物体,然后 merge。如果弹出Automatic merge failed并且场景文件里出现了<<<<<<<标记,说明驱动没生效。常见原因有这么几个:

  • .gitattributes里写的是merge=unityyamlmerge,但git config里的名字拼错,或者大小写不一致
  • 路径里有空格却没加引号,Git 解析时把路径截断了
  • Unity 版本升级后旧路径失效,2021.3.x换成了2022.3.x但配置没改
  • 用了 Git GUI 客户端,客户端自己的合并工具覆盖了 Git 配置

最后一条特别容易忽略。某些图形客户端有自己的"外部合并工具"设置,优先级高于.gitattributes,配了半天不生效就是因为客户端在中间拦截了。排查时先用命令行git merge试,命令行生效了再调客户端。

即便 SmartMerge 正常工作,它也不是万能的。它擅长处理"两边改了不同物体的不同属性",对于"两边改了同一个物体的同一个字段"依然会冲突,对于数组顺序变化、List 增删的处理也不总是正确。所以配好工具只是降低冲突频率,不能指望它兜底。

4.3 LFS 文件锁:把覆盖别人的改动掐死在提交前

美术资源有个特点:PSD 文件没法做有意义的 diff。两个人都改同一张原画,只能选一边。但比起"选哪边",更常见的问题是"根本不知道对方也在改",等推上去才发现覆盖了别人两小时的工作。

LFS 提供了文件锁机制。开启方式是在.gitattributes里给需要保护的扩展名加上lockable:

*.psd filter=lfs diff=lfs merge=lfs -text lockable *.blend filter=lfs diff=lfs merge=lfs -text lockable

然后git lfs lock <文件路径>就能锁住这个文件。锁定状态会同步到远程,其他人推同一文件时会被拒绝,提示文件被锁。改完git lfs unlock释放。

实际操作中要注意两点。一是锁是基于路径的,改了文件名锁就失效了,所以文件名尽量定了就别动。二是团队所有人必须都安装 LFS 并且git lfs install过,否则锁机制形同虚设——没装 LFS 的客户端根本不认识这些指令。

这套机制配合git lfs locks命令可以列出当前所有锁,谁锁了什么、什么时候锁的一目了然。我们后来的做法是每天早上开工前跑一次,确认自己要动的原画没被别人占着。

4.4 分支模型和提交粒度,比工具更影响体验

工具再好,也架不住所有人天天在同一个主分支上推。Unity 项目的特点是"改一个功能经常要动场景、动预制体、动配置",天然容易冲突。把分支切得合理一些,冲突量能降一大截。

小团队(五到八人)我推荐的做法是:

  • main分支保持随时可出包的状态,只接受通过 PR 合并的改动
  • 每个人按功能开短分支,命名带人名或功能名,比如feature/inventory-system
  • 分支存活时间控制在三天以内,超过就强制拆小
  • 场景文件默认只有一个人负责,其他人需要调整就在预制体上改,运行时再拼进场景

最后一条是经验之谈。把一个关卡场景拆成多个预制体,每个人只改自己负责的那块预制体,冲突概率能降一个数量级。场景本身变成纯粹的"装配清单",改动极少。

提交粒度上,最忌讳的是"攒一天然后一次提交"。一个大提交里混着脚本改动、场景改动、配置改动,出了问题根本没法回滚,只能整体回退。合理的粒度是每完成一个小功能点提交一次,提交信息写清楚动了哪块。Unity 有个小技巧:提交前在编辑器里跑一次File > Save Project,确保所有改动已经落盘,不然可能出现编辑器内存里有改动但没写到文件的情况。

5. 自动出包:把手动构建那二十分钟省掉

工程托管好之后,接下来最自然的一步就是自动构建。手动出包的问题是:打开 Unity 等编译、切平台、点构建、等半小时、发现报错、重来。这套流程一天走三遍,时间全耗在等待上。配好 CI 之后,推代码就自动出包,报错也能第一时间看到。

5.1 许可证和凭据的准备

GitHub Actions 的 runner 是干净的虚拟环境,里面没装 Unity,也没有你的许可证。所以第一步是把构建用的环境准备好。

社区里有一套成熟的 Unity 构建 Action(常被称为 GameCI),它支持从加密的许可证文件激活。流程大致是这样:

  1. 本地激活一次 Unity 许可证,拿到许可证文件(.ulf)
  2. 把文件内容转成 base64:base64 -w 0 Unity_v2022.x.ulf > license.txt
  3. 把这一长串字符存进仓库的Settings > Secrets and variables > Actions,命名比如UNITY_LICENSE

如果有 Personal 版许可证,另外还需要UNITY_EMAIL、UNITY_PASSWORD两个 secret。这套凭据属于高度敏感信息,务必确认仓库是私有的,公开仓库里放这种东西等于把账号交出去。

5.2 工作流文件逐段读

在仓库根目录建.github/workflows/build.yml,一份可用的最小配置大概长这样:

name: Build Unity Project on: push: branches: [ main ] workflow_dispatch: jobs: build: runs-on: ubuntu-latest steps: - name: 拉取代码(含 LFS 文件) uses: actions/checkout@v4 with: lfs: true fetch-depth: 0 - name: 恢复 Library 缓存 uses: actions/cache@v4 with: path: Library key: Library-${{ hashFiles('Assets/**', 'Packages/**', 'ProjectSettings/**') }} restore-keys: | Library- - name: 执行构建 uses: game-ci/unity-builder@v4 env: UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }} UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} with: targetPlatform: WebGL unityVersion: auto - name: 上传构建产物 uses: actions/upload-artifact@v4 with: name: build-webgl path: build

几个参数值得展开。lfs: true必须有,否则检出的是 LFS 指针文件,Unity 打开时所有贴图都是坏的。fetch-depth: 0拉取完整历史,某些版本号生成逻辑会用到 git 提交次数。

targetPlatform支持StandaloneWindows64、Android、iOS、WebGL等,一次配多个平台就是多开几个 job,注意并行 job 会同时消耗构建配额。

unityVersion: auto让 Action 自动读取ProjectSettings/ProjectVersion.txt里的版本号去拉对应编辑器镜像。这个镜像有几个 G,第一次跑会慢,之后有缓存就快了。

5.3 缓存Library的两面性

缓存Library是提速的关键。没缓存的话,每次构建都要全量导入资源,一个中等项目二十分钟起步。有缓存能压到三五分钟。

但缓存也有坑。key用的是Assets、Packages、ProjectSettings三处内容的哈希,只要这三处有任何变化,key 就变了,缓存失效重新全量导入。这是保守策略,好处是不会用到过期缓存出错。

更激进的策略是用固定 key 加restore-keys兜底,能命中更久之前的缓存。代价是可能拿到跟当前代码不匹配的Library,偶尔出现"引用了一个已经被删掉的东西"这类诡异报错。我的经验是:项目初期用保守策略,等构建稳定了再考虑放宽。

还有一个细节,Library缓存体积可能到几个 G,GitHub 的缓存空间有上限,超了会淘汰最旧的缓存。所以别把Builds之类的东西也塞进缓存路径。

5.4 构建红了之后先查这几样

CI 挂了的时候,日志往往几百行,直接看容易懵。按这个顺序查,八成的失败都能定位:

许可证报错,日志里出现No valid Unity license或License activation failed。通常是 secret 名字拼错、许可证过期、或者 base64 转换时多了换行。重新生成一遍 secret 基本能解决。

LFS 文件拉取失败,日志里出现smudge filter lfs failed或者This repository is over its data quota。前者检查 checkout 步骤有没有lfs: true;后者是 LFS 流量用完了,得清理历史或者升级配额。

包解析失败,报Cannot resolve package。检查packages-lock.json是否提交完整,以及 CI 环境能不能访问包源。有些包依赖私有源,需要额外配凭据。

平台模块缺失,报Build target not supported。构建 Linux 上出 Android 包,需要预装 Android 模块。GameCI 的镜像默认带了一些,但不是全都有,需要时用with: androidTargetSdkVersion之类的参数指定,或者换用带完整模块的镜像。

构建超时,一般是因为没缓上,全量导入加上下载编辑器镜像超过默认时限。加大 timeout 或者优化缓存策略。

构建产物那一步用upload-artifact存起来,可以直接从 Actions 页面下载。如果要做发布,可以在后面接一个自动创建 release 并附上产物的步骤,这部分按各自需要扩展就行。

6. 最后聊几条不太好写进文档的经验

工程托管这件事,规则层面其实不复杂,难的是让整个团队保持一致。我遇到过的绝大多数事故,都不是技术方案有问题,而是有人绕过了方案:临时手动改了一下.gitignore、图省事git add -A把所有东西都推上去、编辑器里忘了存盘就提交。

有个做法我觉得挺管用:在仓库根目录放一份CONTRIBUTING.md,写清楚三条硬规矩——提交前跑一次git status确认没有Library;新加资源确认.meta一起进暂存区;动ProjectSettings的改动单独提一个 commit。文字不用多,够明确就行。后来发现新人入职读三分钟,能省下后面无数次救火。

还有个检测手段,可以在 CI 里加一个前置 job,检查本次提交是否包含了不该出现的路径,比如Library/、Temp/、*.csproj。命中就直接 fail,比事后清理历史省事得多。规则写起来也就几行:

if git diff --name-only HEAD~1 HEAD | grep -E '^(Library|Temp|obj|Logs)/'; then echo "提交中包含引擎生成目录,请检查 .gitignore" exit 1 fi

这东西的价值在于,它把"事后补救"变成了"当场拦截"。人工 review 靠不住,机器检查才是真的靠得住。

至于仓库规模的长期维护,我自己的习惯是每个月看一眼仓库体积和 LFS 用量。如果 LFS 用量涨得异常快,通常意味着有人在提交一些本该忽略的中间文件,比如渲染出来的序列帧、录屏素材、或者从商店下的原始资源包。早点发现,清理成本就低。

最后补一句关于 Unity 版本的事。ProjectSettings/ProjectVersion.txt一定要提交,并且团队约定统一版本,别出现"你用 2021 我用 2022 大家互相覆盖"的局面。版本不一致导致的资源重新序列化,是那种看起来默默无闻、实际天天在制造无意义 diff 的隐形杀手,等你发现的时候,历史里已经堆了几百个只改了一行版本号的提交了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/26 15:44:28

Java小游戏课设交付全攻略:UML图、README与Swing实现

简介&#xff1a;这是一份基于Java语言开发的完整小游戏项目&#xff0c;兼具课程设计与毕业设计参考价值&#xff0c;也适合初学者通过源码研读进入游戏开发领域。压缩包内含170个文件&#xff0c;大小约15.89MB&#xff0c;既有90个Java源文件实现角色、场景与逻辑控制&#…

作者头像 李华
网站建设 2026/10/26 15:44:27

Linux高并发网络调优实战与内核参数优化

1. Linux高并发网络调优的必要性与场景定位当服务器QPS突破5000时&#xff0c;你会发现默认的Linux网络配置就像早高峰的地铁1号线——明明轨道容量足够&#xff0c;却因为闸机响应慢、乘客进出效率低导致整体吞吐量下降。我在某电商大促期间就遇到过这样的场景&#xff1a;16核…

作者头像 李华
网站建设 2026/10/26 15:43:16

e2e profile1 端到端测试配置方案详解与实操指南

1. 从“e2e”这个标题说起&#xff1a;它到底指什么第一次看到“e2e”这个标题&#xff0c;很多人会愣一下。三个字符&#xff0c;没有上下文&#xff0c;没有修饰词&#xff0c;放在任何技术社区里都像是一个暗号。但如果你在软件工程、测试开发或者质量保障领域待过一段时间&…

作者头像 李华
网站建设 2026/10/26 15:43:13

企业大模型落地实战:从需求到验收的完整工程指南

简介&#xff1a;这份《2025年大模型应用落地白皮书&#xff1a;企业AI转型行动指南》面向企业管理者、AI项目负责人及数字化转型从业者&#xff0c;聚焦大模型从探索走向业务融合过程中的落地路径与实操难题。PDF共1个文件&#xff0c;压缩包约5.44MB&#xff0c;内容围绕大模…

作者头像 李华
网站建设 2026/10/26 15:42:40

目标检测模型调优顺序:基线、数据、锚框、NMS与量化部署

做目标检测这几年&#xff0c;我经手过的调优需求少说也有几十个。大部分人来问的时候&#xff0c;问题都不是 "模型能不能跑"&#xff0c;而是 "模型跑起来了&#xff0c;但 mAP 卡在 0.6 上不去" 或者 "离线指标挺好看&#xff0c;一上产线漏检一堆…

作者头像 李华
网站建设 2026/10/26 15:42:34

医疗AI诊断技术十年演进与核心突破

1. 诊断技术十年演进全景图 2013-2023这十年间&#xff0c;诊断技术经历了从传统经验医学到数字化智能诊断的范式转移。记得2014年我刚入行时&#xff0c;三甲医院的放射科还在用CR系统&#xff08;计算机X线摄影&#xff09;&#xff0c;医生需要手动调整窗宽窗位来看片。而今…

作者头像 李华