unity-package-management
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseUnity Package Management (headless, via the C# Client API)
Unity 包管理(无头模式,基于C# Client API)
Add, remove, upgrade, and discover UPM (Unity Package Manager) packages programmatically with
, driven headless from the terminal or CI. Do not
hand-edit — the Client API resolves dependencies and compatible
versions correctly, whereas manual edits routinely break resolution.
UnityEditor.PackageManager.ClientPackages/manifest.jsonThis complements the skill (editor install, project creation, build/test): the
CLI has no package-management command, so all package work goes through the Editor's C# API.
unity-cli通过以编程方式添加、移除、升级和发现UPM(Unity Package Manager)包,可从终端或CI环境以无头模式运行。请勿手动编辑——Client API能正确解析依赖项和兼容版本,而手动编辑通常会破坏解析流程。
UnityEditor.PackageManager.ClientPackages/manifest.json本技能是对****技能(编辑器安装、项目创建、构建/测试)的补充:CLI没有包管理命令,因此所有包操作都需通过编辑器的C# API完成。
unity-cliWhen to use
使用场景
- Add / remove / upgrade one or more packages in an existing or freshly-created project.
- Set up a project's packages non-interactively in CI.
- Verify a package id exists, or find its available versions, before depending on it.
- Decide which packages a game actually needs — see references/select-packages.md.
- 在现有或新建项目中添加/移除/升级一个或多个包。
- 在CI环境中以非交互方式设置项目的包。
- 在依赖某个包之前,验证其ID是否存在,或查找其可用版本。
- 确定游戏实际需要哪些包——详见references/select-packages.md。
Choosing what to install
选择要安装的包
Install what the project actually needs, not everything; prefer packages the chosen template
already provides (URP templates already include the render pipeline, Input System, etc.). The
genre / look / platform / monetization → package mapping, plus how to search the registry, is
in references/select-packages.md. Produce a deduplicated
list of package ids and read it back to the user before installing.
仅安装项目实际需要的包,而非全部;优先选择所选模板已提供的包(URP模板已包含渲染管线、Input System等)。游戏类型/风格/平台/商业化模式与包的映射关系,以及如何搜索注册表的方法,均在references/select-packages.md中。生成一份去重后的包ID列表,并在安装前告知用户。
The -quit
problem — why NOT unity run
for installs
-quitunity run-quit
问题——为何不能用unity run
进行安装
-quitunity runClient.AddClient.AddAndRemoveRequestEditorApplication.updatewhile (!req.IsCompleted)-executeMethodunity run-quitunity-cli-quitSolution: launch the Editor binary directly in without . The
Editor stays alive, keeps ticking, the poll callback runs, and it
calls itself when done — which both quits and sets the process
exit code.
-batchmode-quitEditorApplication.updateEditorApplication.Exit(code)Client.AddClient.AddAndRemoveRequestEditorApplication.updatewhile (!req.IsCompleted)-executeMethodunity run-quitunity-cli-quit解决方案:直接启动编辑器二进制文件,使用模式且不添加参数。编辑器会保持运行,持续执行,轮询回调会运行,完成后会自行调用——这既会退出编辑器,也会设置进程退出码。
-batchmode-quitEditorApplication.updateEditorApplication.Exit(code)The installer script
安装脚本
Write this to . It must live under an
folder (or an Editor-only assembly) because it uses .
Assets/Editor/ProjectBootstrap/PackageInstaller.csEditor/UnityEditorcsharp
using System.Linq;
using UnityEditor;
using UnityEditor.PackageManager;
using UnityEditor.PackageManager.Requests;
using UnityEngine;
namespace ProjectBootstrap
{
// Installs (and optionally removes) a fixed set of packages via the PackageManager
// Client API, headless-safe.
public static class PackageInstaller
{
// EDIT this list to match the package selection (see references/select-packages.md).
static readonly string[] PackagesToAdd =
{
"com.unity.inputsystem",
"com.unity.cinemachine",
"com.unity.render-pipelines.universal",
// "com.unity.package@1.2.3" // pin a version with @ when a minimum is required
};
// Optionally drop packages in the same resolution pass (e.g. a template default you don't want).
static readonly string[] PackagesToRemove = { };
const double TimeoutSeconds = 600; // UPM resolution + downloads can be slow
static AddAndRemoveRequest _request;
static double _deadline;
// Invoke with: -executeMethod ProjectBootstrap.PackageInstaller.Install (NO -quit)
public static void Install()
{
if (PackagesToAdd.Length == 0 && PackagesToRemove.Length == 0)
{
Debug.Log("[PackageInstaller] Nothing to do.");
EditorApplication.Exit(0);
return;
}
Debug.Log($"[PackageInstaller] Adding: {string.Join(", ", PackagesToAdd)}");
_request = Client.AddAndRemove(packagesToAdd: PackagesToAdd, packagesToRemove: PackagesToRemove);
_deadline = EditorApplication.timeSinceStartup + TimeoutSeconds;
EditorApplication.update += Poll;
}
static void Poll()
{
if (_request == null) return;
if (!_request.IsCompleted)
{
if (EditorApplication.timeSinceStartup > _deadline)
{
EditorApplication.update -= Poll;
Debug.LogError("[PackageInstaller] Timed out waiting for UPM.");
EditorApplication.Exit(2);
}
return;
}
EditorApplication.update -= Poll;
if (_request.Status == StatusCode.Success)
{
var names = _request.Result.Select(p => $"{p.name}@{p.version}");
Debug.Log($"[PackageInstaller] Resolved: {string.Join(", ", names)}");
EditorApplication.Exit(0);
}
else
{
Debug.LogError($"[PackageInstaller] Failed: {_request.Error?.message}");
EditorApplication.Exit(1);
}
}
}
}AddAndRemoveClient.AddAdd / remove / upgrade with one script:
- Add: list the id in .
PackagesToAdd - Remove: list the id in .
PackagesToRemove - Upgrade / pin: add the id with (e.g.
@<version>). Without a version, resolution picks the latest compatible release.com.unity.cinemachine@2.9.7
将以下代码写入。该脚本必须放在文件夹下(或仅编辑器程序集),因为它使用了。
Assets/Editor/ProjectBootstrap/PackageInstaller.csEditor/UnityEditorcsharp
using System.Linq;
using UnityEditor;
using UnityEditor.PackageManager;
using UnityEditor.PackageManager.Requests;
using UnityEngine;
namespace ProjectBootstrap
{
// 通过PackageManager Client API安装(可选移除)一组固定包,支持无头模式。
public static class PackageInstaller
{
// 编辑此列表以匹配所选包(详见references/select-packages.md)。
static readonly string[] PackagesToAdd =
{
"com.unity.inputsystem",
"com.unity.cinemachine",
"com.unity.render-pipelines.universal",
// "com.unity.package@1.2.3" // 当需要最低版本时,使用@固定版本
};
// 可选在同一解析过程中移除包(例如不需要的模板默认包)。
static readonly string[] PackagesToRemove = { };
const double TimeoutSeconds = 600; // UPM解析和下载可能较慢
static AddAndRemoveRequest _request;
static double _deadline;
// 调用方式:-executeMethod ProjectBootstrap.PackageInstaller.Install (请勿加-quit)
public static void Install()
{
if (PackagesToAdd.Length == 0 && PackagesToRemove.Length == 0)
{
Debug.Log("[PackageInstaller] 无操作可执行。");
EditorApplication.Exit(0);
return;
}
Debug.Log($"[PackageInstaller] 正在添加:{string.Join(", ", PackagesToAdd)}");
_request = Client.AddAndRemove(packagesToAdd: PackagesToAdd, packagesToRemove: PackagesToRemove);
_deadline = EditorApplication.timeSinceStartup + TimeoutSeconds;
EditorApplication.update += Poll;
}
static void Poll()
{
if (_request == null) return;
if (!_request.IsCompleted)
{
if (EditorApplication.timeSinceStartup > _deadline)
{
EditorApplication.update -= Poll;
Debug.LogError("[PackageInstaller] 等待UPM超时。");
EditorApplication.Exit(2);
}
return;
}
EditorApplication.update -= Poll;
if (_request.Status == StatusCode.Success)
{
var names = _request.Result.Select(p => $"{p.name}@{p.version}");
Debug.Log($"[PackageInstaller] 解析完成:{string.Join(", ", names)}");
EditorApplication.Exit(0);
}
else
{
Debug.LogError($"[PackageInstaller] 失败:{_request.Error?.message}");
EditorApplication.Exit(1);
}
}
}
}AddAndRemoveClient.Add通过一个脚本实现添加/移除/升级:
- 添加:将ID列入。
PackagesToAdd - 移除:将ID列入。
PackagesToRemove - 升级/固定版本:添加带的ID(例如
@<version>)。如果不指定版本,解析过程会选择最新的兼容版本。com.unity.cinemachine@2.9.7
Discovering / verifying packages
发现/验证包
To confirm an id exists or list its versions before adding it, search the registry. The
in-Editor / calls are also async, so they use the
same poll-and- pattern and the same headless run as the installer. Write
:
Client.SearchAll()Client.Search("<id>")ExitAssets/Editor/ProjectBootstrap/PackageSearch.cscsharp
using System.Linq;
using UnityEditor;
using UnityEditor.PackageManager;
using UnityEditor.PackageManager.Requests;
using UnityEngine;
namespace ProjectBootstrap
{
public static class PackageSearch
{
const double TimeoutSeconds = 120;
static SearchRequest _request;
static double _deadline;
// Invoke with: -executeMethod ProjectBootstrap.PackageSearch.SearchAll (NO -quit)
public static void SearchAll()
{
_request = Client.SearchAll(); // or Client.Search("com.unity.cinemachine")
_deadline = EditorApplication.timeSinceStartup + TimeoutSeconds;
EditorApplication.update += Poll;
}
static void Poll()
{
if (_request == null) return;
if (!_request.IsCompleted)
{
if (EditorApplication.timeSinceStartup > _deadline)
{
EditorApplication.update -= Poll;
Debug.LogError("[PackageSearch] Timed out.");
EditorApplication.Exit(2);
}
return;
}
EditorApplication.update -= Poll;
if (_request.Status == StatusCode.Success)
{
foreach (var p in _request.Result.OrderBy(p => p.name))
Debug.Log($"[PackageSearch] {p.name}@{p.versions.latestCompatible} {p.displayName}");
Debug.Log($"[PackageSearch] {_request.Result.Length} packages found.");
EditorApplication.Exit(0);
}
else
{
Debug.LogError($"[PackageSearch] Failed: {_request.Error?.message}");
EditorApplication.Exit(1);
}
}
}
}_request.ResultPackageInfo[]namedisplayNamedescriptionversions.latest.latestCompatible.all在添加包之前确认其ID是否存在或列出其版本,可搜索注册表。编辑器内的 / 调用同样是异步的,因此它们使用与安装程序相同的轮询和模式,以及相同的无头运行方式。编写:
Client.SearchAll()Client.Search("<id>")ExitAssets/Editor/ProjectBootstrap/PackageSearch.cscsharp
using System.Linq;
using UnityEditor;
using UnityEditor.PackageManager;
using UnityEditor.PackageManager.Requests;
using UnityEngine;
namespace ProjectBootstrap
{
public static class PackageSearch
{
const double TimeoutSeconds = 120;
static SearchRequest _request;
static double _deadline;
// 调用方式:-executeMethod ProjectBootstrap.PackageSearch.SearchAll (请勿加-quit)
public static void SearchAll()
{
_request = Client.SearchAll(); // 或Client.Search("com.unity.cinemachine")
_deadline = EditorApplication.timeSinceStartup + TimeoutSeconds;
EditorApplication.update += Poll;
}
static void Poll()
{
if (_request == null) return;
if (!_request.IsCompleted)
{
if (EditorApplication.timeSinceStartup > _deadline)
{
EditorApplication.update -= Poll;
Debug.LogError("[PackageSearch] 超时。");
EditorApplication.Exit(2);
}
return;
}
EditorApplication.update -= Poll;
if (_request.Status == StatusCode.Success)
{
foreach (var p in _request.Result.OrderBy(p => p.name))
Debug.Log($"[PackageSearch] {p.name}@{p.versions.latestCompatible} {p.displayName}");
Debug.Log($"[PackageSearch] 找到{_request.Result.Length}个包。");
EditorApplication.Exit(0);
}
else
{
Debug.LogError($"[PackageSearch] 失败:{_request.Error?.message}");
EditorApplication.Exit(1);
}
}
}
}_request.ResultPackageInfo[]namedisplayNamedescriptionversions.latest.latestCompatible.allRun it headless (direct Editor invocation, no -quit
)
-quit无头模式运行(直接调用编辑器,不加-quit
)
-quitResolve the Editor binary from the version, then run it in batch mode. The script owns quitting
via , so do not pass :
EditorApplication.Exit-quitbash
VERSION="<version>" # e.g. 6000.0.47f1 (or an installed version)
PROJECT="<project-path>"
METHOD="ProjectBootstrap.PackageInstaller.Install" # or ...PackageSearch.SearchAll根据版本找到编辑器二进制文件,然后以批处理模式运行。脚本会通过自行退出,因此请勿传递参数:
EditorApplication.Exit-quitbash
VERSION="<version>" # 例如6000.0.47f1(或已安装的版本)
PROJECT="<project-path>"
METHOD="ProjectBootstrap.PackageInstaller.Install" # 或...PackageSearch.SearchAllInstall directory of that editor (Hub layout), via the unity CLI
通过unity CLI获取该编辑器的安装目录(Hub布局)
ED=$(unity editors path "$VERSION" --format json | python3 -c "import sys,json;print(json.load(sys.stdin)['data']['path'])")
ED=$(unity editors path "$VERSION" --format json | python3 -c "import sys,json;print(json.load(sys.stdin)['data']['path'])")
Resolve the executable per-OS (handles both "dir containing Unity.app" and the ".app" itself)
根据操作系统解析可执行文件(处理包含Unity.app的目录和.app本身)
case "$(uname)" in
Darwin) if [ -d "$ED/Unity.app" ]; then UNITY_BIN="$ED/Unity.app/Contents/MacOS/Unity";
elif [[ "$ED" == *.app ]]; then UNITY_BIN="$ED/Contents/MacOS/Unity";
else UNITY_BIN="$ED/Unity"; fi ;;
Linux) UNITY_BIN="$ED/Unity" ;;
*) UNITY_BIN="$ED/Unity.exe" ;; # Windows (Git Bash / MSYS); use Unity.exe in PowerShell
esac
"$UNITY_BIN" -batchmode -projectPath "$PROJECT" -executeMethod "$METHOD" -logFile -
echo "Exit code: $?" # 0 = success, 1 = UPM error, 2 = timeout
`-logFile -` streams the Editor log (including the `[PackageInstaller]` / `[PackageSearch]`
lines) to stdout so you can watch resolution progress and read any UPM error. If
`unity editors path` output shape differs on your build, get the directory from
`unity editors --installed --format json` instead.case "$(uname)" in
Darwin) if [ -d "$ED/Unity.app" ]; then UNITY_BIN="$ED/Unity.app/Contents/MacOS/Unity";
elif [[ "$ED" == *.app ]]; then UNITY_BIN="$ED/Contents/MacOS/Unity";
else UNITY_BIN="$ED/Unity"; fi ;;
Linux) UNITY_BIN="$ED/Unity" ;;
*) UNITY_BIN="$ED/Unity.exe" ;; # Windows(Git Bash / MSYS);在PowerShell中使用Unity.exe
esac
"$UNITY_BIN" -batchmode -projectPath "$PROJECT" -executeMethod "$METHOD" -logFile -
echo "退出码: $?" # 0 = 成功, 1 = UPM错误, 2 = 超时
`-logFile -`会将编辑器日志(包括`[PackageInstaller]` / `[PackageSearch]`行)输出到标准输出,以便你查看解析进度并读取任何UPM错误。如果`unity editors path`的输出格式在你的构建版本中有所不同,可从`unity editors --installed --format json`获取目录。Verify
验证
bash
undefinedbash
undefinedEvery requested id should appear as a dependency
每个请求的ID都应作为依赖项出现
cat "<project-path>/Packages/manifest.json"
Confirm the run exited `0` and each package from the list is present in `manifest.json`. If a
package fails to resolve, `_request.Error.message` is logged; read it and check the id/version
against the registry. The Editor's own log (including the `[PackageInstaller]` lines) is the
stdout you streamed with `-logFile -` above — read it there, not via `unity logs` (which shows
the CLI's own log, not the Editor's).cat "<project-path>/Packages/manifest.json"
确认运行退出码为`0`,且列表中的每个包都存在于`manifest.json`中。如果某个包解析失败,`_request.Error.message`会被记录;请读取该信息并检查ID/版本是否符合注册表要求。编辑器的日志(包括`[PackageInstaller]`行)是你通过`-logFile -`输出的标准输出——请在此处查看,而非通过`unity logs`(后者显示的是CLI自身的日志,而非编辑器的日志)。Import & save headlessly (generate .meta
files)
.meta无头模式导入并保存(生成.meta
文件)
.metaAfter a script or tool writes new /asset files, Unity must import them so it generates
the file each asset needs — and every /asset MUST be committed together with its
. Merely opening the project once () imports and generates
them; use this method when you need it headless (in a script or CI).
.cs.meta.cs.metaunity open "<project-path>"Unlike the package installer, this is synchronous — it finishes before returning — so it's
safe to run via (its injected is harmless; the method also calls
for a clean exit code). Write
:
unity run-quitEditorApplication.ExitAssets/Editor/ProjectBootstrap/ProjectSaver.cscsharp
using UnityEditor;
using UnityEngine;
namespace ProjectBootstrap
{
public static class ProjectSaver
{
// Invoke with: -executeMethod ProjectBootstrap.ProjectSaver.SaveAll
public static void SaveAll()
{
AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
AssetDatabase.SaveAssets();
Debug.Log("[ProjectSaver] Assets imported and saved.");
EditorApplication.Exit(0);
}
}
}bash
unity run "<project-path>" --editor-version <version> \
-- -executeMethod ProjectBootstrap.ProjectSaver.SaveAll当脚本或工具写入新的/资源文件后,Unity必须导入这些文件以生成每个资源所需的文件——且每个/资源必须与其文件一起提交。只需打开项目一次()即可导入并生成这些文件;当你需要无头模式(在脚本或CI中)完成此操作时,可使用此方法。
.cs.meta.cs.metaunity open "<project-path>"与包安装程序不同,此操作是同步的——在返回前就会完成,因此可以安全地通过运行(其自动添加的参数无影响;方法也会调用以返回干净的退出码)。编写:
unity run-quitEditorApplication.ExitAssets/Editor/ProjectBootstrap/ProjectSaver.cscsharp
using UnityEditor;
using UnityEngine;
namespace ProjectBootstrap
{
public static class ProjectSaver
{
// 调用方式:-executeMethod ProjectBootstrap.ProjectSaver.SaveAll
public static void SaveAll()
{
AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
AssetDatabase.SaveAssets();
Debug.Log("[ProjectSaver] 资源已导入并保存。");
EditorApplication.Exit(0);
}
}
}bash
unity run "<project-path>" --editor-version <version> \
-- -executeMethod ProjectBootstrap.ProjectSaver.SaveAllNotes
注意事项
- These editor scripts are a bootstrap convenience. Leave them in
(they do nothing unless invoked) or delete them after setup — your call; mention it to the user.
Assets/Editor/ProjectBootstrap/ - All scripts live under because they use
Editor/; they never ship in a build.UnityEditor - Monetization / backend packages (,
com.unity.purchasing, the UGS packages) install through this same mechanism, but do the actual integration via the dedicated skills: implement-in-app-purchases, levelplay-unity-integration, build-live-game.com.unity.services.levelplay
- 这些编辑器脚本是用于初始化的便捷工具。你可以选择将它们留在中(除非被调用,否则它们不会执行任何操作),或在设置完成后删除——由你决定;请告知用户这一点。
Assets/Editor/ProjectBootstrap/ - 所有脚本都位于下,因为它们使用了
Editor/;它们不会被打包到构建版本中。UnityEditor - 商业化/后端包(、
com.unity.purchasing、UGS包)也通过相同机制安装,但实际集成需通过专门的技能:implement-in-app-purchases、levelplay-unity-integration、build-live-game。com.unity.services.levelplay