Skip to main content

unity-package-management

Adds, removes, upgrades, and discovers Unity (UPM) packages from outside the Editor, with the Pipeline package commands first and a C# script as the fallback. Use for package installs, verifying package IDs and versions, or choosing which packages a game needs.

来源信息

仓库
Unity-Technologies/skills
最近来源活动
2026年10月22日 18:20
检测到的 SKILL.md 语言
英语
星标
1,148
Forks
66

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

文件资源管理器
2 个文件

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
unity-package-management
description
Adds, removes, upgrades, and discovers Unity (UPM) packages from outside the Editor, with the Pipeline package commands first and a C# script as the fallback. Use for package installs, verifying package IDs and versions, or choosing which packages a game needs.
allowed-tools
["Bash","Write","Edit"]
# Unity Package Management (Pipeline commands first, C# script as the fallback) Add, remove, upgrade, and discover UPM (Unity Package Manager) packages with the `package` commands of the project's Pipeline package, run through `unity command`. Only when no Editor can answer those commands, fall back to `UnityEditor.PackageManager.Client` driven headless from a script (see [Fallback](#fallback-no-editor-to-ask-the-c-client-api)). Do **not** hand-edit `Packages/manifest.json`: the Package Manager resolves dependencies and compatible versions correctly, whereas manual edits routinely break resolution. This complements the **`unity-cli`** skill (editor install, project creation, build/test), which documents `unity command`, `unity status` and `unity pipeline install`. ## Use the package commands first The Pipeline package registers a `package` command group: `add`, `remove`, `list`, `resolve`, `search` and `status`. They need a running Editor whose project has `com.unity.pipeline` (add it with `unity pipeline install`, then `unity open`, then wait with `unity status --until-ready`). Each command's parameters come from the Editor, so read them before the first call. Don't guess a parameter name. ```bash 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 ``` Pass a command's parameters after its name, as the schema names them (`unity command <group> <command> --<parameter> <value>`). Add every package you need in one `package add` call when the schema accepts several ids. Pin a version the way the schema describes when a minimum is required (the `@<version>` id form belongs to the C# fallback below). Use `package status` or `package list` to verify, and `package remove` to drop a package. Treat a non-zero exit as a failure and read its message. If `unity command package` reports no such group, the project's Pipeline package predates it: run `unity pipeline upgrade`, or use the fallback below. ## When 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 (fallback script when no Editor is running). - 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](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](references/select-packages.md). Produce a **deduplicated list of package ids** and read it back to the user before installing. ## Fallback: no Editor to ask (the C# Client API) Use this section only when `package add` can't run: no Editor is open and you don't want one open, the project has no Pipeline package, or the command group is missing and an upgrade isn't wanted. It installs through `Client.AddAndRemove` from a script the Editor runs in batch mode. ## The `-quit` problem — why NOT `unity run` for installs `Client.Add` / `Client.AddAndRemove` are **asynchronous**: they return a `Request` that only completes on later `EditorApplication.update` ticks (the UPM child process marshals its result back on the Editor's main-loop pump, so a blocking `while (!req.IsCompleted)` busy-wait deadlocks it). The Editor must **stay alive** after `-executeMethod` returns, until the request finishes. `unity run` **cannot** be used for the installer: its default path injects `-quit` (see the reserved flags in the **`unity-cli`** skill). With `-quit`, the Editor quits the instant the method returns — before UPM resolves — so packages never install and the callback never runs. **Solution:** launch the **Editor binary directly** in `-batchmode` **without** `-quit`. The Editor stays alive, `EditorApplication.update` keeps ticking, the poll callback runs, and it calls `EditorApplication.Exit(code)` itself when done — which both quits and sets the process exit code. ## The installer script Write this to `Assets/Editor/ProjectBootstrap/PackageInstaller.cs`. It must live under an `Editor/` folder (or an Editor-only assembly) because it uses `UnityEditor`. ```csharp 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); } } } } ``` `AddAndRemove` installs the whole set in a single UPM resolution pass — faster and less error-prone than one `Client.Add` per package. **Add / remove / upgrade with one script:** - **Add**: list the id in `PackagesToAdd`. - **Remove**: list the id in `PackagesToRemove`. - **Upgrade / pin**: add the id with `@<version>` (e.g. `com.unity.cinemachine@2.9.7`). Without a version, resolution picks the latest compatible release. ## Discovering / verifying packages To confirm an id exists or list its versions before adding it, search the registry. The in-Editor `Client.SearchAll()` / `Client.Search("<id>")` calls are also async, so they use the **same poll-and-`Exit` pattern and the same headless run** as the installer. Write `Assets/Editor/ProjectBootstrap/PackageSearch.cs`: ```csharp 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` is a `PackageInfo[]`; each entry exposes `name`, `displayName`, `description`, and `versions` (`.latest`, `.latestCompatible`, `.all`). For a terminal-only check without the Editor (a **known** id, not free-text search), query the registry directly — see [references/select-packages.md](references/select-packages.md#discovering-and-verifying-packages). ## Run it headless (direct Editor invocation, no `-quit`) Resolve the Editor binary from the version, then run it in batch mode. The script owns quitting via `EditorApplication.Exit`, so do **not** pass `-quit`: ```bash 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 -` 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. ## Verify ```bash # Every requested id should appear as a dependency 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). ## Import & save headlessly (generate `.meta` files) After a script or tool writes new `.cs`/asset files, Unity must **import** them so it generates the `.meta` file each asset needs — and every `.cs`/asset MUST be committed together with its `.meta`. Merely opening the project once (`unity open "<project-path>"`) imports and generates them; use this method when you need it **headless** (in a script or CI). Unlike the package installer, this is **synchronous** — it finishes before returning — so it's safe to run via `unity run` (its injected `-quit` is harmless; the method also calls `EditorApplication.Exit` for a clean exit code). Write `Assets/Editor/ProjectBootstrap/ProjectSaver.cs`: ```csharp 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 ``` ## Notes - These editor scripts are a bootstrap convenience. Leave them in `Assets/Editor/ProjectBootstrap/` (they do nothing unless invoked) or delete them after setup — your call; mention it to the user. - All scripts live under `Editor/` because they use `UnityEditor`; they never ship in a build. - Monetization / backend packages (`com.unity.purchasing`, `com.unity.services.levelplay`, 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**.
在 GitHub 查看