1. 项目概述为什么我们需要告别Git Submodule如果你在一个Unity团队里待过一段时间尤其是项目规模稍微大一点或者需要复用一些自己写的工具、Shader、UI组件那你大概率被Git Submodule折磨过。我经历过而且不止一次。那种感觉就像是你家里有一套非常精密的乐高积木每次想从仓库里拿一个新的模块出来都得小心翼翼地拆开一个已经拼好的部分生怕弄乱了其他结构。Git Submodule就是那个“精密但脆弱”的连接件。简单来说Git Submodule允许你将一个Git仓库作为另一个Git仓库的子目录。听起来很美对吧一个主项目里面嵌套着若干个独立的工具库、资源库。但实操起来问题一大堆新同事克隆项目后得额外执行git submodule update --init --recursive而且网络一波动这个命令就可能卡住没响应直接劝退新人。更头疼的是版本管理主项目提交了但子模块的更新可能忘了提交或者子模块更新了但主项目记录的还是旧版本导致团队成员的本地环境不一致编译错误、资源丢失成了家常便饭。本质上Git Submodule将代码的依赖关系与版本管理强耦合这在一个需要快速迭代、灵活配置的游戏开发环境中显得格外笨重。那么Unity官方给出的现代解决方案是什么是UPMUnity Package Manager。它就像Unity内置的“应用商店”让你可以像安装TextMeshPro、Cinemachine一样以包的形式管理你的内部资产。干净、统一、版本清晰。但问题来了官方的Package Manager只对接Unity官方的注册表我们团队的私有工具包、美术资源包、策划配置表模板这些商业机密显然不能往上放。所以这个项目的核心目标就出来了搭建一个团队内部私有的、类UPM的资产商店。让团队成员能像在Asset Store里点击“Import”一样一键获取内部的最新工具同时享受UPM带来的清晰依赖管理和版本控制。而Verdaccio一个轻量级的私有npm仓库工具正是实现这个目标的关键桥梁。它让我们可以架设一个内部的包服务器然后通过简单的配置让Unity的Package Manager指向它从而无缝接入我们自己的“资产生态”。这不仅仅是技术工具的升级更是团队协作流程的一次重要优化。2. 核心方案选型为什么是Verdaccio UPM面对内部资产管理的难题社区里有过不少尝试。除了Git Submodule还有直接用Git URL的com.company.toolkit: gitgithub.com:company/unity-toolkit.git或者把整个工具库复制到每个项目里。但这些方案都有明显的短板。Git URL方式在离线或仓库权限变更时很麻烦且无法享受UPM的版本锁定和更新提示。直接复制则完全背离了代码复用的初衷维护成本是灾难级的。Verdaccio UPM的组合几乎是为Unity团队量身定制的解决方案。我们来拆解一下这两个核心组件Verdaccio本质上是一个Node.js写的npm私有仓库代理。它轻量可以跑在你的一台内网服务器甚至开发机上、配置简单、支持用户认证和权限管理。最关键的是它完美实现了npm的包发布、拉取协议。对于Unity UPM来说只要一个仓库服务器支持特定的JSON格式package.json和upm.json等它就能被识别为包源。Verdaccio通过插件可以轻松适配这个格式。UPM (Unity Package Manager)从2018.3版本开始逐步成为Unity的官方包管理标准。它通过一个名为manifest.json的文件位于项目根目录的Packages文件夹内来管理项目对所有包的依赖。UPM的核心优势在于依赖解析自动处理包与包之间的依赖关系避免冲突。版本锁定manifest.json里记录的是确切的版本号或版本范围确保团队环境一致。干净隔离包被安装在项目的Library/PackageCache中不会污染项目Assets目录结构清晰。更新便捷在Package Manager窗口里可以直观地查看更新、进行升级。我们的技术方案路径因此非常清晰将团队内部的Unity资产脚本、预制体、Shader、Editor工具等打包成符合UPM规范的.tgz包发布到我们自己搭建的Verdaccio私有服务器上。然后在Unity编辑器中添加这个Verdaccio服务器地址作为自定义的包源Scoped Registry。从此团队成员就可以在Package Manager窗口里浏览、搜索、一键安装或更新所有内部资产了。这个方案的优势是压倒性的开发者体验极佳告别复杂的Git命令可视化操作。运维成本低Verdaccio部署简单UPM是Unity原生支持。流程标准化包的开发、版本、发布、消费有了统一规范。良好的扩展性未来可以很方便地接入CI/CD实现自动化打包和发布。注意在方案选型初期你可能会看到“npm”、“cnpm”等词。这里务必明确我们只是利用Verdaccio对npm协议的支持来“模拟”一个UPM服务器。我们最终制作和消费的是Unity Package而不是Node.js的npm包。理解这一点对后续配置至关重要。3. 环境准备与工具链搭建工欲善其事必先利其器。在开始动手之前我们需要准备好整个工具链。这个过程就像组装一台模型零件齐备后面才能顺畅。3.1 基础软件安装首先确保你的开发机上已经安装了以下软件Node.js npmVerdaccio运行所必需。建议安装LTS长期支持版本比如18.x或20.x。去Node.js官网下载安装包即可安装后会自带npm。验证安装打开终端或PowerShell、CMD输入node -v和npm -v能显示版本号即成功。Git用于管理你的资产包源代码。这个想必大家都有。Unity Hub Unity Editor建议使用2019.4或更高版本对UPM的支持更完善。本项目以主流的2022.3 LTS版本为例进行说明。3.2 安装与配置VerdaccioVerdaccio的安装非常简单通过npm一条命令即可完成。我强烈建议在用于部署的服务器或长期开机的内网机器上进行全局安装。# 使用npm全局安装Verdaccio npm install -g verdaccio6安装完成后直接在命令行输入verdaccio并回车Verdaccio服务就会启动。你会看到类似下面的输出其中包含了服务运行的地址通常是http://localhost:4873和配置文件路径。info --- Verdaccio started info --- Plugin successfully loaded: verdaccio-htpasswd info --- Plugin successfully loaded: verdaccio-audit info --- http address - http://localhost:4873/ - verdaccio/6.0.6第一次运行后它会在用户目录下生成配置文件如C:\Users\你的用户名\.config\verdaccio\config.yaml或~/.config/verdaccio/config.yaml。我们需要修改这个配置文件来满足我们的需求。用文本编辑器打开这个config.yaml文件找到并修改以下几个关键部分# 监听所有网络接口这样局域网内其他机器才能访问 listen: 0.0.0.0:4873 # 关键配置设置上游仓库。因为我们不是做npm代理而是纯粹的私有库所以可以把默认的npmjs源注释掉或移除避免不必要的网络请求和混淆。 packages: */*: access: $all publish: $authenticated unpublish: $authenticated # proxy: npmjs # 注释或删除这一行 **: access: $all publish: $authenticated unpublish: $authenticated # proxy: npmjs # 注释或删除这一行 # 认证插件使用内置的htpasswd即可 auth: htpasswd: file: ./htpasswd max_users: -1 # 允许任意数量用户注册 # 日志输出级别开发阶段可以设为debug生产环境建议info logs: { type: stdout, format: pretty, level: debug }保存配置文件后重启Verdaccio。现在你的私有包仓库就已经在http://你的机器IP:4873上运行起来了。打开浏览器访问这个地址你应该能看到Verdaccio的Web界面。3.3 创建第一个Unity UPM包接下来我们要创建一个符合UPM规范的包。Unity包的核心是一个package.json文件它描述了包的元信息。创建一个新的文件夹作为你的包根目录例如MyCompany.CoreLib。在该文件夹内创建以下结构和文件MyCompany.CoreLib/ ├── package.json ├── README.md ├── CHANGELOG.md └── Runtime/ └── MyCompany/ └── CoreLib/ ├── Editor/ │ └── ExampleEditorTool.cs └── Runtime/ └── ExampleComponent.cs目录结构解释Runtime和Editor是UPM约定的特殊文件夹。Runtime下的内容会在游戏运行时加载Editor下的内容仅在Unity编辑器中加载。将你的脚本、预制体等资源按此规范放置。编辑package.json文件这是包的心脏{ name: com.mycompany.corelib, displayName: My Company Core Library, version: 1.0.0, unity: 2022.3, description: A core library containing common utilities and extensions for our projects., keywords: [utility, tool, mycompany], category: Tools, dependencies: { com.unity.nuget.newtonsoft-json: 3.2.1 }, author: { name: Your Name, email: devmycompany.com } }name包的唯一标识符必须采用小写并以com.公司名.包名的格式命名这是UPM的推荐规范。version遵循语义化版本控制SemVer例如主版本.次版本.修订号。unity指定兼容的Unity版本。dependencies声明此包所依赖的其他UPM包。这里示例依赖了Unity官方提供的Newtonsoft Json包。实操心得在包开发的初期我建议先在本地用file:协议进行测试。即在你的Unity项目的Packages/manifest.json文件中像这样添加依赖com.mycompany.corelib: file:../../本地路径/MyCompany.CoreLib。这样可以快速迭代调试包的内容和结构无需反复发布到Verdaccio。4. 发布包到私有Verdaccio仓库当你的包在本地测试无误后就可以准备发布到团队内部的Verdaccio仓库了。这个过程和向npm发布包非常相似。4.1 配置npm以指向Verdaccio首先你需要告诉本地的npm默认的发布仓库是你刚搭建的Verdaccio而不是公网的npmjs。打开终端执行以下命令# 设置npm的注册表地址为你的Verdaccio服务器 npm set registry http://你的服务器IP:4873 # 为了安全可以为特定作用域scope设置注册表更推荐这种方式 npm config set mycompany:registry http://你的服务器IP:4873第二条命令的意思是所有以mycompany开头的包如mycompany/corelib其发布和安装都指向你的私有仓库。这与我们UPM包名com.mycompany.corelib的规范能很好地对应虽然UPM包名不是npm作用域但Verdaccio通常能很好处理。4.2 在Verdaccio上创建用户并登录在浏览器中打开Verdaccio的Web界面http://服务器IP:4873点击右上角的“Register”注册一个新用户比如用户名team_dev。然后在终端中使用这个用户登录到你的私有仓库npm login --registryhttp://你的服务器IP:4873根据提示输入用户名、密码和邮箱。登录成功后你的认证信息会保存在本地。4.3 打包并发布进入你的UPM包根目录MyCompany.CoreLib执行发布命令# 确保你在包的根目录并且package.json中的version是新的比如1.0.0 npm publish如果一切顺利终端会显示成功发布的信息。刷新Verdaccio的Web界面你应该能在包列表里看到com.mycompany.corelib。这里有一个至关重要的细节标准的npm包发布时会忽略package.json中files字段未指定的文件或者根据.npmignore来忽略。但Unity UPM包必须包含完整的目录结构。为了确保所有必要文件都被发布你有两个选择在包根目录创建一个.npmignore文件并在里面仅写入以下内容# 忽略所有文件 # 然后显式地取消忽略我们需要的文件和文件夹 !package.json !README.md !CHANGELOG.md !Runtime/** !Editor/** !Tests/** !Samples~/** # 注意示例文件夹的命名有特殊要求或者在package.json中显式指定files字段更推荐{ ... // 其他字段 files: [ package.json, README.md, CHANGELOG.md, Runtime, Editor, Tests, Samples~ ] }使用files字段能更精确地控制发布内容。踩坑记录我第一次发布时忘了配置这些忽略规则结果发布上去的包只有package.json一个文件Unity完全识别不了。务必检查Verdaccio上包的“版本详情”确认tgz文件里包含了Runtime等目录。5. 在Unity项目中配置与使用私有包源包已经安静地躺在我们的私有仓库里了下一步就是告诉Unity“嘿除了官方商店我还有一个自己的宝库去那里看看。”5.1 修改Unity项目的manifest.json每个Unity项目都有一个Packages/manifest.json文件。我们需要手动编辑它添加我们的私有包源Scoped Registry和依赖。用文本编辑器打开项目根目录/Packages/manifest.json。在顶层对象中找到或添加scopedRegistries和dependencies字段。{ scopedRegistries: [ { name: MyCompany Internal, url: http://你的服务器IP:4873, scopes: [ com.mycompany ] } ], dependencies: { com.mycompany.corelib: 1.0.0, com.unity.collab-proxy: 2.0.5, ... // 其他已有的包依赖 } }scopedRegistries定义了自定义的包源。name给这个源起个名字在Unity编辑器中会显示。url你的Verdaccio服务器地址。scopes这是关键。它告诉Unity哪些包名的前缀应该从这个源查找。这里我们写了com.mycompany意味着所有以com.mycompany.开头的包Unity都会去http://你的服务器IP:4873寻找。dependencies在依赖里像引用官方包一样写上我们私有包的名字和版本号com.mycompany.corelib: 1.0.0。保存manifest.json文件。5.2 在Unity编辑器中验证与操作回到Unity编辑器它会自动检测到manifest.json的更改并开始刷新包数据库。打开Window Package Manager。在左上角的下拉菜单中选择“My Packages”或“In Project”。你应该能在列表里看到“My Company Core Library”这个包并且它的来源显示为我们自定义的“MyCompany Internal”。如果包没有自动安装状态可能是“Not installed”你可以点击它然后在右侧详情页点击“Install”按钮。安装成功后你可以在项目的Packages目录下看到它以只读形式存在而你写的ExampleComponent.cs等脚本就可以在项目中正常使用了。Package Manager窗口的高级用法更新当Verdaccio仓库中的包发布了新版本如1.0.1在Package Manager里该包右侧会显示一个“Update”按钮。版本选择你可以点击包名左侧的小箭头选择安装特定的版本。查看依赖在包详情里可以看到它声明的依赖关系。5.3 处理身份认证如果需要如果你的Verdaccio配置了强制认证publish: $authenticated且access: $authenticated那么Unity在拉取包时也需要身份信息。Verdaccio使用的是HTTP Basic Auth。你需要在manifest.json的scopedRegistries的配置中以以下格式添加认证信息{ scopedRegistries: [ { name: MyCompany Internal, url: http://你的服务器IP:4873, scopes: [com.mycompany], config: { auth: { username: team_dev, password: your_plain_text_password // 注意这里是明文密码 } } } ], ... // dependencies }重要安全警告将明文密码写入manifest.json并提交到Git仓库是极不安全的因为所有有仓库访问权限的人都能看到密码。在生产环境中有几种更安全的做法使用只读令牌在Verdaccio上为用户生成一个只有access权限没有publish权限的令牌。环境变量或本地覆盖不将config.auth字段提交到仓库而是通过本地的manifest.json覆盖机制或者由CI/CD流程在构建时动态注入。网络隔离将Verdaccio服务器部署在内网并配置IP白名单仅允许公司网络访问从而完全取消认证access: $all。这是最简单也最常用的团队内部方案。6. 进阶配置与自动化实践基础流程跑通后我们可以追求更高效、更规范的协作方式。这部分内容能让你团队的资产商店从“能用”进化到“好用”。6.1 使用SemVer与CHANGELOG进行版本管理语义化版本控制SemVer是包管理的基石。严格遵守主版本.次版本.修订号的规则修订号1.0.x向后兼容的问题修复。团队成员可以安全地更新到最新的修订版。次版本1.x.0向后兼容的功能性新增。团队成员可以评估后更新。主版本x.0.0包含不兼容的API变更。更新时需要谨慎可能涉及代码修改。每次发布新版本时务必更新package.json中的version字段并认真编写CHANGELOG.md。一个规范的CHANGELOG能让团队成员一目了然地了解这次更新做了什么。推荐使用类似“Keep a Changelog”的格式。6.2 集成CI/CD实现自动化发布手动执行npm publish容易出错且效率低。我们可以使用GitHub Actions、GitLab CI或Jenkins等工具实现自动化。核心思路是当开发者向资产包仓库的特定分支如main推送标签如v1.1.0时CI流程自动触发执行以下步骤检出代码。验证package.json中的版本号与Git标签是否一致。运行包的单元测试如果有。执行npm publish需要预先在CI环境配置好npm认证。一个简化的GitHub Actions工作流示例.github/workflows/publish.ymlname: Publish UPM Package on: push: tags: - v* # 当推送v开头的标签时触发 jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - uses: actions/setup-nodev4 with: node-version: 20 registry-url: http://你的verdaccio服务器:4873 - run: npm publish env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} # 在仓库Settings中配置的Secret你需要先在Verdaccio上生成一个发布用的Access Token并将其作为NPM_TOKEN存储在GitHub仓库的Secrets中。6.3 组织多包与处理包间依赖随着团队资产增多你会有多个包并且它们之间可能存在依赖关系。例如一个com.mycompany.uikit包可能依赖于com.mycompany.corelib。这完全在UPM的支持范围内。只需要在uikit包的package.json中声明依赖即可{ name: com.mycompany.uikit, version: 1.0.0, dependencies: { com.mycompany.corelib: 1.2.0 } }当在Unity项目中安装com.mycompany.uikit时UPM会自动解析并安装其依赖的com.mycompany.corelib的指定版本。这解决了Git Submodule时代需要手动管理嵌套依赖的噩梦。最佳实践建议建立团队内部的包命名规范。例如com.mycompany.core- 核心运行时库com.mycompany.editor-tools- 编辑器扩展工具集com.mycompany.shaders- 通用Shader集合com.mycompany.art-utilities- 美术资源导入/处理工具7. 常见问题与排查技巧实录在实际搭建和使用过程中你肯定会遇到各种“坑”。下面是我和团队踩过的一些典型问题及解决方法希望能帮你节省大量排查时间。7.1 Unity无法找到或安装私有包症状在Package Manager里看不到包或者看到但显示为“Error”或“Not found”。排查步骤检查manifest.json配置确保scopedRegistries的url和scopes拼写绝对正确。scopes里的com.mycompany末尾没有斜杠url的IP和端口无误。验证网络连通性在浏览器中直接访问http://你的服务器IP:4873/com.mycompany.corelib应该能下载到一个.tgz文件。如果不能说明Verdaccio服务或网络有问题。检查包是否成功发布在Verdaccio的Web界面搜索你的包名确认版本存在。清除Unity包缓存关闭Unity删除项目目录下的Library/PackageCache和Library/ScriptAssemblies文件夹然后重启Unity。这能解决很多诡异的缓存问题。查看Unity编辑器日志打开Editor.log文件Windows在%LOCALAPPDATA%\Unity\Editor\Editor.log搜索你的包名或Verdaccio的URL看是否有错误信息。7.2 发布包时出现权限错误E403/E401症状执行npm publish时提示403 Forbidden或401 Unauthorized。解决方法确认登录状态运行npm whoami --registryhttp://你的服务器IP:4873检查当前登录的用户是否有发布权限。重新登录执行npm logout --registryhttp://你的服务器IP:4873然后重新npm login。检查Verdaccio配置确认config.yaml中对应包作用域的publish权限设置为$authenticated或更具体的用户/用户组。检查.npmrc文件用户目录下的.npmrc文件可能包含了冲突的注册表配置。可以临时删除它或者使用--registry参数显式指定。7.3 包内容不完整或结构错误症状包能安装但脚本丢失、编辑器工具不显示或者Unity报错说找不到类型。排查步骤检查包的实际内容从Verdaccio Web界面下载发布的.tgz文件解压后查看内部结构。是否包含了Runtime,Editor等关键文件夹package.json文件是否在根目录验证.npmignore或files字段这是最可能出问题的地方。确保你没有无意中忽略了关键目录。一个快速测试方法是在包目录运行npm pack这会生成一个本地.tgz文件而不发布解压它来检查内容。检查脚本命名空间和程序集定义确保你的C#脚本放在了正确的命名空间下。如果包内使用了程序集定义.asmdef请确保其配置正确特别是其引用的其他程序集。7.4 版本依赖冲突症状安装或更新包时Unity提示依赖冲突例如两个包要求同一个依赖包的不同版本。解决方法理解UPM的依赖解析UPM会尝试找到一个能满足所有依赖项版本要求的版本。如果找不到就会报冲突。升级或降级包尝试将发生冲突的某个包升级或降级到一个与其他包依赖更兼容的版本。使用版本范围在包的package.json中声明依赖时可以使用语义化版本范围如com.mycompany.corelib: ^1.0.0兼容1.0.0及以上但低于2.0.0这给了UPM更大的解析灵活性。根本解决协调内部包的开发者统一核心依赖库的版本或者将公共功能进一步拆分成更小、更稳定的包。7.5 Verdaccio服务器维护与数据备份症状服务器磁盘满了、进程挂了或者需要迁移服务器。维护要点数据目录Verdaccio存储包数据storage和用户认证文件htpasswd的路径在config.yaml中指定默认在安装目录下。定期备份这个storage文件夹。进程管理在生产环境不要直接用verdaccio命令在前台运行。使用pm2、systemd或docker来管理进程保证其稳定运行和开机自启。日志查看Verdaccio的日志输出在控制台也可以通过配置写入文件。定期查看日志能及时发现错误或异常访问。磁盘监控私有包仓库会随着时间增长务必监控服务器磁盘使用情况。从Git Submodule迁移到VerdaccioUPM的私有资产商店初期确实需要一些学习和配置成本。但一旦这套流程运转起来你会发现它为团队协作带来的效率提升和心智负担的降低是巨大的。它标准化了资产的交付方式让复用变得简单可靠让版本管理一目了然。我个人最大的体会是它把我们从“代码搬运工”和“依赖调解员”的角色中解放了出来让我们能更专注于创造本身。如果你还在被Submodule困扰或者苦于内部资产共享的混乱强烈建议你花一个下午的时间搭起这套系统你的团队会感谢你的。