关于 unity-package-management
unity-package-management 是用于添加、移除、升级和查找 Unity Package Manager 包的技能。它优先使用项目的 Pipeline 包命令,并说明无可用 Editor 时的无界面 C# 备用方式。
主要功能
- 01添加、移除和升级 UPM 包
- 02搜索包并查看版本
- 03优先使用 Pipeline 包命令
- 04必要时使用无界面 C# 备用方案
- 05安装后验证包
适用场景
- 01管理现有 Unity 项目的依赖
- 02在 CI 中非交互式配置包
- 03检查包 ID 和可用版本
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ unity-technologies/ unity-agent-plugin --skill unity-package-management安装后在 Claude Code 中输入/unity-package-management确认已加载。
附带文件references/select-packages.md
详细介绍
Unity 包管理(优先使用 Pipeline 命令,C# 脚本作为备用方案)
使用项目 Pipeline 包的 package 命令,通过 unity command 添加、移除、升级和查找 UPM(Unity Package Manager)包。只有在没有 Editor 能执行这些命令时,才回退到由脚本驱动的无头 UnityEditor.PackageManager.Client(参见 备用方案)。不要手动编辑 Packages/manifest.json:Package Manager 能正确解析依赖项和兼容版本,而手动编辑常常会破坏解析。
这与 unity-cli 技能互为补充;该技能介绍 Editor 安装、项目创建、构建/测试,以及 unity command、unity status 和 unity pipeline install。
优先使用 package 命令
Pipeline 包会注册一个 package 命令组,包含 add、remove、list、resolve、search 和 status。这些命令需要一个正在运行的 Editor,且其项目中包含 com.unity.pipeline(使用 unity pipeline install 添加,然后运行 unity open,再通过 unity status --until-ready 等待就绪)。每个命令的参数都由 Editor 提供,因此首次调用前请先读取参数。不要猜测参数名称。
unity status --until-ready --project-path "<project-path>"
unity command package --project-path "<project-path>" # the package commands this Editor has
unity command --query package --format json --project-path "<project-path>" # each command's parameter schema
unity command package search --project-path "<project-path>" # then pass the parameters the schema lists
unity command package add --project-path "<project-path>" # add one or more packages by id
unity command package list --project-path "<project-path>" # confirm what is installed
按架构指定的名称,在命令名称后传入参数(unity command <group> <command> --<parameter> <value>)。如果架构支持多个 ID,请在一次 package add 调用中添加所需的所有包。如果需要指定最低版本,请按照架构说明固定版本(@<version> ID 形式属于下文的 C# 备用方案)。使用 package status 或 package list 验证,并使用 package remove 移除包。将非零退出码视为失败,并阅读其消息。
如果 unity command package 报告不存在该命令组,说明项目中的 Pipeline 包版本过旧:运行 unity pipeline upgrade,或使用下方的备用方案。备用方案会启动自己的 Editor,因此在当前 Editor 已打开该项目时无法运行:请先让用户保存并关闭项目(参见备用方案的第一条规则)。
适用场景
- 在现有项目或新建项目中添加、移除或升级一个或多个包。
- 在 CI 中以非交互方式配置项目的包(没有 Editor 运行时使用备用脚本)。
- 在依赖某个包之前,验证其 ID 是否存在,或查找其可用版本。
- 判断游戏实际需要哪些包——参见 references/select-packages.md。
选择要安装的内容
只安装项目实际需要的内容,不要一股脑安装所有包;优先使用所选模板已提供的包(URP 模板已包含渲染管线、Input System 等)。按游戏类型 / 画面风格 / 平台 / 盈利方式选择包的对应关系,以及如何搜索注册表,详见 references/select-packages.md。生成一份去重后的包 ID 列表,并在安装前复述给用户。
后备方案:无法通过 Editor 操作(C# Client API)
仅在 package add 无法运行时使用此部分:没有打开的 Editor,且你不希望打开 Editor;项目没有 Pipeline 包;或者缺少命令组,且不想升级。此方案通过 Editor 以批处理模式运行的脚本,使用 Client.AddAndRemove 安装。
必须先关闭项目。 脚本会在第二个批处理模式的 Editor 中运行,而已经打开该项目的 Editor 会持有项目锁,导致批处理运行在脚本启动前就失败。unity editors running --format json 会列出所有已打开的 Editor,无论是否安装了 Pipeline 包(unity status 只会列出 Pipeline 项目,因此那里没有结果并不表示项目已关闭)。将每个 data.instances[].projectPath 与目标项目进行比较。同时检查 <project-path>/Temp/UnityLockfile 是否存在,并询问用户项目是否在 Editor 中打开。如果有 Editor 打开了该项目,或你无法确定,请用户保存工作并关闭项目,然后等待用户确认项目已关闭。如果批处理运行仍因锁而失败,则视为项目仍处于打开状态,并请用户关闭项目;不要重试,也不要移除锁文件。切勿自行关闭或终止用户的 Editor:其中可能有未保存的场景或 Play Mode 会话。如果用户希望继续保持打开状态,则不要运行后备方案:告知用户,必须等项目关闭或升级 Pipeline 包(先运行 unity pipeline upgrade,再运行 package add)后才能安装。
-quit 问题——为何安装时不能使用 unity run
Client.Add / Client.AddAndRemove 是异步的:它们会返回一个 Request,只有在后续的 EditorApplication.update 运行周期中才会完成(UPM 子进程会将结果传回 Editor 的主循环泵,因此使用阻塞式 while (!req.IsCompleted) 忙等会造成死锁)。-executeMethod 返回后,Editor 必须继续运行,直到请求完成。
安装程序不能使用 unity run:它的默认路径会注入 -quit(参见 unity-cli 技能中的保留标志)。使用 -quit 时,Editor 会在方法返回后立即退出——此时 UPM 尚未完成解析,因此包不会安装,回调也不会运行。
解决方案:直接启动 Editor 二进制文件,使用 -batchmode,但不要使用 -quit。Editor 会继续运行,EditorApplication.update 会持续触发,轮询回调会执行,并在完成时自行调用 EditorApplication.Exit(code)——该调用会退出 Editor 并设置进程退出代码。
安装程序脚本
将以下脚本写入 Assets/Editor/ProjectBootstrap/PackageInstaller.cs。它必须位于 Editor/ 文件夹下(或 Editor 专用程序集内),因为它使用了 UnityEditor。
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",
// "[email protected]" // 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);
}
}
}
}
AddAndRemove 会在一次 UPM 解析过程中安装整组包——比每个包单独调用一次 Client.Add 更快,也更不容易出错。
使用一个脚本添加/移除/升级:
- 添加:将 ID 列入
PackagesToAdd。 - 移除:将 ID 列入
PackagesToRemove。 - 升级/固定版本:为 ID 添加
@<version>(例如[email protected])。如果不指定版本,解析过程会选择最新的兼容版本。
查找/验证包
添加之前,可搜索注册表以确认 ID 是否存在,或列出其版本。Editor 内的 Client.SearchAll() / Client.Search("<id>") 调用同样是异步的,因此会使用与安装程序相同的轮询并调用 Exit 的模式和相同的无头运行方式。编写 Assets/Editor/ProjectBootstrap/PackageSearch.cs:
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.Result 是一个 PackageInfo[];其中每个条目都提供 name、displayName、description 和 versions(.latest、.latestCompatible、.all)。如果要在不启动 Editor 的情况下仅通过终端检查已知 ID(不支持自由文本搜索),请直接查询注册表——参见references/select-packages.md。
无界面运行(直接调用 Editor,不使用 -quit)
根据版本解析 Editor 二进制文件,然后以批处理模式运行。仅当没有 Editor 打开该项目时才运行(参见回退方案的第一条规则)。脚本通过
EditorApplication.Exit 自行退出,因此不要传入 -quit:
VERSION="<version>" # e.g. 6000.0.47f1 (or an installed version)
PROJECT="<project-path>"
METHOD="ProjectBootstrap.PackageInstaller.Install" # or ...PackageSearch.SearchAll
# Install directory of that editor (Hub layout), via the unity CLI
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)
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/Editor/Unity" ;;
*) UNITY_BIN="$ED/Editor/Unity.exe" ;; # Windows (Git Bash / MSYS); use Editor\Unity.exe in PowerShell
esac
"$UNITY_BIN" -batchmode -projectPath "$PROJECT" -executeMethod "$METHOD" -logFile -
echo "Exit code: $?" # 0 = success, 1 = UPM error, 2 = timeout
-logFile - 会将 Editor 日志(包括 [PackageInstaller] / [PackageSearch]
行)流式输出到 stdout,以便你查看解析进度并读取任何 UPM 错误。如果
unity editors path 的输出格式与你的构建版本不同,请改用 unity editors --installed --format json 获取目录。
验证
# Every requested id should appear as a dependency
cat "<project-path>/Packages/manifest.json"
确认运行以 0 退出,并且列表中的每个包都存在于 manifest.json 中。如果某个包解析失败,日志会记录 _request.Error.message;读取该信息,并检查包 ID/版本是否与注册表一致。Editor 自身的日志(包括 [PackageInstaller] 行)就是你通过上面的 -logFile - 流式输出到 stdout 的内容——在这里读取,而不是通过 unity logs(它显示的是 CLI 自身的日志,而不是 Editor 的日志)。
无界面导入并保存(生成 .meta 文件)
脚本或工具写入新的 .cs/资源文件后,Unity 必须导入它们,才能为每个资源生成所需的 .meta 文件——每个 .cs/资源都必须与其 .meta 一起提交。只需打开项目一次(unity open "<project-path>"),即可导入并生成这些文件;需要以无界面方式执行时(例如在脚本或 CI 中),请使用此方法。
与包安装不同,此操作是同步的——返回前即已完成——因此可安全地通过 unity run 运行(它注入的 -quit 不会造成问题;此方法也会调用
EditorApplication.Exit,以确保退出代码正确)。写入
Assets/Editor/ProjectBootstrap/ProjectSaver.cs:
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);
}
}
}
unity run "<project-path>" --editor-version <version> \
-- -executeMethod ProjectBootstrap.ProjectSaver.SaveAll
注意事项
- 这些 Editor 脚本是为了方便引导项目。可将它们保留在
Assets/Editor/ProjectBootstrap/中(除非调用,否则不会执行),也可以在设置完成后删除——由你决定;请告知用户。 - 所有脚本都位于
Editor/下,因为它们使用了UnityEditor;它们不会随构建一起发布。 - 变现/后端包(
com.unity.purchasing、com.unity.services.levelplay、UGS 包)通过同一机制安装,但实际的集成请使用专用技能:implement-in-app-purchases、levelplay-unity-integration、build-live-game。