
如果你是个Unity开发者却还没把项目装进Git仓库里管起来我强烈建议你今天就开始做这件事。原因很直白Unity项目的目录结构天然就对Git不太友好加上场景、Prefab、贴图这些资源的特殊性如果不按正确姿势来你早晚会被一堆莫名其妙的合并冲突、反复重新导入的Library目录、.meta文件报错折磨到心态爆炸。这篇文章我会把Unity Git GitHub这套工业化版本控制方案的完整步骤整理成一份可直接照抄的清单覆盖从Git安装、GitHub配置、Unity工程准备、首次提交到日常分支协作、场景合并冲突处理的完整闭环。会专门解释每一步背后的原因以及我这些年实际踩过的坑和总结出的经验帮你少走弯路。1. 为什么Unity项目用Git经常翻车1.1 Unity工程结构对Git并不友好很多人第一次把Unity项目放进Git仓库后都会发现一个诡异现象明明只改了一行脚本提交时却冒出一堆完全不认识的文件变动有的在Library目录里有的是莫名其妙生成的临时文件。这个问题要搞明白得先从Unity工程目录结构说起。一个标准Unity项目的根目录大致长这样Assets存放所有我们真正关心的资源包括场景、脚本、Prefab、材质、贴图、模型、音频。LibraryUnity本地缓存目录用来存储导入资源后的中间数据、Shader编译缓存、场景预览图等。它完全可以从Assets和ProjectSettings重建。ProjectSettings项目配置比如渲染管线、输入系统、物理设置等都是文本文件。Packages项目依赖的Unity包管理清单。Temp、Logs、Obj、UserSettings临时文件、日志、编译中间文件、编辑器界面布局配置都属于“本机私有状态”。问题就出在Library和Temp这类目录上。有些人贪方便直接git add .把整个工程塞进仓库结果仓库体积轻松超过好几个G团队每个人拉下来都要重新处理一遍巨大的缓存目录整个仓库越来越臃肿。更麻烦的是哪怕你没有主动改动任何东西每次打开UnityLibrary里的文件可能都会变化提交记录变得无比混乱。还有就是Unity的资源序列化模式。默认情况下Unity会以Force Binary模式保存场景和Prefab二进制文件在Git里完全没法做差异对比两个人改了同一个场景合并时只能干瞪眼。所以要做版本控制第一步就是把这个选项改成Force Text才能让场景、Prefab变成可读的YAML文本格式Git才有办法做文本级别的合并。1.2 团队协作中的真正痛点在我的实际项目经历里Unity上Git团队协作的痛点主要集中在几个地方。第一个就是场景文件。就算你把序列化模式改成了Force Text同一个.unity场景文件里依然可能存着大量元素包括场景内所有物体的Transform、组件参数、Lightmap引用、烘焙数据等。两个人同时在同一个场景里摆UI、调灯光提交时几乎必然冲突而且冲突内容非常难看一长串YAML标记夹杂着m_FileID和guid手工合并简直灾难。第二个是.meta文件问题。Unity为Assets下每个文件都分配了一个.meta文件里面记录着GUID。不同文件、脚本、材质之间的引用关系靠的就是GUID。如果meta文件丢失或被改名Unity会生成新的GUID导致所有引用它的资源全部断开材质变粉、脚本丢失、Prefab组件丢失。所以meta文件必须提交但很多人不知道这一点或者用了错误的隐藏meta文件设置直接把整个项目引用的根基给毁了。第三个是二进制大文件。项目美术资源里贴图、模型、音频动不动就是几十上百MB直接放进Git仓库仓库体积迅速膨胀提交、拉取、克隆都变得极其缓慢而且这些二进制文件没有增量存储的概念每次改动都是整份重新保存。这时候就必须借助LFS来做大文件托管。2. 从零到一的准备阶段2.1 Git安装与全局配置这一节只说最基础、最必须的步骤Git安装本身并不复杂但有几个细节值得注意。Windows用户建议直接去Git官网下载Git for Windows安装包装的时候注意几个选项勾选“Add Git Bash to Windows PATH”这样可以让你在CMD或PowerShell里也能直接用git命令行结束符换行符转换建议选“Checkout as-is, commit as-is”这样能避免因为Windows和Mac/Linux换行符差异导致的诡异diff。Mac用户可以直接brew install gitLinux用户根据发行版用apt或yum安装。装完先验证一下git --version接下来配置全局用户信息这一步不配的话你的每次提交都会被标记成未知用户而且后面推送的时候极大概率认不出你git config --global user.name 你的名字 git config --global user.email 你的邮箱我建议再顺手配一个默认分支名和常用别名git config --global init.defaultBranch main git config --global alias.st status git config --global alias.co checkout git config --global alias.ci commit全局配置里最重要的一点是user.name和user.email必须和GitHub账号对得上特别是你自己邮箱或GitHub noreply邮箱。如果你不想把真实邮箱暴露在公开仓库里可以在GitHub的Settings - Emails里找到noreply邮箱地址把它配置成全局邮箱。2.2 GitHub仓库准备与SSH配置代码托管平台我这边默认用GitHub因为它在业界生态最丰富Pull Request、Actions、LFS这些功能都做得比较完善。操作上先注册并登录GitHub点击右上角加号选择New repository。创建仓库时有一个很关键的细节如果本地已经有Unity工程建议仓库初始化的选项全都不要勾包括README、.gitignore、License直接创建一个空仓库。这样能避免本地仓库和远程仓库从两个互不相关的历史节点开始后续合并时多出不必要的麻烦。连接GitHub的方式我强烈推荐用SSH而不是HTTPS。HTTPS每次推送都要输用户名密码而且现在GitHub已经不支持密码认证只能用Personal Access Token体验很差。SSH密钥配上之后推送拉取都不需要再输密码。生成SSH密钥很简单在Git Bash里执行ssh-keygen -t ed25519 -C 你的注册邮箱一路回车到底会在用户目录的.ssh文件夹下生成id_ed25519和id_ed25519.pub两个文件。公钥内容查看一下cat ~/.ssh/id_ed25519.pub复制整段内容打开GitHub的Settings - SSH and GPG keys - New SSH key粘贴保存。然后测试连接ssh -T gitgithub.com如果出现Hi xxx! Youve successfully authenticated, but GitHub does not provide shell access.这行提示说明SSH配置成功。这里还有个常见问题公司电脑或者多人共用电脑时一个账号部署多个SSH key是可以的建议在创建密钥时用-f参数指定文件名区分比如ssh-keygen -t ed25519 -C 邮箱 -f ~/.ssh/id_ed25519_work。多密钥情况下要在~/.ssh/config里按Host区分用哪个密钥比如Host github.com HostName github.com User git IdentityFile ~/.ssh/id_ed25519_work2.3 Unity编辑器端关键设置在Unity里动手之前有两个编辑器设置必须先改。打开菜单Edit - Project Settings - Editor看右下角的Asset Serialization区域Asset Serialization Mode必须改成Force Text。这样场景、Prefab、材质等资源才会以YAML文本格式保存Git才能做文本差异和合并。Version Control Mode必须改成Visible Meta Files。这一步是很多新手的盲区。只有在这个模式下Assets下每个文件都会生成对应的.meta文件这些meta文件才能被纳入版本控制。如果选的是Hidden Meta Filesmeta文件不落盘Git仓库里没有meta记录换一个人拉下代码Unity会对所有资源重新生成GUID整个项目的引用结构直接崩塌。这两个设置必须在创建工程初期就确认好如果项目一开始是以二进制模式创建的后期修改这个选项后场景和Prefab会重新序列化第一次提交前把这些设置统一好就行。另外如果你所在团队是主机平台或WebGL开发者建议再检查一下Project Settings - Player里的Scripting Runtime Version和API Compatibility Level这些在项目开始前确定好能减少后期升级带来的不必要diff。不过这部分跟版本控制本身关系不大属于顺手提醒。3. 项目初始化首次提交的完整操作3.1 写一份正确的.gitignore终于到了动手创建仓库的核心环节。假设你手上有一个创建好的Unity工程第一步不是急着git init而是先在工程根目录放一份正确的.gitignore。没有.gitignore直接提交的后果我在第一节已经说过了Library和Temp这些目录一旦进去后面再想剔除很麻烦。我直接把我日常使用的Unity.gitignore模板贴出来这份是根据Unity官方模板再结合实际经验调整过的[Ll]ibrary/ [Tt]emp/ [Oo]bj/ [Bb]uild/ [Bb]uilds/ [Ll]ogs/ [Uu]serSettings/ [Uu]serData/ [Uu]serLibrary/ [Mm]emoryCaptures/ [Rr]ecordings/ *.csproj *.sln *.suo *.tmp *.user *.userprefs *.pidb *.booproj *.svd *.pdb *.mdb *.opendb *.VC.db .vs/ .idea/ .vscode/ .utmp/ .ucache/逐条解释一下为什么要ignore。Library、Temp、Obj、Logs、UserSettings这五个目录属于本机状态每个人本地生成的都不一样完全没有必要提交也不应该提交它们都可以从Assets和ProjectSettings里重新生成。Build和Builds是打包输出目录打包产物不应该进仓库制品应该走专门的发布渠道。Vs和VSCode是IDE配置目录里面存的是本地调试配置也会导致不同开发者之间互相污染。需要注意的是UserSettings/里包含的EditorLayout.state是个人编辑器窗口布局有些人喜欢提交但我个人倾向于不提交因为团队每个人的屏幕尺寸、编辑器习惯不同提交了反而容易让别人的布局被覆盖。3.2 安装并配置Git LFS接下来是Unity项目版本控制的另一个核心Git LFS。LFS的全称是Large File Storage简单理解就是GitHub官方提供的大文件存储方案它把真正的大文件内容替换成一个指针存在仓库里大文件本体存放在LFS服务器端。这样克隆仓库时不会把所有历史版本的大文件都拉到本地仓库体积和操作速度都能得到显著改善。安装LFS很简单去Git官网下载Git LFS安装包或者如果你用了Homebrew直接brew install git-lfs装完先在任意目录跑一次全局初始化git lfs install然后在Unity工程根目录下执行git lfs track *.psd git lfs track *.png git lfs track *.jpg git lfs track *.jpeg git lfs track *.tga git lfs track *.tif git lfs track *.tiff git lfs track *.gif git lfs track *.mp4 git lfs track *.mov git lfs track *.avi git lfs track *.fbx git lfs track *.blend git lfs track *.obj git lfs track *.max git lfs track *.dae git lfs track *.3ds git lfs track *.wav git lfs track *.mp3 git lfs track *.ogg git lfs track *.aiff git lfs track *.flac git lfs track *.dll git lfs track *.exe git lfs track *.app git lfs track *.so git lfs track *.jar git lfs track *.bin git lfs track *.bytes git lfs track *.asset注意最后这行*.asset需要斟酌。普通脚本创建的ScriptableObject的asset文件是文本不需要LFS但项目如果做了Addressables或AssetBundle相关的asset资源体积可能会很大可以考虑纳入LFS。我的建议是先只跟踪明确的大体积二进制格式等确实遇到大体积asset时再单独加。执行完LFS track之后根目录下会生成一个.gitattributes文件这个文件记录了哪些路径被LFS跟踪务必提交到仓库里。你可以在项目里打开看一下检查内容是否正确。3.3 首次提交与推送到GitHub准备工作做完就可以正式初始化仓库了。在Unity工程根目录打开Git Bash依次执行git init git add . git commit -m chore: 初始化Unity项目配置Git和LFS git branch -M main git remote add origin gitgithub.com:你的用户名/你的仓库名.git git push -u origin main执行到git add .的时候建议先跑一下git status看一眼暂存清单确认里面没有出现Library、Temp这种不该进来的文件。如果发现有回头检查.gitignore是否生效。首次提交信息我个人习惯用chore:前缀标示这是一次工程初始化不涉及具体功能。推送成功后去GitHub仓库页刷新应该能看到完整的Unity工程文件。这里要提醒一下首次推送大工程时如果LFS里面有很多大文件可能需要等一段时间期间千万不要中途按CtrlC。如果网络不好导致推送失败可以试着重跑一遍git pushgit支持断点续传。4. 日常开发流分支模型、提交规范与代码审查4.1 分支策略怎么选仓库建好了接下来是日常协作里最核心的问题分支怎么用。很多人小团队或单人开发图省事直接在main上push代码这样做短期内没问题但只要两个人同时开发不同功能提交历史就会纠缠不清回退、排查问题都很麻烦。我推荐绝大多数Unity团队采用GitHub Flow分支模型简单高效足够支撑多数项目。核心规则只有几条main分支永远保持可运行、可发布状态。任何新功能、Bug修复、实验性改动都基于最新的main拉一个feature分支。分支命名建议带上类型和用途比如feature/player-movement、fix/ui-button-response、test/lighting-shader。功能开发完成、自测通过后通过Pull Request合回main合回之前做代码评审。如果你的项目处于中后期迭代节奏变得很快一次发布要同时管理多个版本可以考虑在GitHub Flow基础上引入release分支。比如从main拉出release/2.0.0工程师在release分支上做最后修复main继续向下一个版本开发。但Unity项目一般不建议搞太复杂的Git Flow结构因为场景和资源合并成本高分支生命周期越长最终合并时冲突越惨。实际项目里最常见的坑是一个人拉着feature分支开发了三个星期期间main已经被别人推进了几十个commit等到合并的时候场景文件、灯光文件、烘焙数据冲突成一片理都理不清。所以Unity项目做长生命周期功能分支时一定要定期把main的更新合并回feature分支保持feature分支离main尽量近。这个习惯能极大降低后续冲突的爆炸概率。4.2 统一提交信息与PR流程提交信息的规范化在Unity项目团队里尤其重要。因为Unity的YAML资源文件diff可读性差如果提交信息再写得含糊其辞过两周回头看根本无从下手。我给团队定的规范通常是feat新功能。比如feat: 实现角色冲刺机制。fix修复问题。比如fix: 修复连续跳跃时动画卡顿。refactor重构不改功能只改结构。比如refactor: 将战斗逻辑拆解为状态机。docs文档变更。style代码格式、空格、分号等不影响逻辑的改动。perf性能优化。chore构建配置、工具链、依赖调整等杂项。规则不复杂但统一要求之后git历史看起来会非常舒服。另外建议强制要求每个PR只做一件事不要在一个PR里一边修Bug一边加新功能。Unity场景文件对改动粒度的敏感程度比普通代码高得多改动混在一起出问题后定位、回滚都极其痛苦。PR流程方面Unity项目除了让写代码的人看代码逻辑还应该让场景相关的负责人仔细核对资源提交范围。我经常在评审时看到有人顺手把整个场景文件提交了里面包含了一堆本地Debug用的临时物体这种误操作在混合了代码和资源的Unity项目里太常见了。评审时拿diff软件仔细看.unity文件的变更内容比嘴上喊着“注意安全”管用一百倍。5. 场景合并冲突的终结方案5.1 为什么Unity场景一合并就爆炸代码文件冲突大多数情况下都还算好解决因为diff工具能直观展示哪些行被改了。Unity场景文件就完全是另一个物种。哪怕把序列化改成文本一个大型场景文件动辄几万行YAML包含成千上万个物体的m_FileID、m_LocalPosition、m_Component引用。两个人同时往场景里各放一个道具冲突区域可能相隔千里但Git依然会把整个文件标记为conflict。更深层的麻烦在于场景里很多字段是Unity自动维护的比如光照烘焙数据、导航网格数据、遮挡剔除数据这些字段在visual元素没变甚至没有人为改动的情况下也可能因为烘焙时机的不同而产生变化。这就导致一个很无语的现状A只调了UI按钮文字B只动了地形两边merge时依然会冲突而且冲突内容是一堆难以人工裁决的GUID和哈希值。想避免场景冲突最有效的手段是限流而非解决。具体做法就是把场景拆小不要用一个大场景承载全部游戏内容按区域、按系统拆成多个预制体Prefab然后把Prefab放到场景中引用。这样不同人开发不同子系统的时候操作的是不同Prefab冲突概率呈指数级下降。我在团队的规范里明确要求场景里只放“组装对象”和“场景特有逻辑”所有可复用的实体、UI、交互物体一律做成Prefab。5.2 UnityYAMLMerge智能合并配置尽管做足了预防场景冲突还是无法完全避免。这时候就要请出Unity官方提供的智能合并工具UnityYAMLMerge了。UnityYAMLMerge是Unity编辑器自带的一个命令行工具它比Git内置的diff合并聪明在哪里它可以识别YAML结构准确理解Unity场景、Prefab、Animator Controller这些资源文件的结构化特性能在冲突时尽量把双方的不同修改合并到最终文件中而不是像普通文本合并那样简单粗暴地标记冲突。要使用UnityYAMLMerge在Git配置里把它设为mergetool即可。Windows下通常这样配置git config --global merge.tool unityyamlmerge git config --global mergetool.unityyamlmerge.trustExitCode false git config --global mergetool.unityyamlmerge.cmd C:/Program Files/Unity/Hub/Editor/2021.3.11f1/Editor/Data/Tools/UnityYAMLMerge.exe merge -p \\$BASE\ \\$REMOTE\ \\$LOCAL\ \\$MERGED\注意UnityYAMLMerge的路径要替换成你本机安装的Unity版本对应路径。Mac上的路径一般是/Applications/Unity/Hub/Editor/2021.3.11f1/Unity.app/Contents/Tools/UnityYAMLMerge。配置好后当git merge遇到场景冲突时执行git mergetoolGit会自动用UnityYAMLMerge打开冲突文件工具会尽量把两边的修改都综合进结果文件里。这一步跑完之后如果工具返回成功说明合并基本完成你只需要在Unity里打开场景检查一下关键物件是否完整确认没被误删或错位然后git add保存合并结果。需要提醒的是UnityYAMLMerge并不是万能药。它在处理“同一场景同一物体的同一属性被两边改成不同值”这类冲突时依然无能为力这种情况它会退出你还是得手工介入。另外它处理嵌套Prefab和Overrides相关的冲突时体验一般因为Prefab Overrides在文件里的表达极其复杂。5.3 需要手动处理时怎么办如果某次场景冲突严重到UnityYAMLMerge也hold不住就只能手工解决了。步骤大致如下先用任意编辑器打开冲突的场景文件搜索。你会看到Git标出的冲突区块。接下来根据冲突内容做判断如果冲突标记内的两边改动只是新增了不同物体且没有互相引用就手动把两边内容都保留删掉冲突标记如果改动集中在同一个物体上就需要你根据产品需求决定保留哪一边的版本或者手动把两边的修改合并成一个最终结果。手动处理场景冲突非常耗时我有一次处理一个大型地编场景的冲突光是最重要的地形层就花了一个下午。所以再次强调能拆Prefab就拆Prefab能让一个人专职负责某一个场景就绝不两个人同时碰同一个场景。这是Unity团队协作的底层逻辑不要指望合并工具能拯救一切。6. 常见问题与排查技巧实录6.1 Library文件夹被提交了怎么办这是新手最常见的事故。一旦Library进了仓库仓库体积和提交记录都会被污染。处理办法是先把Library从Git索引中移除但保留本地文件。git rm -r --cached Library同时确认.gitignore里已经有[Ll]ibrary/然后提交一条清理记录git commit -m chore: 移除误提交的Library缓存目录更新.gitignore推送远端。接着让所有同事做一次拉取并在本地执行git rm -r --cached Library再commit。多写一步的目的是确保远端仓库历史中不再追踪Library文件避免后续每个开发者本地还残留着旧索引。这里有个要点清理后第一次提交diff会非常大因为是从远端仓库删除大量文件这是正常现象。不要让团队成员在删除过程中手抖把本地真实的Library给删了。只要大家本地的Library都还在Everything正常。6.2 .meta文件丢失导致引用错乱.meta文件丢失通常是因为克隆或拉取时忽略meta、设置错误导致meta没有被提交或者某些开发者本地的人为删除。在这些情况下Unity会在打开项目时自动补一个新meta但文件GUID和原版不同所有引用该资源的物体都会断掉连接。如果meta丢失发生在你还没有commit的情况下最简单的恢复方法是从Git历史中找回原meta文件git checkout HEAD -- 路径/文件名.meta如果是要找回一个较早历史版本的meta可以用git log --oneline找到那一版提交再用git checkout commit_id -- 路径/文件名.meta。找回后提交即可。如果meta文件彻底不存在且Git历史里也没有记录那就只能手动重新关联引用了这个就很痛苦因为场景里所有连接到该资源的组件GUID都要改不建议手工操作能重做资源就重做资源。防止这类事故的最有效方法还是那句话Unity里设置成Force Text Visible Meta Files并且保证这些meta文件进入Git仓库团队里任何人不要手动删除meta文件。6.3 LFS配额不够怎么办GitHub对LFS有免费配额限制具体数值不同时期会调整但大致在存储1GB、带宽每月1GB这个量级。一旦超了仓库推送就会报LFS quota exceeded之类的错误。解决办法有两个方向。第一个是清理历史中的LFS对象重写历史释放存储空间但这个操作会影响所有人要在团队拉新代码之前统一协调。第二个是升级GitHub账号的LFS套餐花钱买空间适用于确实需要长期托管大量大文件的团队。更治本的做法是不要什么大文件都往LFS里扔。图片纹理这类资源最好在导入Unity前就完成压缩以更小的格式入仓库模型文件做减面处理音频尽量转成压缩格式。Unity项目本身有压缩设置合理配置能省下大量体积。另外已经在LFS里的大体积文件如果确定不再使用也要及时用LFS命令移除。6.4 GitHub连接不稳定时的备案措施这个相信不少开发者都遇到过git push、git clone的时候连接GitHub偶尔会超时、掉线甚至直接拒绝连接。因为是网络问题不同网络环境下表现差异非常大有时换一个网络就好了有时需要重试多次。我的日常做法是把项目的远端同时配置为两个仓库一个放在GitHub一个放在其他可选的代码托管平台作为备份和同步中转。具体执行是在项目根目录添加多个remotegit remote add origin gitgithub.com:你的用户名/仓库名.git git remote add backup gitgitee.com:你的用户名/仓库名.git平常开发两个远端都推git push origin main git push backup main这样即使GitHub临时连不上代码也还完整保存在另一个远端和本地不会影响开发节奏。等GitHub恢复后再补推一次就行。如果再遇到连接问题可以试试更换DNS为公共DNS或在网络相对空闲的时段重试刚发布或大流量时段GitHub确实更容易不稳定。还有一个实用小技巧如果你为了下载某个开源仓库的压缩包而连不上GitHub很多代码托管平台都提供“仓库导入”功能直接把GitHub仓库的一键导入到那个平台再下载速度通常会好很多。日常开发中尽量呆在本地分支操作不要频繁依赖远程仓库这样即使网络不好本地开发流程也不受影响。最后说点真心话如果你刚开始给Unity项目配Git我的的建议是这一整套流程看着步骤多其实核心就三件事一是设置好Force Text和Visible Meta Files二是写对.gitignore并配好LFS三是坚持用分支开发和Pull Request场景里能拆Prefab就拆Prefab。我早期在unity项目上翻过的最大的车几乎全是这三件事没做好酿成的。把这套流程走一遍之后你就有了可以放心提交代码、协作开发、随时回滚的工程基础。再往后还能继续演化比如接入GitHub Actions做自动化构建、配置CI/CD流水线、做版本发布管理这些都是在这个地基上长出来的能力先把今天这一套基础打牢比什么都重要。