Initial commit: AIGame Unity client with URP, HybridCLR, YooAsset and tooling.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
---
|
||||
name: unity-pipeline
|
||||
description: Drive a running Unity Editor or development Player from the command line via the unity-pipeline package — install the package, keep the editor ticking while unfocused, run the edit→recompile→run_tests loop, evaluate C#, and code-reload files at runtime. Use when an agent needs to control a live Unity instance, automate Unity tests, recompile scripts headlessly, or apply runtime reloads. Assumes the `unity` CLI is on PATH.
|
||||
---
|
||||
|
||||
# Unity Pipeline (agent control)
|
||||
|
||||
Invoke commands with `unity command <name> [args]`. Run `unity command` with no name to
|
||||
list what an instance exposes. Two servers exist: **Editor** (`7800-7849`, auto-starts with
|
||||
the editor) and **Runtime** (`7900-7949`, only in a dev Player build). See the package
|
||||
`README.md` "Commands" reference for the full list and parameters.
|
||||
|
||||
## 1. Install & verify
|
||||
|
||||
```bash
|
||||
unity pipeline install # install into current project (or --project-path)
|
||||
unity pipeline list # confirm the editor instance + server are reachable
|
||||
unity command editor_status # confirm the editor server answers
|
||||
```
|
||||
|
||||
## 2. Autonomous edit loop (Editor)
|
||||
|
||||
This is the core agent workflow: keep the editor alive, change code, recompile, test.
|
||||
|
||||
```bash
|
||||
# 1. Keep the editor ticking even when unfocused/minimized. REQUIRED before headless work —
|
||||
# Unity otherwise throttles or stalls update/compile when it isn't the active app.
|
||||
unity command set_autotick --enable true
|
||||
|
||||
# 2. Edit C# source files on disk normally.
|
||||
|
||||
# 3. Recompile (async: triggers a domain reload, then poll until done).
|
||||
unity command recompile
|
||||
unity command recompile_status # repeat until "completed" or "up_to_date"
|
||||
# Tolerate connection errors while the domain reload is in flight — that is expected.
|
||||
# If recompile_status reports failed=true, read its "errors" array and fix before testing.
|
||||
|
||||
# 4. (Optional) List available tests without running them.
|
||||
unity command list_tests --mode editor # mode: all | editor | playmode
|
||||
|
||||
# 5. Run tests (filter to keep it fast).
|
||||
unity command run_tests --mode editor --filter MyFixture.MyTest
|
||||
```
|
||||
|
||||
`run_tests` modes: `all` | `editor` | `playmode`. `filter_type`: `testName` | `assembly` |
|
||||
`category`. For long runs use `--async_tests true` and poll `unity command test_status`
|
||||
(abort with `unity command cancel_tests`).
|
||||
|
||||
> **Known caveat:** when any selected test *fails*, `run_tests` may surface an opaque
|
||||
> result instead of the failure details. Re-run a narrower `--filter`, or inspect the
|
||||
> editor's Test Runner / logs to get the real failure.
|
||||
|
||||
**Reading exit codes.** They separate "rewrite the invocation" from "the Editor failed", which
|
||||
is the distinction this loop depends on:
|
||||
|
||||
| Exit | Meaning | What to do |
|
||||
|------|---------|------------|
|
||||
| `2` | Bad arguments — a misspelled flag, a wrong type, too many positionals. Nothing ran. | Fix the command line and retry. The error names the problem and often suggests the right flag. |
|
||||
| `6` | The command ran and failed, or the Editor could not service it. | Read the error; retrying the same invocation will not help. |
|
||||
|
||||
Do not retry an exit 2 unchanged, and do not rewrite a command line on an exit 6.
|
||||
|
||||
## 3. Runtime code reload
|
||||
|
||||
Change gameplay code in a **running** game with no domain reload. The game must be live:
|
||||
enter Editor Play Mode (`unity command editor_play`) or run a dev Player. A
|
||||
The runtime Pipeline driver (enabled via Project Settings > Pipeline > Runtime) auto-discovers tagged methods on `Awake` (no manual
|
||||
registration). Mono only — Editor Play Mode and Mono desktop dev builds, not IL2CPP. The token
|
||||
is auto-injected for local requests.
|
||||
|
||||
### `reload_file`
|
||||
|
||||
Edit the method body directly; no separate file, no boilerplate.
|
||||
|
||||
1. Tag the method `[CodeReload]` on the MonoBehaviour.
|
||||
2. Edit its body on disk.
|
||||
3. Apply (re-run to iterate):
|
||||
```bash
|
||||
unity command reload_file --filename Assets/Spinner.cs
|
||||
```
|
||||
Add `--pdb` to make it debuggable — emits a portable PDB mapped to your source so breakpoints in
|
||||
the original file bind (attach the IDE + enable Editor Attaching; compiles unoptimized):
|
||||
```bash
|
||||
unity command reload_file --filename Assets/Spinner.cs --pdb
|
||||
```
|
||||
|
||||
Constraints: `void` instance methods only; **public** members only; debugging requires `--pdb`
|
||||
(the default emits no symbols).
|
||||
|
||||
`unity command codereload_status` shows active overrides. `reload_file` options: `--timeout <ms>`
|
||||
(default 30000), `--assemblyDir <dir>` (persist DLLs instead of in-memory);
|
||||
`cleanup_codereload --assemblyDir <dir>` clears old DLLs.
|
||||
|
||||
## 4. Bulk construction: `run_script` (the builder pattern)
|
||||
|
||||
For bulk work — creating many objects, wiring fields, generating content — put the code in a
|
||||
**versioned project script** and run a named static entry point with `run_script`. No domain
|
||||
reload, no code carried through the protocol; iterating costs an in-memory compile (< ~2s), not
|
||||
a 15–20s recompile.
|
||||
|
||||
```bash
|
||||
# 1. Write the builder OUTSIDE Assets/ (so the write triggers no asset import / domain reload),
|
||||
# e.g. AgentScripts/Build.cs: public static class Build { public static int All() { ... } }
|
||||
# 2. Run it — relative paths resolve against the project root (the parent of Assets/):
|
||||
unity command run_script --file AgentScripts/Build.cs --entry Build.All
|
||||
# 3. Iterate: edit the file, re-run. Useful extras:
|
||||
unity command run_script --file AgentScripts/Build.cs --dry_run true # compile-only check: diagnostics, nothing loaded or executed
|
||||
unity command run_script --file AgentScripts/Build.cs --entry Build.All --args '[3, "Green"]'
|
||||
```
|
||||
|
||||
Rules of thumb:
|
||||
|
||||
- **Code goes in files on disk via `run_script`; `eval` is for genuinely ad-hoc one-liners.**
|
||||
Never ship multi-line escaped C# strings through `eval` — write the file, run the entry.
|
||||
- Compiles see the project's **active editor defines** (`UNITY_EDITOR`, version/platform symbols);
|
||||
`--defines` appends extra symbols on top.
|
||||
- Entry points may be `async Task`/`Task<T>` — they are awaited asynchronously (the editor keeps
|
||||
pumping, so awaits resuming on Unity's context work naturally) and `Task<T>.Result` is returned.
|
||||
The wait is bounded by `timeout_ms`; on expiry the task keeps running detached.
|
||||
- Runtime exceptions come back with `file:line` mapped to your source (a source-mapped PDB is
|
||||
always emitted for executing runs; they compile unoptimized).
|
||||
- `--mode hotpatch` instead applies `[CodeReload]` in-place method replacements (delegates to
|
||||
`reload_file`); `entry`/`args`/`dry_run` are rejected there and `references`/`defines` don't apply.
|
||||
|
||||
## 5. Quick C# eval (Runtime)
|
||||
|
||||
For genuinely ad-hoc one-liners only — anything longer belongs in a file run via `run_script`.
|
||||
|
||||
```bash
|
||||
unity command eval "return 2 + 2;"
|
||||
# Or evaluate a .cs file on disk (contents run through the same path):
|
||||
unity command eval_file Assets/Scratch.cs
|
||||
```
|
||||
|
||||
## 5. Project audit (Editor)
|
||||
|
||||
Static-analysis scan via Project Auditor, producing a CSV of issues to fix. Trigger, poll, read.
|
||||
|
||||
```bash
|
||||
unity command audit # optional: --categories Code,ProjectSetting --output my.csv
|
||||
unity command audit_status # repeat until terminal status
|
||||
```
|
||||
|
||||
`audit_status` is terminal on `completed` (with `csvPath` + `issueCount`), `failed`, `unavailable`, or
|
||||
`interrupted` (a domain reload killed the scan — just re-run `audit`). Polling stays responsive while a
|
||||
Code-category scan compiles assemblies and holds the main thread. One scan at a time: a second `audit`
|
||||
returns `busy`. There is no cancel — stop polling to abandon a scan.
|
||||
|
||||
CSV columns: `Category, Severity, Areas, Description, RelativePath, Line, DescriptorId, Recommendation`
|
||||
(diagnostics only, so every row is something to fix; `Recommendation` says how).
|
||||
|
||||
> **Requires Project Auditor plus its rules.** `unavailable` means the Editor has no Project Auditor,
|
||||
> or it has no analysis rules — in a built-in-module Editor the rules live in the separate
|
||||
> `com.unity.project-auditor-rules` package (`unity command package_add --identifier
|
||||
> com.unity.project-auditor-rules --confirm true`). Read the `message` field; the command never reports
|
||||
> an empty `completed` scan that would look like a clean project.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **`set_autotick` first.** Without it, recompile and tests can hang while the editor is
|
||||
unfocused. The package's watchdog relies on the tick loop staying alive.
|
||||
- **A stuck command may mean a modal dialog is open**, not a hang — a dialog blocks the main
|
||||
thread until dismissed. If a command runs long, check `unity command editor_status` (answers
|
||||
instantly even when blocked); `status: "blocked_by_dialog"` means stop retrying and tell the
|
||||
human what's blocking (its `dialog.title`/`message`/`buttons`) — it can't be clicked over CLI.
|
||||
- **Code reload needs the game running.** `reload_file` applies to a live
|
||||
game — enter Editor Play Mode (`unity command editor_play`) first, or run a dev Player.
|
||||
- **Player-only commands need a dev Player.** `log`, `set_timescale`, `runtime_status`, etc. hit
|
||||
the Runtime server, which does not run in the Editor.
|
||||
- **Async commands poll.** `recompile`→`recompile_status`, `run_tests --async_tests`→
|
||||
`test_status`. Never assume completion from the trigger call's response.
|
||||
- **Target a specific instance** with `--instance host:port` or `--project-path <path>`
|
||||
when more than one is running.
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
description: AIGame 项目基础规则(资源登记等必须遵守)
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# AIGame 项目说明
|
||||
|
||||
## 基础规则
|
||||
|
||||
1. **资源登记(必须)**:所有资源生成完成后,必须按类型追加到对应描述文件,并写明简要说明(用途、路径、生成方式等)。未登记视为流程未完成。
|
||||
- 索引:`Doc/ResDesc.md`
|
||||
- model → `Doc/ResDesc_Model.md`
|
||||
- animation → `Doc/ResDesc_Animation.md`
|
||||
- picture → `Doc/ResDesc_Picture.md`
|
||||
- sound → `Doc/ResDesc_Sound.md`
|
||||
- effect → `Doc/ResDesc_Effect.md`
|
||||
@@ -0,0 +1,173 @@
|
||||
---
|
||||
name: unity-pipeline
|
||||
description: Drive a running Unity Editor or development Player from the command line via the unity-pipeline package — install the package, keep the editor ticking while unfocused, run the edit→recompile→run_tests loop, evaluate C#, and code-reload files at runtime. Use when an agent needs to control a live Unity instance, automate Unity tests, recompile scripts headlessly, or apply runtime reloads. Assumes the `unity` CLI is on PATH.
|
||||
---
|
||||
|
||||
# Unity Pipeline (agent control)
|
||||
|
||||
Invoke commands with `unity command <name> [args]`. Run `unity command` with no name to
|
||||
list what an instance exposes. Two servers exist: **Editor** (`7800-7849`, auto-starts with
|
||||
the editor) and **Runtime** (`7900-7949`, only in a dev Player build). See the package
|
||||
`README.md` "Commands" reference for the full list and parameters.
|
||||
|
||||
## 1. Install & verify
|
||||
|
||||
```bash
|
||||
unity pipeline install # install into current project (or --project-path)
|
||||
unity pipeline list # confirm the editor instance + server are reachable
|
||||
unity command editor_status # confirm the editor server answers
|
||||
```
|
||||
|
||||
## 2. Autonomous edit loop (Editor)
|
||||
|
||||
This is the core agent workflow: keep the editor alive, change code, recompile, test.
|
||||
|
||||
```bash
|
||||
# 1. Keep the editor ticking even when unfocused/minimized. REQUIRED before headless work —
|
||||
# Unity otherwise throttles or stalls update/compile when it isn't the active app.
|
||||
unity command set_autotick --enable true
|
||||
|
||||
# 2. Edit C# source files on disk normally.
|
||||
|
||||
# 3. Recompile (async: triggers a domain reload, then poll until done).
|
||||
unity command recompile
|
||||
unity command recompile_status # repeat until "completed" or "up_to_date"
|
||||
# Tolerate connection errors while the domain reload is in flight — that is expected.
|
||||
# If recompile_status reports failed=true, read its "errors" array and fix before testing.
|
||||
|
||||
# 4. (Optional) List available tests without running them.
|
||||
unity command list_tests --mode editor # mode: all | editor | playmode
|
||||
|
||||
# 5. Run tests (filter to keep it fast).
|
||||
unity command run_tests --mode editor --filter MyFixture.MyTest
|
||||
```
|
||||
|
||||
`run_tests` modes: `all` | `editor` | `playmode`. `filter_type`: `testName` | `assembly` |
|
||||
`category`. For long runs use `--async_tests true` and poll `unity command test_status`
|
||||
(abort with `unity command cancel_tests`).
|
||||
|
||||
> **Known caveat:** when any selected test *fails*, `run_tests` may surface an opaque
|
||||
> result instead of the failure details. Re-run a narrower `--filter`, or inspect the
|
||||
> editor's Test Runner / logs to get the real failure.
|
||||
|
||||
**Reading exit codes.** They separate "rewrite the invocation" from "the Editor failed", which
|
||||
is the distinction this loop depends on:
|
||||
|
||||
| Exit | Meaning | What to do |
|
||||
|------|---------|------------|
|
||||
| `2` | Bad arguments — a misspelled flag, a wrong type, too many positionals. Nothing ran. | Fix the command line and retry. The error names the problem and often suggests the right flag. |
|
||||
| `6` | The command ran and failed, or the Editor could not service it. | Read the error; retrying the same invocation will not help. |
|
||||
|
||||
Do not retry an exit 2 unchanged, and do not rewrite a command line on an exit 6.
|
||||
|
||||
## 3. Runtime code reload
|
||||
|
||||
Change gameplay code in a **running** game with no domain reload. The game must be live:
|
||||
enter Editor Play Mode (`unity command editor_play`) or run a dev Player. A
|
||||
The runtime Pipeline driver (enabled via Project Settings > Pipeline > Runtime) auto-discovers tagged methods on `Awake` (no manual
|
||||
registration). Mono only — Editor Play Mode and Mono desktop dev builds, not IL2CPP. The token
|
||||
is auto-injected for local requests.
|
||||
|
||||
### `reload_file`
|
||||
|
||||
Edit the method body directly; no separate file, no boilerplate.
|
||||
|
||||
1. Tag the method `[CodeReload]` on the MonoBehaviour.
|
||||
2. Edit its body on disk.
|
||||
3. Apply (re-run to iterate):
|
||||
```bash
|
||||
unity command reload_file --filename Assets/Spinner.cs
|
||||
```
|
||||
Add `--pdb` to make it debuggable — emits a portable PDB mapped to your source so breakpoints in
|
||||
the original file bind (attach the IDE + enable Editor Attaching; compiles unoptimized):
|
||||
```bash
|
||||
unity command reload_file --filename Assets/Spinner.cs --pdb
|
||||
```
|
||||
|
||||
Constraints: `void` instance methods only; **public** members only; debugging requires `--pdb`
|
||||
(the default emits no symbols).
|
||||
|
||||
`unity command codereload_status` shows active overrides. `reload_file` options: `--timeout <ms>`
|
||||
(default 30000), `--assemblyDir <dir>` (persist DLLs instead of in-memory);
|
||||
`cleanup_codereload --assemblyDir <dir>` clears old DLLs.
|
||||
|
||||
## 4. Bulk construction: `run_script` (the builder pattern)
|
||||
|
||||
For bulk work — creating many objects, wiring fields, generating content — put the code in a
|
||||
**versioned project script** and run a named static entry point with `run_script`. No domain
|
||||
reload, no code carried through the protocol; iterating costs an in-memory compile (< ~2s), not
|
||||
a 15–20s recompile.
|
||||
|
||||
```bash
|
||||
# 1. Write the builder OUTSIDE Assets/ (so the write triggers no asset import / domain reload),
|
||||
# e.g. AgentScripts/Build.cs: public static class Build { public static int All() { ... } }
|
||||
# 2. Run it — relative paths resolve against the project root (the parent of Assets/):
|
||||
unity command run_script --file AgentScripts/Build.cs --entry Build.All
|
||||
# 3. Iterate: edit the file, re-run. Useful extras:
|
||||
unity command run_script --file AgentScripts/Build.cs --dry_run true # compile-only check: diagnostics, nothing loaded or executed
|
||||
unity command run_script --file AgentScripts/Build.cs --entry Build.All --args '[3, "Green"]'
|
||||
```
|
||||
|
||||
Rules of thumb:
|
||||
|
||||
- **Code goes in files on disk via `run_script`; `eval` is for genuinely ad-hoc one-liners.**
|
||||
Never ship multi-line escaped C# strings through `eval` — write the file, run the entry.
|
||||
- Compiles see the project's **active editor defines** (`UNITY_EDITOR`, version/platform symbols);
|
||||
`--defines` appends extra symbols on top.
|
||||
- Entry points may be `async Task`/`Task<T>` — they are awaited asynchronously (the editor keeps
|
||||
pumping, so awaits resuming on Unity's context work naturally) and `Task<T>.Result` is returned.
|
||||
The wait is bounded by `timeout_ms`; on expiry the task keeps running detached.
|
||||
- Runtime exceptions come back with `file:line` mapped to your source (a source-mapped PDB is
|
||||
always emitted for executing runs; they compile unoptimized).
|
||||
- `--mode hotpatch` instead applies `[CodeReload]` in-place method replacements (delegates to
|
||||
`reload_file`); `entry`/`args`/`dry_run` are rejected there and `references`/`defines` don't apply.
|
||||
|
||||
## 5. Quick C# eval (Runtime)
|
||||
|
||||
For genuinely ad-hoc one-liners only — anything longer belongs in a file run via `run_script`.
|
||||
|
||||
```bash
|
||||
unity command eval "return 2 + 2;"
|
||||
# Or evaluate a .cs file on disk (contents run through the same path):
|
||||
unity command eval_file Assets/Scratch.cs
|
||||
```
|
||||
|
||||
## 5. Project audit (Editor)
|
||||
|
||||
Static-analysis scan via Project Auditor, producing a CSV of issues to fix. Trigger, poll, read.
|
||||
|
||||
```bash
|
||||
unity command audit # optional: --categories Code,ProjectSetting --output my.csv
|
||||
unity command audit_status # repeat until terminal status
|
||||
```
|
||||
|
||||
`audit_status` is terminal on `completed` (with `csvPath` + `issueCount`), `failed`, `unavailable`, or
|
||||
`interrupted` (a domain reload killed the scan — just re-run `audit`). Polling stays responsive while a
|
||||
Code-category scan compiles assemblies and holds the main thread. One scan at a time: a second `audit`
|
||||
returns `busy`. There is no cancel — stop polling to abandon a scan.
|
||||
|
||||
CSV columns: `Category, Severity, Areas, Description, RelativePath, Line, DescriptorId, Recommendation`
|
||||
(diagnostics only, so every row is something to fix; `Recommendation` says how).
|
||||
|
||||
> **Requires Project Auditor plus its rules.** `unavailable` means the Editor has no Project Auditor,
|
||||
> or it has no analysis rules — in a built-in-module Editor the rules live in the separate
|
||||
> `com.unity.project-auditor-rules` package (`unity command package_add --identifier
|
||||
> com.unity.project-auditor-rules --confirm true`). Read the `message` field; the command never reports
|
||||
> an empty `completed` scan that would look like a clean project.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **`set_autotick` first.** Without it, recompile and tests can hang while the editor is
|
||||
unfocused. The package's watchdog relies on the tick loop staying alive.
|
||||
- **A stuck command may mean a modal dialog is open**, not a hang — a dialog blocks the main
|
||||
thread until dismissed. If a command runs long, check `unity command editor_status` (answers
|
||||
instantly even when blocked); `status: "blocked_by_dialog"` means stop retrying and tell the
|
||||
human what's blocking (its `dialog.title`/`message`/`buttons`) — it can't be clicked over CLI.
|
||||
- **Code reload needs the game running.** `reload_file` applies to a live
|
||||
game — enter Editor Play Mode (`unity command editor_play`) first, or run a dev Player.
|
||||
- **Player-only commands need a dev Player.** `log`, `set_timescale`, `runtime_status`, etc. hit
|
||||
the Runtime server, which does not run in the Editor.
|
||||
- **Async commands poll.** `recompile`→`recompile_status`, `run_tests --async_tests`→
|
||||
`test_status`. Never assume completion from the trigger call's response.
|
||||
- **Target a specific instance** with `--instance host:port` or `--project-path <path>`
|
||||
when more than one is running.
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
# Python / tools
|
||||
Tools/__pycache__/
|
||||
Tools/**/__pycache__/
|
||||
Tools/**/*.pyc
|
||||
|
||||
# 混元3D 登录凭证与任务中间文件(勿提交)
|
||||
Tools/data/hunyuan/storage_state.json
|
||||
Tools/data/hunyuan/status.json
|
||||
Tools/data/hunyuan/jobs/
|
||||
|
||||
# model_tools 中间产物(按需)
|
||||
Tools/model_tools/out_bake/
|
||||
Tools/model_tools/**/_low_work/
|
||||
|
||||
# 本地输出(按需取消忽略)
|
||||
# Tools/output/
|
||||
|
||||
# ===== Unity =====
|
||||
AIClient/[Ll]ibrary/
|
||||
AIClient/[Tt]emp/
|
||||
AIClient/[Oo]bj/
|
||||
AIClient/[Bb]uild/
|
||||
AIClient/[Bb]uilds/
|
||||
AIClient/[Ll]ogs/
|
||||
AIClient/[Uu]ser[Ss]ettings/
|
||||
AIClient/[Mm]emoryCaptures/
|
||||
AIClient/[Rr]ecordings/
|
||||
|
||||
# Unity generated IDE / project files
|
||||
AIClient/*.csproj
|
||||
AIClient/*.sln
|
||||
AIClient/*.suo
|
||||
AIClient/*.user
|
||||
AIClient/*.userprefs
|
||||
AIClient/*.pidb
|
||||
AIClient/*.booproj
|
||||
AIClient/*.svd
|
||||
AIClient/*.pdb
|
||||
AIClient/*.mdb
|
||||
AIClient/*.opendb
|
||||
AIClient/*.VC.db
|
||||
AIClient/.vs/
|
||||
AIClient/.gradle/
|
||||
|
||||
# HybridCLR intermediate clone repos (LocalIl2Cpp 可按需提交)
|
||||
AIClient/HybridCLRData/hybridclr_repo/
|
||||
AIClient/HybridCLRData/il2cpp_plus_repo/
|
||||
|
||||
# Crash / OS
|
||||
*.stackdump
|
||||
Thumbs.db
|
||||
Desktop.ini
|
||||
.DS_Store
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"version": "1.0",
|
||||
"components": [
|
||||
"Microsoft.VisualStudio.Workload.ManagedGame"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1f9e1fb3ff717414ab8d0cf27dbc6f3d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,22 @@
|
||||
#if UNITY_EDITOR
|
||||
using HybridCLR.Editor.Installer;
|
||||
|
||||
public static class SetupHybridClr
|
||||
{
|
||||
public static string Install()
|
||||
{
|
||||
var c = new InstallerController();
|
||||
if (c.HasInstalledHybridCLR())
|
||||
return $"already_installed pkg={c.PackageVersion} local={c.InstalledLibil2cppVersion}";
|
||||
|
||||
c.InstallDefaultHybridCLR();
|
||||
return $"installed pkg={c.PackageVersion} has={c.HasInstalledHybridCLR()} local={c.InstalledLibil2cppVersion}";
|
||||
}
|
||||
|
||||
public static string Status()
|
||||
{
|
||||
var c = new InstallerController();
|
||||
return $"has={c.HasInstalledHybridCLR()} pkg={c.PackageVersion} local={c.InstalledLibil2cppVersion}";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b09764b3bf807f248b2a73e9dc8f04da
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,72 @@
|
||||
#if UNITY_EDITOR
|
||||
using System.Text;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
|
||||
public static class SetupUrp
|
||||
{
|
||||
// Map Quality level name -> URP asset under Assets/Settings/URP
|
||||
static readonly (string qualityName, string assetPath)[] QualityPipelineMap =
|
||||
{
|
||||
("Very Low", "Assets/Settings/URP/UniversalRP-0VeryLowQuality.asset"),
|
||||
("Low", "Assets/Settings/URP/UniversalRP-1LowQuality.asset"),
|
||||
("Medium", "Assets/Settings/URP/UniversalRP-2MediumQuality.asset"),
|
||||
("High", "Assets/Settings/URP/UniversalRP-3HighQuality.asset"),
|
||||
("Very High", "Assets/Settings/URP/UniversalRP-4VeryHighQuality.asset"),
|
||||
("Ultra", "Assets/Settings/URP/UniversalRP-4VeryHighQuality.asset"),
|
||||
};
|
||||
|
||||
const string FallbackPipeline = "Assets/Settings/URP/UniversalRP-3HighQuality.asset";
|
||||
|
||||
/// <summary>
|
||||
/// Assign a URP asset to every Quality level and set Graphics default pipeline.
|
||||
/// Fixes: "platforms have assets in its associated Quality levels that belong to different render pipelines"
|
||||
/// </summary>
|
||||
public static string FixQualityPipelines()
|
||||
{
|
||||
var log = new StringBuilder();
|
||||
var names = QualitySettings.names;
|
||||
UniversalRenderPipelineAsset fallback = AssetDatabase.LoadAssetAtPath<UniversalRenderPipelineAsset>(FallbackPipeline);
|
||||
if (fallback == null)
|
||||
return "ERROR: missing fallback URP asset at " + FallbackPipeline;
|
||||
|
||||
for (int i = 0; i < names.Length; i++)
|
||||
{
|
||||
var path = ResolvePipelinePath(names[i]);
|
||||
var asset = AssetDatabase.LoadAssetAtPath<UniversalRenderPipelineAsset>(path) ?? fallback;
|
||||
QualitySettings.SetQualityLevel(i, applyExpensiveChanges: false);
|
||||
QualitySettings.renderPipeline = asset;
|
||||
log.AppendLine($"{i}:{names[i]} -> {AssetDatabase.GetAssetPath(asset)}");
|
||||
}
|
||||
|
||||
// Prefer High-tier as project default (Graphics Settings)
|
||||
GraphicsSettings.renderPipelineAsset =
|
||||
AssetDatabase.LoadAssetAtPath<UniversalRenderPipelineAsset>(FallbackPipeline) ?? fallback;
|
||||
|
||||
// Restore previous current quality if possible, else Ultra/last
|
||||
int restore = System.Array.IndexOf(names, "Ultra");
|
||||
if (restore < 0) restore = names.Length - 1;
|
||||
QualitySettings.SetQualityLevel(restore, applyExpensiveChanges: true);
|
||||
|
||||
AssetDatabase.SaveAssets();
|
||||
EditorUtility.SetDirty(AssetDatabase.LoadAllAssetsAtPath("ProjectSettings/QualitySettings.asset")[0]);
|
||||
log.AppendLine("Graphics default -> " + AssetDatabase.GetAssetPath(GraphicsSettings.renderPipelineAsset));
|
||||
log.AppendLine("Current quality -> " + QualitySettings.names[QualitySettings.GetQualityLevel()]);
|
||||
return log.ToString();
|
||||
}
|
||||
|
||||
static string ResolvePipelinePath(string qualityName)
|
||||
{
|
||||
foreach (var entry in QualityPipelineMap)
|
||||
{
|
||||
if (entry.qualityName == qualityName)
|
||||
return entry.assetPath;
|
||||
}
|
||||
return FallbackPipeline;
|
||||
}
|
||||
|
||||
public static string Execute() => FixQualityPipelines();
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9e35ed0006e33004a987f71bac5117bf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a5f0c1a941fd9c4fa22ba5b2e81514e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,267 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!29 &1
|
||||
OcclusionCullingSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_OcclusionBakeSettings:
|
||||
smallestOccluder: 5
|
||||
smallestHole: 0.25
|
||||
backfaceThreshold: 100
|
||||
m_SceneGUID: 00000000000000000000000000000000
|
||||
m_OcclusionCullingData: {fileID: 0}
|
||||
--- !u!104 &2
|
||||
RenderSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 9
|
||||
m_Fog: 0
|
||||
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
|
||||
m_FogMode: 3
|
||||
m_FogDensity: 0.01
|
||||
m_LinearFogStart: 0
|
||||
m_LinearFogEnd: 300
|
||||
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
|
||||
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
|
||||
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
|
||||
m_AmbientIntensity: 1
|
||||
m_AmbientMode: 0
|
||||
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
|
||||
m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_HaloStrength: 0.5
|
||||
m_FlareStrength: 1
|
||||
m_FlareFadeSpeed: 3
|
||||
m_HaloTexture: {fileID: 0}
|
||||
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
|
||||
m_DefaultReflectionMode: 0
|
||||
m_DefaultReflectionResolution: 128
|
||||
m_ReflectionBounces: 1
|
||||
m_ReflectionIntensity: 1
|
||||
m_CustomReflection: {fileID: 0}
|
||||
m_Sun: {fileID: 705507994}
|
||||
m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
m_UseRadianceAmbientProbe: 0
|
||||
--- !u!157 &3
|
||||
LightmapSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 12
|
||||
m_GIWorkflowMode: 1
|
||||
m_GISettings:
|
||||
serializedVersion: 2
|
||||
m_BounceScale: 1
|
||||
m_IndirectOutputScale: 1
|
||||
m_AlbedoBoost: 1
|
||||
m_EnvironmentLightingMode: 0
|
||||
m_EnableBakedLightmaps: 1
|
||||
m_EnableRealtimeLightmaps: 0
|
||||
m_LightmapEditorSettings:
|
||||
serializedVersion: 12
|
||||
m_Resolution: 2
|
||||
m_BakeResolution: 40
|
||||
m_AtlasSize: 1024
|
||||
m_AO: 0
|
||||
m_AOMaxDistance: 1
|
||||
m_CompAOExponent: 1
|
||||
m_CompAOExponentDirect: 0
|
||||
m_ExtractAmbientOcclusion: 0
|
||||
m_Padding: 2
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_LightmapsBakeMode: 1
|
||||
m_TextureCompression: 1
|
||||
m_FinalGather: 0
|
||||
m_FinalGatherFiltering: 1
|
||||
m_FinalGatherRayCount: 256
|
||||
m_ReflectionCompression: 2
|
||||
m_MixedBakeMode: 2
|
||||
m_BakeBackend: 1
|
||||
m_PVRSampling: 1
|
||||
m_PVRDirectSampleCount: 32
|
||||
m_PVRSampleCount: 500
|
||||
m_PVRBounces: 2
|
||||
m_PVREnvironmentSampleCount: 500
|
||||
m_PVREnvironmentReferencePointCount: 2048
|
||||
m_PVRFilteringMode: 2
|
||||
m_PVRDenoiserTypeDirect: 0
|
||||
m_PVRDenoiserTypeIndirect: 0
|
||||
m_PVRDenoiserTypeAO: 0
|
||||
m_PVRFilterTypeDirect: 0
|
||||
m_PVRFilterTypeIndirect: 0
|
||||
m_PVRFilterTypeAO: 0
|
||||
m_PVREnvironmentMIS: 0
|
||||
m_PVRCulling: 1
|
||||
m_PVRFilteringGaussRadiusDirect: 1
|
||||
m_PVRFilteringGaussRadiusIndirect: 5
|
||||
m_PVRFilteringGaussRadiusAO: 2
|
||||
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
|
||||
m_PVRFilteringAtrousPositionSigmaIndirect: 2
|
||||
m_PVRFilteringAtrousPositionSigmaAO: 1
|
||||
m_ExportTrainingData: 0
|
||||
m_TrainingDataDestination: TrainingData
|
||||
m_LightProbeSampleCountMultiplier: 4
|
||||
m_LightingDataAsset: {fileID: 0}
|
||||
m_LightingSettings: {fileID: 0}
|
||||
--- !u!196 &4
|
||||
NavMeshSettings:
|
||||
serializedVersion: 2
|
||||
m_ObjectHideFlags: 0
|
||||
m_BuildSettings:
|
||||
serializedVersion: 2
|
||||
agentTypeID: 0
|
||||
agentRadius: 0.5
|
||||
agentHeight: 2
|
||||
agentSlope: 45
|
||||
agentClimb: 0.4
|
||||
ledgeDropHeight: 0
|
||||
maxJumpAcrossDistance: 0
|
||||
minRegionArea: 2
|
||||
manualCellSize: 0
|
||||
cellSize: 0.16666667
|
||||
manualTileSize: 0
|
||||
tileSize: 256
|
||||
accuratePlacement: 0
|
||||
debug:
|
||||
m_Flags: 0
|
||||
m_NavMeshData: {fileID: 0}
|
||||
--- !u!1 &705507993
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 705507995}
|
||||
- component: {fileID: 705507994}
|
||||
m_Layer: 0
|
||||
m_Name: Directional Light
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!108 &705507994
|
||||
Light:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_GameObject: {fileID: 705507993}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 8
|
||||
m_Type: 1
|
||||
m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
|
||||
m_Intensity: 1
|
||||
m_Range: 10
|
||||
m_SpotAngle: 30
|
||||
m_CookieSize: 10
|
||||
m_Shadows:
|
||||
m_Type: 2
|
||||
m_Resolution: -1
|
||||
m_CustomResolution: -1
|
||||
m_Strength: 1
|
||||
m_Bias: 0.05
|
||||
m_NormalBias: 0.4
|
||||
m_NearPlane: 0.2
|
||||
m_Cookie: {fileID: 0}
|
||||
m_DrawHalo: 0
|
||||
m_Flare: {fileID: 0}
|
||||
m_RenderMode: 0
|
||||
m_CullingMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_Lightmapping: 1
|
||||
m_LightShadowCasterMode: 0
|
||||
m_AreaSize: {x: 1, y: 1}
|
||||
m_BounceIntensity: 1
|
||||
m_ColorTemperature: 6570
|
||||
m_UseColorTemperature: 0
|
||||
m_ShadowRadius: 0
|
||||
m_ShadowAngle: 0
|
||||
--- !u!4 &705507995
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_GameObject: {fileID: 705507993}
|
||||
m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
|
||||
m_LocalPosition: {x: 0, y: 3, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 1
|
||||
m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
|
||||
--- !u!1 &963194225
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 963194228}
|
||||
- component: {fileID: 963194227}
|
||||
- component: {fileID: 963194226}
|
||||
m_Layer: 0
|
||||
m_Name: Main Camera
|
||||
m_TagString: MainCamera
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!81 &963194226
|
||||
AudioListener:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_GameObject: {fileID: 963194225}
|
||||
m_Enabled: 1
|
||||
--- !u!20 &963194227
|
||||
Camera:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_GameObject: {fileID: 963194225}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 2
|
||||
m_ClearFlags: 1
|
||||
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
|
||||
m_projectionMatrixMode: 1
|
||||
m_SensorSize: {x: 36, y: 24}
|
||||
m_LensShift: {x: 0, y: 0}
|
||||
m_GateFitMode: 2
|
||||
m_FocalLength: 50
|
||||
m_NormalizedViewPortRect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 1
|
||||
height: 1
|
||||
near clip plane: 0.3
|
||||
far clip plane: 1000
|
||||
field of view: 60
|
||||
orthographic: 0
|
||||
orthographic size: 5
|
||||
m_Depth: -1
|
||||
m_CullingMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_RenderingPath: -1
|
||||
m_TargetTexture: {fileID: 0}
|
||||
m_TargetDisplay: 0
|
||||
m_TargetEye: 3
|
||||
m_HDR: 1
|
||||
m_AllowMSAA: 1
|
||||
m_AllowDynamicResolution: 0
|
||||
m_ForceIntoRT: 0
|
||||
m_OcclusionCulling: 1
|
||||
m_StereoConvergence: 10
|
||||
m_StereoSeparation: 0.022
|
||||
--- !u!4 &963194228
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_GameObject: {fileID: 963194225}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 1, z: -10}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9fc0d4010bbf28b4594072e72b8655ab
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1345306f7b23afb4bb7701f34d02bae3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1424677654ae2a5429cb4ba77e52c1cf
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,386 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &-2127331727782644450
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 88650573, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: CharacterRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
--- !u!114 &-277282247946242087
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1749369493, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: EffectRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
RenderTargetName: _ScreenColorTexture
|
||||
SamplingShader: {fileID: 4800000, guid: 04c410c9937594faa893a11dceb85f7e, type: 3}
|
||||
BlitShader: {fileID: 4800000, guid: c17132b1f77d20942aa75f8429c0f8bc, type: 3}
|
||||
UberEffectPostShader: {fileID: 4800000, guid: 0f60273b12723a047a568dca50178e02, type: 3}
|
||||
UberEffectPostMaterial: {fileID: 0}
|
||||
RenderTargetSampling: 0
|
||||
UberEffectPostShaderName: Engine/Effect/UberEffectPost
|
||||
--- !u!114 &-172830343826181105
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 09d572d36a83daa4bb576ae814b31f16, type: 3}
|
||||
m_Name: TAA
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
PreviewInSceneView: 0
|
||||
UseMotionVector: 0
|
||||
RenderingMode: 0
|
||||
WorkOnPrepass: 0
|
||||
IgonreTransparentObject: 1
|
||||
Use32Bit: 0
|
||||
Shader: {fileID: 4800000, guid: 8c0d9373e5d33404ba27de953fd7a309, type: 3}
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: de640fe3d0db1804a85f9fc8f5cadab6, type: 3}
|
||||
m_Name: ForwardRenderer 0VeryLowQuality
|
||||
m_EditorClassIdentifier:
|
||||
debugShaders:
|
||||
debugReplacementPS: {fileID: 4800000, guid: cf852408f2e174538bcd9b7fda1c5ae7, type: 3}
|
||||
hdrDebugViewPS: {fileID: 4800000, guid: 573620ae32aec764abd4d728906d2587, type: 3}
|
||||
m_RendererFeatures: []
|
||||
m_RendererFeatureMap:
|
||||
m_UseNativeRenderPass: 0
|
||||
postProcessData: {fileID: 0}
|
||||
xrSystemData: {fileID: 11400000, guid: 60e1133243b97e347b653163a8c01b64, type: 2}
|
||||
shaders:
|
||||
blitPS: {fileID: 4800000, guid: c17132b1f77d20942aa75f8429c0f8bc, type: 3}
|
||||
copyDepthPS: {fileID: 4800000, guid: d6dae50ee9e1bfa4db75f19f99355220, type: 3}
|
||||
screenSpaceShadowPS: {fileID: 4800000, guid: 0f854b35a0cf61a429bd5dcfea30eddd, type: 3}
|
||||
samplingPS: {fileID: 4800000, guid: 04c410c9937594faa893a11dceb85f7e, type: 3}
|
||||
stencilDeferredPS: {fileID: 4800000, guid: e9155b26e1bc55942a41e518703fe304, type: 3}
|
||||
fallbackErrorPS: {fileID: 4800000, guid: e6e9a19c3678ded42a3bc431ebef7dbd, type: 3}
|
||||
fallbackLoadingPS: {fileID: 4800000, guid: 7f888aff2ac86494babad1c2c5daeee2, type: 3}
|
||||
materialErrorPS: {fileID: 4800000, guid: 5fd9a8feb75a4b5894c241777f519d4e, type: 3}
|
||||
coreBlitPS: {fileID: 4800000, guid: 93446b5c5339d4f00b85c159e1159b7c, type: 3}
|
||||
coreBlitColorAndDepthPS: {fileID: 4800000, guid: d104b2fc1ca6445babb8e90b0758136b, type: 3}
|
||||
blitHDROverlay: {fileID: 4800000, guid: a89bee29cffa951418fc1e2da94d1959, type: 3}
|
||||
cameraMotionVector: {fileID: 4800000, guid: c56b7e0d4c7cb484e959caeeedae9bbf, type: 3}
|
||||
objectMotionVector: {fileID: 4800000, guid: 7b3ede40266cd49a395def176e1bc486, type: 3}
|
||||
dataDrivenLensFlare: {fileID: 4800000, guid: 6cda457ac28612740adb23da5d39ea92, type: 3}
|
||||
terrainDetailLitPS: {fileID: 4800000, guid: f6783ab646d374f94b199774402a5144, type: 3}
|
||||
terrainDetailGrassPS: {fileID: 4800000, guid: e507fdfead5ca47e8b9a768b51c291a1, type: 3}
|
||||
terrainDetailGrassBillboardPS: {fileID: 4800000, guid: 29868e73b638e48ca99a19ea58c48d90, type: 3}
|
||||
m_AssetVersion: 2
|
||||
m_OpaqueLayerMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_TransparentLayerMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_DefaultStencilState:
|
||||
overrideStencilState: 0
|
||||
stencilReference: 0
|
||||
stencilCompareFunction: 8
|
||||
passOperation: 2
|
||||
failOperation: 0
|
||||
zFailOperation: 0
|
||||
m_ShadowTransparentReceive: 0
|
||||
m_RenderingMode: 0
|
||||
m_DepthPrimingMode: 0
|
||||
m_CopyDepthMode: 0
|
||||
m_AccurateGbufferNormals: 0
|
||||
m_IntermediateTextureMode: 1
|
||||
--- !u!114 &721371497446299689
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: -1181071007, guid: 58d7e77cafa53d146af19f551ff0a3f7, type: 3}
|
||||
m_Name: NiloToonCharacterRendererFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
mCharacterRenderSetting:
|
||||
mOutlineSetting:
|
||||
ShouldRenderOutline: 1
|
||||
UseBigSceneOutline: 0
|
||||
BigSceneOutlineAtten: 250
|
||||
outlineWidthMultiplier: 4
|
||||
outlineTintColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
outlineWidthAutoAdjustToCameraDistanceAndFOV: 1
|
||||
mSelfShadowSetting:
|
||||
mEnableSelfShadow: 0
|
||||
mShadowUDAngle: 20
|
||||
mShadowLRAngle: 1.4
|
||||
mShadowMapSize: 2048
|
||||
mMaxShadowDistance: 100
|
||||
mShadowDepthBias: 0.78
|
||||
mUseNDotLFix: 1
|
||||
mPerfectCullingForShadowCasters: 0
|
||||
mDebugShadowCameraVisibleBox: 0
|
||||
mMiscParamsSetting:
|
||||
mFinalColorMultiplier: {r: 1, g: 1, b: 1, a: 1}
|
||||
mRenderDepthNormal: 1
|
||||
mFinalColorIntensity: 1
|
||||
mCharacterValueMultiplier: 1
|
||||
mCharacterSaturateMultiplier: 0.2
|
||||
mGlobalMainDirectionalLightMultiplier: 0.7
|
||||
mOverridedLightUpDownAngle: -30
|
||||
mOverridedLightLRAngle: 30
|
||||
mOverrideLightDirectionIntensity: 1
|
||||
mSpecularIntensity: 1
|
||||
mSpecularColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
mUseMainLightDirForSpecular: 1
|
||||
mIndirecLightMinColor: {r: 0.01, g: 0.01, b: 0.01, a: 1}
|
||||
mIndirecLightMultiplier: 1
|
||||
mOverridedRimLightUpDownAngle: 180
|
||||
mOverridedRimLightLRAngle: -66
|
||||
mOverrideRimLightDirectionIntensity: 1
|
||||
mGlobalRimLightTintColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
mGlobalRimLightMultipiler: 4
|
||||
mRimLightStartFadeIn: 0
|
||||
mRimLightStartFadeOut: 100
|
||||
mRimLightAndShadowWidthMultiplier: 2
|
||||
mRimLightDepthDiffThresholdOffset: 0.5
|
||||
mAddLightMultipiler: {r: 1, g: 1, b: 1, a: 0.5}
|
||||
mAddLightMaxContribution: {r: 1, g: 1, b: 1, a: 1}
|
||||
mShadowColor: {r: 0, g: 0, b: 0, a: 0.5}
|
||||
mShadowAngle: 311
|
||||
mShadowLength: 0.89
|
||||
mShadowHeight: 0.1
|
||||
--- !u!114 &858565357924166977
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1137749194, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: OutlineFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
settings:
|
||||
profilerTag: Selection Outline
|
||||
targetLayer:
|
||||
serializedVersion: 2
|
||||
m_Bits: 2048
|
||||
outlineMaterial: {fileID: 2100000, guid: 19d53d4eaf4ccbf479c715921ed8d533, type: 2}
|
||||
outlineColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
outlineWidth: 1.5
|
||||
renderPassEvent: 550
|
||||
--- !u!114 &1787417439418964875
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: -1467291068, guid: 58d7e77cafa53d146af19f551ff0a3f7, type: 3}
|
||||
m_Name: EffectRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
RenderTargetName: _ScreenColorTexture
|
||||
SamplingShader: {fileID: 4800000, guid: 04c410c9937594faa893a11dceb85f7e, type: 3}
|
||||
BlitShader: {fileID: 4800000, guid: c17132b1f77d20942aa75f8429c0f8bc, type: 3}
|
||||
UberEffectPostShader: {fileID: 4800000, guid: e4dc0f5e3a376594b807a6e398748f7b, type: 3}
|
||||
UberEffectPostMaterial: {fileID: 0}
|
||||
RenderTargetSampling: 1
|
||||
UberEffectPostShaderName: GYGame/Effect/UberEffectPost
|
||||
--- !u!114 &3022920579844600396
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1741494071, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: GlobalScanFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
settings:
|
||||
scanMaterial: {fileID: 2100000, guid: d67115296155f3c41ab11dbf400eaadf, type: 2}
|
||||
renderPassEvent: 500
|
||||
--- !u!114 &4726037285063046694
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: -1608182031, guid: 58d7e77cafa53d146af19f551ff0a3f7, type: 3}
|
||||
m_Name: SceneRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
Settings:
|
||||
SceneParameters:
|
||||
CopyColor: 1
|
||||
ShadowColor: {r: 0.38492838, g: 0.3236027, b: 0.6792453, a: 1}
|
||||
BakedShadowStrength: 0.807
|
||||
GlobalBrightness: 1
|
||||
SceneDebug: 0
|
||||
NormalDebug: 0
|
||||
VertexColorDebug: 0
|
||||
DebugCustomValue:
|
||||
FogParameters:
|
||||
OpenFog: 1
|
||||
LineFogColor: {r: 0.9811321, g: 0.8294182, b: 0.5784977, a: 1}
|
||||
LineFogStartDistance: 77.46
|
||||
LineFogEndDistance: 144.2
|
||||
LineFogStrength: 0.846
|
||||
HeightFogColor: {r: 0.12116811, g: 0.56545126, b: 0.65199995, a: 1}
|
||||
HeightFogStartHeight: -2.77
|
||||
HeightFogFalloff: 1.974
|
||||
HeightFogDensity: 0.0116
|
||||
HeightFogStartDistance: 55.1
|
||||
HeightFogMinOpacity: 1
|
||||
FogAmbient: 1
|
||||
--- !u!114 &4957971812801212640
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1749369493, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: EffectRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
RenderTargetName: _ScreenColorTexture
|
||||
SamplingShader: {fileID: 4800000, guid: 04c410c9937594faa893a11dceb85f7e, type: 3}
|
||||
BlitShader: {fileID: 4800000, guid: c17132b1f77d20942aa75f8429c0f8bc, type: 3}
|
||||
UberEffectPostShader: {fileID: 4800000, guid: 0f60273b12723a047a568dca50178e02, type: 3}
|
||||
UberEffectPostMaterial: {fileID: 0}
|
||||
RenderTargetSampling: 0
|
||||
UberEffectPostShaderName: Engine/Effect/UberEffectPost
|
||||
--- !u!114 &5078528552640289789
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: a5c14bbf9359ff549afb7ea0f09037df, type: 3}
|
||||
m_Name: NiloToonCharacterRendererFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
mCharacterRenderSetting:
|
||||
mOutlineSetting:
|
||||
ShouldRenderOutline: 1
|
||||
UseBigSceneOutline: 0
|
||||
BigSceneOutlineAtten: 250
|
||||
outlineWidthMultiplier: 1
|
||||
outlineTintColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
outlineWidthAutoAdjustToCameraDistanceAndFOV: 1
|
||||
mSelfShadowSetting:
|
||||
mEnableSelfShadow: 1
|
||||
mShadowUDAngle: 30
|
||||
mShadowLRAngle: 0
|
||||
mShadowMapSize: 512
|
||||
mMaxShadowDistance: 50
|
||||
mShadowDepthBias: 1
|
||||
mUseNDotLFix: 1
|
||||
mPerfectCullingForShadowCasters: 0
|
||||
mDebugShadowCameraVisibleBox: 0
|
||||
mMiscParamsSetting:
|
||||
mFinalColorMultiplier: {r: 1, g: 1, b: 1, a: 1}
|
||||
mRenderDepthNormal: 0
|
||||
mFinalColorIntensity: 1
|
||||
mCharacterValueMultiplier: 1
|
||||
mCharacterSaturateMultiplier: 1
|
||||
mGlobalMainDirectionalLightMultiplier: 1
|
||||
mOverridedLightUpDownAngle: 0
|
||||
mOverridedLightLRAngle: 0
|
||||
mOverrideLightDirectionIntensity: 0
|
||||
mSpecularIntensity: 1
|
||||
mSpecularColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
mUseMainLightDirForSpecular: 1
|
||||
mIndirecLightMinColor: {r: 0.01, g: 0.01, b: 0.01, a: 1}
|
||||
mIndirecLightMultiplier: 1
|
||||
mOverridedRimLightUpDownAngle: 0
|
||||
mOverridedRimLightLRAngle: 0
|
||||
mOverrideRimLightDirectionIntensity: 0
|
||||
mGlobalRimLightTintColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
mGlobalRimLightMultipiler: 1
|
||||
mRimLightStartFadeIn: 1
|
||||
mRimLightStartFadeOut: 50
|
||||
mRimLightAndShadowWidthMultiplier: 1
|
||||
mRimLightDepthDiffThresholdOffset: 0.5
|
||||
mAddLightMultipiler: {r: 1, g: 1, b: 1, a: 0.5}
|
||||
mAddLightMaxContribution: {r: 1, g: 1, b: 1, a: 1}
|
||||
mShadowColor: {r: 0, g: 0, b: 0, a: 0.5}
|
||||
mShadowAngle: 70
|
||||
mShadowLength: 1
|
||||
mShadowHeight: 0.1
|
||||
--- !u!114 &5744085369761597830
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: -929177382, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: SceneRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
SSRComputeShader: {fileID: 7200000, guid: cf8964bc444045b4c830d86205bf1838, type: 3}
|
||||
Settings:
|
||||
SceneParameters:
|
||||
CopyColor: 0
|
||||
ShadowColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
|
||||
BakedShadowStrength: 1
|
||||
GlobalBrightness: 1
|
||||
SceneDebug: 0
|
||||
NormalDebug: 0
|
||||
VertexColorDebug: 0
|
||||
DebugCustomValue:
|
||||
FogParameters:
|
||||
OpenFog: 1
|
||||
LineFogColor: {r: 0.5, g: 0, b: 0, a: 1}
|
||||
LineFogStartDistance: 10
|
||||
LineFogEndDistance: 100
|
||||
LineFogStrength: 0
|
||||
HeightFogColor: {r: 0.5, g: 0, b: 0, a: 1}
|
||||
HeightFogStartHeight: 0
|
||||
HeightFogFalloff: 0
|
||||
HeightFogDensity: 0
|
||||
HeightFogStartDistance: 0
|
||||
HeightFogMinOpacity: 0
|
||||
FogAmbient: 0
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 39a007eb403d850479271fda4ed87835
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,386 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &-8140000362427506868
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1749369493, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: EffectRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
RenderTargetName: _ScreenColorTexture
|
||||
SamplingShader: {fileID: 4800000, guid: 04c410c9937594faa893a11dceb85f7e, type: 3}
|
||||
BlitShader: {fileID: 4800000, guid: c17132b1f77d20942aa75f8429c0f8bc, type: 3}
|
||||
UberEffectPostShader: {fileID: 4800000, guid: 0f60273b12723a047a568dca50178e02, type: 3}
|
||||
UberEffectPostMaterial: {fileID: 0}
|
||||
RenderTargetSampling: 0
|
||||
UberEffectPostShaderName: Engine/Effect/UberEffectPost
|
||||
--- !u!114 &-7290361237560686537
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: -1181071007, guid: 58d7e77cafa53d146af19f551ff0a3f7, type: 3}
|
||||
m_Name: NiloToonCharacterRendererFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
mCharacterRenderSetting:
|
||||
mOutlineSetting:
|
||||
ShouldRenderOutline: 1
|
||||
UseBigSceneOutline: 0
|
||||
BigSceneOutlineAtten: 250
|
||||
outlineWidthMultiplier: 1
|
||||
outlineTintColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
outlineWidthAutoAdjustToCameraDistanceAndFOV: 1
|
||||
mSelfShadowSetting:
|
||||
mEnableSelfShadow: 1
|
||||
mShadowUDAngle: 20
|
||||
mShadowLRAngle: 0
|
||||
mShadowMapSize: 2048
|
||||
mMaxShadowDistance: 100
|
||||
mShadowDepthBias: 1
|
||||
mUseNDotLFix: 1
|
||||
mPerfectCullingForShadowCasters: 1
|
||||
mDebugShadowCameraVisibleBox: 0
|
||||
mMiscParamsSetting:
|
||||
mFinalColorMultiplier: {r: 1, g: 1, b: 1, a: 1}
|
||||
mRenderDepthNormal: 1
|
||||
mFinalColorIntensity: 1
|
||||
mCharacterValueMultiplier: 1
|
||||
mCharacterSaturateMultiplier: 1
|
||||
mGlobalMainDirectionalLightMultiplier: 0.72
|
||||
mOverridedLightUpDownAngle: -30
|
||||
mOverridedLightLRAngle: -20
|
||||
mOverrideLightDirectionIntensity: 1
|
||||
mSpecularIntensity: 1.2
|
||||
mSpecularColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
mUseMainLightDirForSpecular: 1
|
||||
mIndirecLightMinColor: {r: 0.01, g: 0.01, b: 0.01, a: 1}
|
||||
mIndirecLightMultiplier: 1
|
||||
mOverridedRimLightUpDownAngle: 4
|
||||
mOverridedRimLightLRAngle: 47
|
||||
mOverrideRimLightDirectionIntensity: 1
|
||||
mGlobalRimLightTintColor: {r: 1, g: 0.6084906, b: 0.68266994, a: 1}
|
||||
mGlobalRimLightMultipiler: 8.3
|
||||
mRimLightStartFadeIn: 1
|
||||
mRimLightStartFadeOut: 50
|
||||
mRimLightAndShadowWidthMultiplier: 1
|
||||
mRimLightDepthDiffThresholdOffset: 0.5
|
||||
mAddLightMultipiler: {r: 1, g: 1, b: 1, a: 0.5}
|
||||
mAddLightMaxContribution: {r: 1, g: 1, b: 1, a: 1}
|
||||
mShadowColor: {r: 0, g: 0, b: 0, a: 0.5}
|
||||
mShadowAngle: 70
|
||||
mShadowLength: 1
|
||||
mShadowHeight: 0.1
|
||||
--- !u!114 &-5507381615842164399
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 88650573, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: CharacterRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
--- !u!114 &-1672808413483951387
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1749369493, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: EffectRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
RenderTargetName: _ScreenColorTexture
|
||||
SamplingShader: {fileID: 4800000, guid: 04c410c9937594faa893a11dceb85f7e, type: 3}
|
||||
BlitShader: {fileID: 4800000, guid: c17132b1f77d20942aa75f8429c0f8bc, type: 3}
|
||||
UberEffectPostShader: {fileID: 4800000, guid: 0f60273b12723a047a568dca50178e02, type: 3}
|
||||
UberEffectPostMaterial: {fileID: 0}
|
||||
RenderTargetSampling: 0
|
||||
UberEffectPostShaderName: Engine/Effect/UberEffectPost
|
||||
--- !u!114 &-1190632597373315364
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: -929177382, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: SceneRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
SSRComputeShader: {fileID: 7200000, guid: cf8964bc444045b4c830d86205bf1838, type: 3}
|
||||
Settings:
|
||||
SceneParameters:
|
||||
CopyColor: 0
|
||||
ShadowColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
|
||||
BakedShadowStrength: 1
|
||||
GlobalBrightness: 1
|
||||
SceneDebug: 0
|
||||
NormalDebug: 0
|
||||
VertexColorDebug: 0
|
||||
DebugCustomValue:
|
||||
FogParameters:
|
||||
OpenFog: 1
|
||||
LineFogColor: {r: 0.5, g: 0, b: 0, a: 1}
|
||||
LineFogStartDistance: 10
|
||||
LineFogEndDistance: 100
|
||||
LineFogStrength: 0
|
||||
HeightFogColor: {r: 0.5, g: 0, b: 0, a: 1}
|
||||
HeightFogStartHeight: 0
|
||||
HeightFogFalloff: 0
|
||||
HeightFogDensity: 0
|
||||
HeightFogStartDistance: 0
|
||||
HeightFogMinOpacity: 0
|
||||
FogAmbient: 0
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: de640fe3d0db1804a85f9fc8f5cadab6, type: 3}
|
||||
m_Name: ForwardRenderer 1LowQuality
|
||||
m_EditorClassIdentifier:
|
||||
debugShaders:
|
||||
debugReplacementPS: {fileID: 4800000, guid: cf852408f2e174538bcd9b7fda1c5ae7, type: 3}
|
||||
hdrDebugViewPS: {fileID: 4800000, guid: 573620ae32aec764abd4d728906d2587, type: 3}
|
||||
m_RendererFeatures: []
|
||||
m_RendererFeatureMap:
|
||||
m_UseNativeRenderPass: 0
|
||||
postProcessData: {fileID: 0}
|
||||
xrSystemData: {fileID: 11400000, guid: 60e1133243b97e347b653163a8c01b64, type: 2}
|
||||
shaders:
|
||||
blitPS: {fileID: 4800000, guid: c17132b1f77d20942aa75f8429c0f8bc, type: 3}
|
||||
copyDepthPS: {fileID: 4800000, guid: d6dae50ee9e1bfa4db75f19f99355220, type: 3}
|
||||
screenSpaceShadowPS: {fileID: 4800000, guid: 0f854b35a0cf61a429bd5dcfea30eddd, type: 3}
|
||||
samplingPS: {fileID: 4800000, guid: 04c410c9937594faa893a11dceb85f7e, type: 3}
|
||||
stencilDeferredPS: {fileID: 4800000, guid: e9155b26e1bc55942a41e518703fe304, type: 3}
|
||||
fallbackErrorPS: {fileID: 4800000, guid: e6e9a19c3678ded42a3bc431ebef7dbd, type: 3}
|
||||
fallbackLoadingPS: {fileID: 4800000, guid: 7f888aff2ac86494babad1c2c5daeee2, type: 3}
|
||||
materialErrorPS: {fileID: 4800000, guid: 5fd9a8feb75a4b5894c241777f519d4e, type: 3}
|
||||
coreBlitPS: {fileID: 4800000, guid: 93446b5c5339d4f00b85c159e1159b7c, type: 3}
|
||||
coreBlitColorAndDepthPS: {fileID: 4800000, guid: d104b2fc1ca6445babb8e90b0758136b, type: 3}
|
||||
blitHDROverlay: {fileID: 4800000, guid: a89bee29cffa951418fc1e2da94d1959, type: 3}
|
||||
cameraMotionVector: {fileID: 4800000, guid: c56b7e0d4c7cb484e959caeeedae9bbf, type: 3}
|
||||
objectMotionVector: {fileID: 4800000, guid: 7b3ede40266cd49a395def176e1bc486, type: 3}
|
||||
dataDrivenLensFlare: {fileID: 4800000, guid: 6cda457ac28612740adb23da5d39ea92, type: 3}
|
||||
terrainDetailLitPS: {fileID: 4800000, guid: f6783ab646d374f94b199774402a5144, type: 3}
|
||||
terrainDetailGrassPS: {fileID: 4800000, guid: e507fdfead5ca47e8b9a768b51c291a1, type: 3}
|
||||
terrainDetailGrassBillboardPS: {fileID: 4800000, guid: 29868e73b638e48ca99a19ea58c48d90, type: 3}
|
||||
m_AssetVersion: 2
|
||||
m_OpaqueLayerMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_TransparentLayerMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_DefaultStencilState:
|
||||
overrideStencilState: 0
|
||||
stencilReference: 0
|
||||
stencilCompareFunction: 8
|
||||
passOperation: 2
|
||||
failOperation: 0
|
||||
zFailOperation: 0
|
||||
m_ShadowTransparentReceive: 0
|
||||
m_RenderingMode: 0
|
||||
m_DepthPrimingMode: 0
|
||||
m_CopyDepthMode: 0
|
||||
m_AccurateGbufferNormals: 0
|
||||
m_IntermediateTextureMode: 1
|
||||
--- !u!114 &1558999346734371754
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1741494071, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: GlobalScanFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
settings:
|
||||
scanMaterial: {fileID: 2100000, guid: d67115296155f3c41ab11dbf400eaadf, type: 2}
|
||||
renderPassEvent: 500
|
||||
--- !u!114 &1682763883357394284
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1137749194, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: OutlineFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
settings:
|
||||
profilerTag: Selection Outline
|
||||
targetLayer:
|
||||
serializedVersion: 2
|
||||
m_Bits: 2048
|
||||
outlineMaterial: {fileID: 2100000, guid: 19d53d4eaf4ccbf479c715921ed8d533, type: 2}
|
||||
outlineColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
outlineWidth: 1.5
|
||||
renderPassEvent: 550
|
||||
--- !u!114 &3537890718694377718
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: a5c14bbf9359ff549afb7ea0f09037df, type: 3}
|
||||
m_Name: NiloToonCharacterRendererFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
mCharacterRenderSetting:
|
||||
mOutlineSetting:
|
||||
ShouldRenderOutline: 1
|
||||
UseBigSceneOutline: 0
|
||||
BigSceneOutlineAtten: 250
|
||||
outlineWidthMultiplier: 1
|
||||
outlineTintColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
outlineWidthAutoAdjustToCameraDistanceAndFOV: 1
|
||||
mSelfShadowSetting:
|
||||
mEnableSelfShadow: 1
|
||||
mShadowUDAngle: 30
|
||||
mShadowLRAngle: 0
|
||||
mShadowMapSize: 512
|
||||
mMaxShadowDistance: 50
|
||||
mShadowDepthBias: 1
|
||||
mUseNDotLFix: 1
|
||||
mPerfectCullingForShadowCasters: 0
|
||||
mDebugShadowCameraVisibleBox: 0
|
||||
mMiscParamsSetting:
|
||||
mFinalColorMultiplier: {r: 1, g: 1, b: 1, a: 1}
|
||||
mRenderDepthNormal: 0
|
||||
mFinalColorIntensity: 1
|
||||
mCharacterValueMultiplier: 1
|
||||
mCharacterSaturateMultiplier: 1
|
||||
mGlobalMainDirectionalLightMultiplier: 1
|
||||
mOverridedLightUpDownAngle: 0
|
||||
mOverridedLightLRAngle: 0
|
||||
mOverrideLightDirectionIntensity: 0
|
||||
mSpecularIntensity: 1
|
||||
mSpecularColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
mUseMainLightDirForSpecular: 1
|
||||
mIndirecLightMinColor: {r: 0.01, g: 0.01, b: 0.01, a: 1}
|
||||
mIndirecLightMultiplier: 1
|
||||
mOverridedRimLightUpDownAngle: 0
|
||||
mOverridedRimLightLRAngle: 0
|
||||
mOverrideRimLightDirectionIntensity: 0
|
||||
mGlobalRimLightTintColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
mGlobalRimLightMultipiler: 1
|
||||
mRimLightStartFadeIn: 1
|
||||
mRimLightStartFadeOut: 50
|
||||
mRimLightAndShadowWidthMultiplier: 1
|
||||
mRimLightDepthDiffThresholdOffset: 0.5
|
||||
mAddLightMultipiler: {r: 1, g: 1, b: 1, a: 0.5}
|
||||
mAddLightMaxContribution: {r: 1, g: 1, b: 1, a: 1}
|
||||
mShadowColor: {r: 0, g: 0, b: 0, a: 0.5}
|
||||
mShadowAngle: 70
|
||||
mShadowLength: 1
|
||||
mShadowHeight: 0.1
|
||||
--- !u!114 &3542269519574187309
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: -1467291068, guid: 58d7e77cafa53d146af19f551ff0a3f7, type: 3}
|
||||
m_Name: EffectRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
RenderTargetName: _ScreenColorTexture
|
||||
SamplingShader: {fileID: 4800000, guid: 04c410c9937594faa893a11dceb85f7e, type: 3}
|
||||
BlitShader: {fileID: 4800000, guid: c17132b1f77d20942aa75f8429c0f8bc, type: 3}
|
||||
UberEffectPostShader: {fileID: 4800000, guid: e4dc0f5e3a376594b807a6e398748f7b, type: 3}
|
||||
UberEffectPostMaterial: {fileID: 0}
|
||||
RenderTargetSampling: 1
|
||||
UberEffectPostShaderName: GYGame/Effect/UberEffectPost
|
||||
--- !u!114 &7530585995642756810
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 09d572d36a83daa4bb576ae814b31f16, type: 3}
|
||||
m_Name: TAA
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
PreviewInSceneView: 0
|
||||
UseMotionVector: 0
|
||||
RenderingMode: 0
|
||||
WorkOnPrepass: 0
|
||||
IgonreTransparentObject: 1
|
||||
Use32Bit: 0
|
||||
Shader: {fileID: 4800000, guid: 8c0d9373e5d33404ba27de953fd7a309, type: 3}
|
||||
--- !u!114 &7600997400158244659
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: -1608182031, guid: 58d7e77cafa53d146af19f551ff0a3f7, type: 3}
|
||||
m_Name: SceneRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
Settings:
|
||||
SceneParameters:
|
||||
CopyColor: 0
|
||||
ShadowColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
|
||||
BakedShadowStrength: 1
|
||||
GlobalBrightness: 1
|
||||
SceneDebug: 0
|
||||
NormalDebug: 0
|
||||
VertexColorDebug: 0
|
||||
DebugCustomValue:
|
||||
FogParameters:
|
||||
OpenFog: 1
|
||||
LineFogColor: {r: 0.5, g: 0, b: 0, a: 1}
|
||||
LineFogStartDistance: 10
|
||||
LineFogEndDistance: 100
|
||||
LineFogStrength: 0
|
||||
HeightFogColor: {r: 0.5, g: 0, b: 0, a: 1}
|
||||
HeightFogStartHeight: 0
|
||||
HeightFogFalloff: 0
|
||||
HeightFogDensity: 0
|
||||
HeightFogStartDistance: 0
|
||||
HeightFogMinOpacity: 0
|
||||
FogAmbient: 0
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ab43e90c602bfdb4e97e042f15d35169
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,386 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &-7718913652599309650
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: -929177382, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: SceneRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
SSRComputeShader: {fileID: 7200000, guid: cf8964bc444045b4c830d86205bf1838, type: 3}
|
||||
Settings:
|
||||
SceneParameters:
|
||||
CopyColor: 0
|
||||
ShadowColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
|
||||
BakedShadowStrength: 1
|
||||
GlobalBrightness: 1
|
||||
SceneDebug: 0
|
||||
NormalDebug: 0
|
||||
VertexColorDebug: 0
|
||||
DebugCustomValue:
|
||||
FogParameters:
|
||||
OpenFog: 1
|
||||
LineFogColor: {r: 0.5, g: 0, b: 0, a: 1}
|
||||
LineFogStartDistance: 10
|
||||
LineFogEndDistance: 100
|
||||
LineFogStrength: 0
|
||||
HeightFogColor: {r: 0.5, g: 0, b: 0, a: 1}
|
||||
HeightFogStartHeight: 0
|
||||
HeightFogFalloff: 0
|
||||
HeightFogDensity: 0
|
||||
HeightFogStartDistance: 0
|
||||
HeightFogMinOpacity: 0
|
||||
FogAmbient: 0
|
||||
--- !u!114 &-6991330952846024812
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: -1181071007, guid: 58d7e77cafa53d146af19f551ff0a3f7, type: 3}
|
||||
m_Name: NiloToonCharacterRendererFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
mCharacterRenderSetting:
|
||||
mOutlineSetting:
|
||||
ShouldRenderOutline: 1
|
||||
UseBigSceneOutline: 0
|
||||
BigSceneOutlineAtten: 250
|
||||
outlineWidthMultiplier: 4
|
||||
outlineTintColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
outlineWidthAutoAdjustToCameraDistanceAndFOV: 1
|
||||
mSelfShadowSetting:
|
||||
mEnableSelfShadow: 0
|
||||
mShadowUDAngle: 20
|
||||
mShadowLRAngle: 1.4
|
||||
mShadowMapSize: 2048
|
||||
mMaxShadowDistance: 100
|
||||
mShadowDepthBias: 0.78
|
||||
mUseNDotLFix: 1
|
||||
mPerfectCullingForShadowCasters: 0
|
||||
mDebugShadowCameraVisibleBox: 0
|
||||
mMiscParamsSetting:
|
||||
mFinalColorMultiplier: {r: 1, g: 1, b: 1, a: 1}
|
||||
mRenderDepthNormal: 1
|
||||
mFinalColorIntensity: 1
|
||||
mCharacterValueMultiplier: 1
|
||||
mCharacterSaturateMultiplier: 0.2
|
||||
mGlobalMainDirectionalLightMultiplier: 0.7
|
||||
mOverridedLightUpDownAngle: -30
|
||||
mOverridedLightLRAngle: 30
|
||||
mOverrideLightDirectionIntensity: 1
|
||||
mSpecularIntensity: 1
|
||||
mSpecularColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
mUseMainLightDirForSpecular: 1
|
||||
mIndirecLightMinColor: {r: 0.01, g: 0.01, b: 0.01, a: 1}
|
||||
mIndirecLightMultiplier: 1
|
||||
mOverridedRimLightUpDownAngle: 180
|
||||
mOverridedRimLightLRAngle: -66
|
||||
mOverrideRimLightDirectionIntensity: 1
|
||||
mGlobalRimLightTintColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
mGlobalRimLightMultipiler: 4
|
||||
mRimLightStartFadeIn: 0
|
||||
mRimLightStartFadeOut: 100
|
||||
mRimLightAndShadowWidthMultiplier: 2
|
||||
mRimLightDepthDiffThresholdOffset: 0.5
|
||||
mAddLightMultipiler: {r: 1, g: 1, b: 1, a: 0.5}
|
||||
mAddLightMaxContribution: {r: 1, g: 1, b: 1, a: 1}
|
||||
mShadowColor: {r: 0, g: 0, b: 0, a: 0.5}
|
||||
mShadowAngle: 311
|
||||
mShadowLength: 0.89
|
||||
mShadowHeight: 0.1
|
||||
--- !u!114 &-5455261652415300069
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: -1467291068, guid: 58d7e77cafa53d146af19f551ff0a3f7, type: 3}
|
||||
m_Name: EffectRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
RenderTargetName: _ScreenColorTexture
|
||||
SamplingShader: {fileID: 4800000, guid: 04c410c9937594faa893a11dceb85f7e, type: 3}
|
||||
BlitShader: {fileID: 4800000, guid: c17132b1f77d20942aa75f8429c0f8bc, type: 3}
|
||||
UberEffectPostShader: {fileID: 4800000, guid: e4dc0f5e3a376594b807a6e398748f7b, type: 3}
|
||||
UberEffectPostMaterial: {fileID: 0}
|
||||
RenderTargetSampling: 1
|
||||
UberEffectPostShaderName: GYGame/Effect/UberEffectPost
|
||||
--- !u!114 &-2003660147899227534
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1741494071, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: GlobalScanFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
settings:
|
||||
scanMaterial: {fileID: 2100000, guid: d67115296155f3c41ab11dbf400eaadf, type: 2}
|
||||
renderPassEvent: 500
|
||||
--- !u!114 &-847716761063671179
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 88650573, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: CharacterRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
--- !u!114 &-490646824858370943
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1749369493, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: EffectRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
RenderTargetName: _ScreenColorTexture
|
||||
SamplingShader: {fileID: 4800000, guid: 04c410c9937594faa893a11dceb85f7e, type: 3}
|
||||
BlitShader: {fileID: 4800000, guid: c17132b1f77d20942aa75f8429c0f8bc, type: 3}
|
||||
UberEffectPostShader: {fileID: 4800000, guid: 0f60273b12723a047a568dca50178e02, type: 3}
|
||||
UberEffectPostMaterial: {fileID: 0}
|
||||
RenderTargetSampling: 0
|
||||
UberEffectPostShaderName: Engine/Effect/UberEffectPost
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: de640fe3d0db1804a85f9fc8f5cadab6, type: 3}
|
||||
m_Name: ForwardRenderer 2MediumQuality
|
||||
m_EditorClassIdentifier:
|
||||
debugShaders:
|
||||
debugReplacementPS: {fileID: 4800000, guid: cf852408f2e174538bcd9b7fda1c5ae7, type: 3}
|
||||
hdrDebugViewPS: {fileID: 4800000, guid: 573620ae32aec764abd4d728906d2587, type: 3}
|
||||
m_RendererFeatures: []
|
||||
m_RendererFeatureMap:
|
||||
m_UseNativeRenderPass: 0
|
||||
postProcessData: {fileID: 11400000, guid: 41439944d30ece34e96484bdb6645b55, type: 2}
|
||||
xrSystemData: {fileID: 11400000, guid: 60e1133243b97e347b653163a8c01b64, type: 2}
|
||||
shaders:
|
||||
blitPS: {fileID: 4800000, guid: c17132b1f77d20942aa75f8429c0f8bc, type: 3}
|
||||
copyDepthPS: {fileID: 4800000, guid: d6dae50ee9e1bfa4db75f19f99355220, type: 3}
|
||||
screenSpaceShadowPS: {fileID: 4800000, guid: 0f854b35a0cf61a429bd5dcfea30eddd, type: 3}
|
||||
samplingPS: {fileID: 4800000, guid: 04c410c9937594faa893a11dceb85f7e, type: 3}
|
||||
stencilDeferredPS: {fileID: 4800000, guid: e9155b26e1bc55942a41e518703fe304, type: 3}
|
||||
fallbackErrorPS: {fileID: 4800000, guid: e6e9a19c3678ded42a3bc431ebef7dbd, type: 3}
|
||||
fallbackLoadingPS: {fileID: 4800000, guid: 7f888aff2ac86494babad1c2c5daeee2, type: 3}
|
||||
materialErrorPS: {fileID: 4800000, guid: 5fd9a8feb75a4b5894c241777f519d4e, type: 3}
|
||||
coreBlitPS: {fileID: 4800000, guid: 93446b5c5339d4f00b85c159e1159b7c, type: 3}
|
||||
coreBlitColorAndDepthPS: {fileID: 4800000, guid: d104b2fc1ca6445babb8e90b0758136b, type: 3}
|
||||
blitHDROverlay: {fileID: 4800000, guid: a89bee29cffa951418fc1e2da94d1959, type: 3}
|
||||
cameraMotionVector: {fileID: 4800000, guid: c56b7e0d4c7cb484e959caeeedae9bbf, type: 3}
|
||||
objectMotionVector: {fileID: 4800000, guid: 7b3ede40266cd49a395def176e1bc486, type: 3}
|
||||
dataDrivenLensFlare: {fileID: 4800000, guid: 6cda457ac28612740adb23da5d39ea92, type: 3}
|
||||
terrainDetailLitPS: {fileID: 4800000, guid: f6783ab646d374f94b199774402a5144, type: 3}
|
||||
terrainDetailGrassPS: {fileID: 4800000, guid: e507fdfead5ca47e8b9a768b51c291a1, type: 3}
|
||||
terrainDetailGrassBillboardPS: {fileID: 4800000, guid: 29868e73b638e48ca99a19ea58c48d90, type: 3}
|
||||
m_AssetVersion: 2
|
||||
m_OpaqueLayerMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_TransparentLayerMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_DefaultStencilState:
|
||||
overrideStencilState: 0
|
||||
stencilReference: 0
|
||||
stencilCompareFunction: 8
|
||||
passOperation: 2
|
||||
failOperation: 0
|
||||
zFailOperation: 0
|
||||
m_ShadowTransparentReceive: 1
|
||||
m_RenderingMode: 0
|
||||
m_DepthPrimingMode: 0
|
||||
m_CopyDepthMode: 0
|
||||
m_AccurateGbufferNormals: 0
|
||||
m_IntermediateTextureMode: 1
|
||||
--- !u!114 &5699326553694581020
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1137749194, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: OutlineFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
settings:
|
||||
profilerTag: Selection Outline
|
||||
targetLayer:
|
||||
serializedVersion: 2
|
||||
m_Bits: 2048
|
||||
outlineMaterial: {fileID: 2100000, guid: 19d53d4eaf4ccbf479c715921ed8d533, type: 2}
|
||||
outlineColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
outlineWidth: 1.5
|
||||
renderPassEvent: 550
|
||||
--- !u!114 &6943902112415961173
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1749369493, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: EffectRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
RenderTargetName: _ScreenColorTexture
|
||||
SamplingShader: {fileID: 4800000, guid: 04c410c9937594faa893a11dceb85f7e, type: 3}
|
||||
BlitShader: {fileID: 4800000, guid: c17132b1f77d20942aa75f8429c0f8bc, type: 3}
|
||||
UberEffectPostShader: {fileID: 4800000, guid: 0f60273b12723a047a568dca50178e02, type: 3}
|
||||
UberEffectPostMaterial: {fileID: 0}
|
||||
RenderTargetSampling: 0
|
||||
UberEffectPostShaderName: Engine/Effect/UberEffectPost
|
||||
--- !u!114 &7161363195401994471
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: a5c14bbf9359ff549afb7ea0f09037df, type: 3}
|
||||
m_Name: NiloToonCharacterRendererFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
mCharacterRenderSetting:
|
||||
mOutlineSetting:
|
||||
ShouldRenderOutline: 1
|
||||
UseBigSceneOutline: 0
|
||||
BigSceneOutlineAtten: 250
|
||||
outlineWidthMultiplier: 1
|
||||
outlineTintColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
outlineWidthAutoAdjustToCameraDistanceAndFOV: 1
|
||||
mSelfShadowSetting:
|
||||
mEnableSelfShadow: 1
|
||||
mShadowUDAngle: 30
|
||||
mShadowLRAngle: 0
|
||||
mShadowMapSize: 512
|
||||
mMaxShadowDistance: 50
|
||||
mShadowDepthBias: 1
|
||||
mUseNDotLFix: 1
|
||||
mPerfectCullingForShadowCasters: 0
|
||||
mDebugShadowCameraVisibleBox: 0
|
||||
mMiscParamsSetting:
|
||||
mFinalColorMultiplier: {r: 1, g: 1, b: 1, a: 1}
|
||||
mRenderDepthNormal: 0
|
||||
mFinalColorIntensity: 1
|
||||
mCharacterValueMultiplier: 1
|
||||
mCharacterSaturateMultiplier: 1
|
||||
mGlobalMainDirectionalLightMultiplier: 1
|
||||
mOverridedLightUpDownAngle: 0
|
||||
mOverridedLightLRAngle: 0
|
||||
mOverrideLightDirectionIntensity: 0
|
||||
mSpecularIntensity: 1
|
||||
mSpecularColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
mUseMainLightDirForSpecular: 1
|
||||
mIndirecLightMinColor: {r: 0.01, g: 0.01, b: 0.01, a: 1}
|
||||
mIndirecLightMultiplier: 1
|
||||
mOverridedRimLightUpDownAngle: 0
|
||||
mOverridedRimLightLRAngle: 0
|
||||
mOverrideRimLightDirectionIntensity: 0
|
||||
mGlobalRimLightTintColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
mGlobalRimLightMultipiler: 1
|
||||
mRimLightStartFadeIn: 1
|
||||
mRimLightStartFadeOut: 50
|
||||
mRimLightAndShadowWidthMultiplier: 1
|
||||
mRimLightDepthDiffThresholdOffset: 0.5
|
||||
mAddLightMultipiler: {r: 1, g: 1, b: 1, a: 0.5}
|
||||
mAddLightMaxContribution: {r: 1, g: 1, b: 1, a: 1}
|
||||
mShadowColor: {r: 0, g: 0, b: 0, a: 0.5}
|
||||
mShadowAngle: 70
|
||||
mShadowLength: 1
|
||||
mShadowHeight: 0.1
|
||||
--- !u!114 &8305250864628069769
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: -1608182031, guid: 58d7e77cafa53d146af19f551ff0a3f7, type: 3}
|
||||
m_Name: SceneRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
Settings:
|
||||
SceneParameters:
|
||||
CopyColor: 1
|
||||
ShadowColor: {r: 0.38492838, g: 0.3236027, b: 0.6792453, a: 1}
|
||||
BakedShadowStrength: 0.807
|
||||
GlobalBrightness: 1
|
||||
SceneDebug: 0
|
||||
NormalDebug: 0
|
||||
VertexColorDebug: 0
|
||||
DebugCustomValue:
|
||||
FogParameters:
|
||||
OpenFog: 1
|
||||
LineFogColor: {r: 0.9811321, g: 0.8294182, b: 0.5784977, a: 1}
|
||||
LineFogStartDistance: 77.46
|
||||
LineFogEndDistance: 144.2
|
||||
LineFogStrength: 0.846
|
||||
HeightFogColor: {r: 0.12116811, g: 0.56545126, b: 0.65199995, a: 1}
|
||||
HeightFogStartHeight: -2.77
|
||||
HeightFogFalloff: 1.974
|
||||
HeightFogDensity: 0.0116
|
||||
HeightFogStartDistance: 55.1
|
||||
HeightFogMinOpacity: 1
|
||||
FogAmbient: 1
|
||||
--- !u!114 &8504462900099931217
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 09d572d36a83daa4bb576ae814b31f16, type: 3}
|
||||
m_Name: TAA
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
PreviewInSceneView: 0
|
||||
UseMotionVector: 0
|
||||
RenderingMode: 0
|
||||
WorkOnPrepass: 0
|
||||
IgonreTransparentObject: 1
|
||||
Use32Bit: 0
|
||||
Shader: {fileID: 4800000, guid: 8c0d9373e5d33404ba27de953fd7a309, type: 3}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f454285041681a44999cc258865df961
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,386 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &-7801194434031207102
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: -1181071007, guid: 58d7e77cafa53d146af19f551ff0a3f7, type: 3}
|
||||
m_Name: NiloToonCharacterRendererFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
mCharacterRenderSetting:
|
||||
mOutlineSetting:
|
||||
ShouldRenderOutline: 1
|
||||
UseBigSceneOutline: 0
|
||||
BigSceneOutlineAtten: 250
|
||||
outlineWidthMultiplier: 1
|
||||
outlineTintColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
outlineWidthAutoAdjustToCameraDistanceAndFOV: 1
|
||||
mSelfShadowSetting:
|
||||
mEnableSelfShadow: 1
|
||||
mShadowUDAngle: 0
|
||||
mShadowLRAngle: 0
|
||||
mShadowMapSize: 512
|
||||
mMaxShadowDistance: 50
|
||||
mShadowDepthBias: 1
|
||||
mUseNDotLFix: 1
|
||||
mPerfectCullingForShadowCasters: 1
|
||||
mDebugShadowCameraVisibleBox: 0
|
||||
mMiscParamsSetting:
|
||||
mFinalColorMultiplier: {r: 1, g: 1, b: 1, a: 1}
|
||||
mRenderDepthNormal: 1
|
||||
mFinalColorIntensity: 1
|
||||
mCharacterValueMultiplier: 1
|
||||
mCharacterSaturateMultiplier: 1
|
||||
mGlobalMainDirectionalLightMultiplier: 1
|
||||
mOverridedLightUpDownAngle: 0
|
||||
mOverridedLightLRAngle: 0
|
||||
mOverrideLightDirectionIntensity: 0
|
||||
mSpecularIntensity: 1
|
||||
mSpecularColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
mUseMainLightDirForSpecular: 1
|
||||
mIndirecLightMinColor: {r: 0.01, g: 0.01, b: 0.01, a: 1}
|
||||
mIndirecLightMultiplier: 1
|
||||
mOverridedRimLightUpDownAngle: -93
|
||||
mOverridedRimLightLRAngle: -66
|
||||
mOverrideRimLightDirectionIntensity: 1
|
||||
mGlobalRimLightTintColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
mGlobalRimLightMultipiler: 1
|
||||
mRimLightStartFadeIn: 1
|
||||
mRimLightStartFadeOut: 50
|
||||
mRimLightAndShadowWidthMultiplier: 1
|
||||
mRimLightDepthDiffThresholdOffset: 0.5
|
||||
mAddLightMultipiler: {r: 1, g: 1, b: 1, a: 0.5}
|
||||
mAddLightMaxContribution: {r: 1, g: 1, b: 1, a: 1}
|
||||
mShadowColor: {r: 0, g: 0, b: 0, a: 0.5}
|
||||
mShadowAngle: 70
|
||||
mShadowLength: 1
|
||||
mShadowHeight: 0.1
|
||||
--- !u!114 &-6694115839964173624
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1137749194, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: OutlineFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
settings:
|
||||
profilerTag: Selection Outline
|
||||
targetLayer:
|
||||
serializedVersion: 2
|
||||
m_Bits: 2048
|
||||
outlineMaterial: {fileID: 2100000, guid: 19d53d4eaf4ccbf479c715921ed8d533, type: 2}
|
||||
outlineColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
outlineWidth: 1.5
|
||||
renderPassEvent: 550
|
||||
--- !u!114 &-4899579376300271201
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1741494071, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: GlobalScanFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
settings:
|
||||
scanMaterial: {fileID: 2100000, guid: d67115296155f3c41ab11dbf400eaadf, type: 2}
|
||||
renderPassEvent: 500
|
||||
--- !u!114 &-3248482767401251789
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1749369493, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: EffectRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
RenderTargetName: _ScreenColorTexture
|
||||
SamplingShader: {fileID: 4800000, guid: 04c410c9937594faa893a11dceb85f7e, type: 3}
|
||||
BlitShader: {fileID: 4800000, guid: c17132b1f77d20942aa75f8429c0f8bc, type: 3}
|
||||
UberEffectPostShader: {fileID: 4800000, guid: 0f60273b12723a047a568dca50178e02, type: 3}
|
||||
UberEffectPostMaterial: {fileID: 0}
|
||||
RenderTargetSampling: 0
|
||||
UberEffectPostShaderName: Engine/Effect/UberEffectPost
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: de640fe3d0db1804a85f9fc8f5cadab6, type: 3}
|
||||
m_Name: ForwardRenderer 3HighQuality
|
||||
m_EditorClassIdentifier:
|
||||
debugShaders:
|
||||
debugReplacementPS: {fileID: 4800000, guid: cf852408f2e174538bcd9b7fda1c5ae7, type: 3}
|
||||
hdrDebugViewPS: {fileID: 4800000, guid: 573620ae32aec764abd4d728906d2587, type: 3}
|
||||
m_RendererFeatures: []
|
||||
m_RendererFeatureMap:
|
||||
m_UseNativeRenderPass: 0
|
||||
postProcessData: {fileID: 11400000, guid: 41439944d30ece34e96484bdb6645b55, type: 2}
|
||||
xrSystemData: {fileID: 11400000, guid: 60e1133243b97e347b653163a8c01b64, type: 2}
|
||||
shaders:
|
||||
blitPS: {fileID: 4800000, guid: c17132b1f77d20942aa75f8429c0f8bc, type: 3}
|
||||
copyDepthPS: {fileID: 4800000, guid: d6dae50ee9e1bfa4db75f19f99355220, type: 3}
|
||||
screenSpaceShadowPS: {fileID: 4800000, guid: 0f854b35a0cf61a429bd5dcfea30eddd, type: 3}
|
||||
samplingPS: {fileID: 4800000, guid: 04c410c9937594faa893a11dceb85f7e, type: 3}
|
||||
stencilDeferredPS: {fileID: 4800000, guid: e9155b26e1bc55942a41e518703fe304, type: 3}
|
||||
fallbackErrorPS: {fileID: 4800000, guid: e6e9a19c3678ded42a3bc431ebef7dbd, type: 3}
|
||||
fallbackLoadingPS: {fileID: 4800000, guid: 7f888aff2ac86494babad1c2c5daeee2, type: 3}
|
||||
materialErrorPS: {fileID: 4800000, guid: 5fd9a8feb75a4b5894c241777f519d4e, type: 3}
|
||||
coreBlitPS: {fileID: 4800000, guid: 93446b5c5339d4f00b85c159e1159b7c, type: 3}
|
||||
coreBlitColorAndDepthPS: {fileID: 4800000, guid: d104b2fc1ca6445babb8e90b0758136b, type: 3}
|
||||
blitHDROverlay: {fileID: 4800000, guid: a89bee29cffa951418fc1e2da94d1959, type: 3}
|
||||
cameraMotionVector: {fileID: 4800000, guid: c56b7e0d4c7cb484e959caeeedae9bbf, type: 3}
|
||||
objectMotionVector: {fileID: 4800000, guid: 7b3ede40266cd49a395def176e1bc486, type: 3}
|
||||
dataDrivenLensFlare: {fileID: 4800000, guid: 6cda457ac28612740adb23da5d39ea92, type: 3}
|
||||
terrainDetailLitPS: {fileID: 4800000, guid: f6783ab646d374f94b199774402a5144, type: 3}
|
||||
terrainDetailGrassPS: {fileID: 4800000, guid: e507fdfead5ca47e8b9a768b51c291a1, type: 3}
|
||||
terrainDetailGrassBillboardPS: {fileID: 4800000, guid: 29868e73b638e48ca99a19ea58c48d90, type: 3}
|
||||
m_AssetVersion: 2
|
||||
m_OpaqueLayerMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_TransparentLayerMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_DefaultStencilState:
|
||||
overrideStencilState: 0
|
||||
stencilReference: 0
|
||||
stencilCompareFunction: 8
|
||||
passOperation: 2
|
||||
failOperation: 0
|
||||
zFailOperation: 0
|
||||
m_ShadowTransparentReceive: 1
|
||||
m_RenderingMode: 0
|
||||
m_DepthPrimingMode: 0
|
||||
m_CopyDepthMode: 0
|
||||
m_AccurateGbufferNormals: 0
|
||||
m_IntermediateTextureMode: 1
|
||||
--- !u!114 &2306520689226812427
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1749369493, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: EffectRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
RenderTargetName: _ScreenColorTexture
|
||||
SamplingShader: {fileID: 4800000, guid: 04c410c9937594faa893a11dceb85f7e, type: 3}
|
||||
BlitShader: {fileID: 4800000, guid: c17132b1f77d20942aa75f8429c0f8bc, type: 3}
|
||||
UberEffectPostShader: {fileID: 4800000, guid: 0f60273b12723a047a568dca50178e02, type: 3}
|
||||
UberEffectPostMaterial: {fileID: 0}
|
||||
RenderTargetSampling: 0
|
||||
UberEffectPostShaderName: Engine/Effect/UberEffectPost
|
||||
--- !u!114 &3389092417788614536
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 88650573, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: CharacterRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
--- !u!114 &4194082081604926240
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: -1467291068, guid: 58d7e77cafa53d146af19f551ff0a3f7, type: 3}
|
||||
m_Name: EffectRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
RenderTargetName: _ScreenColorTexture
|
||||
SamplingShader: {fileID: 4800000, guid: 04c410c9937594faa893a11dceb85f7e, type: 3}
|
||||
BlitShader: {fileID: 4800000, guid: c17132b1f77d20942aa75f8429c0f8bc, type: 3}
|
||||
UberEffectPostShader: {fileID: 4800000, guid: e4dc0f5e3a376594b807a6e398748f7b, type: 3}
|
||||
UberEffectPostMaterial: {fileID: 0}
|
||||
RenderTargetSampling: 0
|
||||
UberEffectPostShaderName: GYGame/Effect/UberEffectPost
|
||||
--- !u!114 &4324850898653140070
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: a5c14bbf9359ff549afb7ea0f09037df, type: 3}
|
||||
m_Name: NiloToonCharacterRendererFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
mCharacterRenderSetting:
|
||||
mOutlineSetting:
|
||||
ShouldRenderOutline: 1
|
||||
UseBigSceneOutline: 0
|
||||
BigSceneOutlineAtten: 250
|
||||
outlineWidthMultiplier: 1
|
||||
outlineTintColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
outlineWidthAutoAdjustToCameraDistanceAndFOV: 1
|
||||
mSelfShadowSetting:
|
||||
mEnableSelfShadow: 1
|
||||
mShadowUDAngle: 30
|
||||
mShadowLRAngle: 0
|
||||
mShadowMapSize: 512
|
||||
mMaxShadowDistance: 50
|
||||
mShadowDepthBias: 1
|
||||
mUseNDotLFix: 1
|
||||
mPerfectCullingForShadowCasters: 0
|
||||
mDebugShadowCameraVisibleBox: 0
|
||||
mMiscParamsSetting:
|
||||
mFinalColorMultiplier: {r: 1, g: 1, b: 1, a: 1}
|
||||
mRenderDepthNormal: 0
|
||||
mFinalColorIntensity: 1
|
||||
mCharacterValueMultiplier: 1
|
||||
mCharacterSaturateMultiplier: 1
|
||||
mGlobalMainDirectionalLightMultiplier: 1
|
||||
mOverridedLightUpDownAngle: 0
|
||||
mOverridedLightLRAngle: 0
|
||||
mOverrideLightDirectionIntensity: 0
|
||||
mSpecularIntensity: 1
|
||||
mSpecularColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
mUseMainLightDirForSpecular: 1
|
||||
mIndirecLightMinColor: {r: 0.01, g: 0.01, b: 0.01, a: 1}
|
||||
mIndirecLightMultiplier: 1
|
||||
mOverridedRimLightUpDownAngle: 0
|
||||
mOverridedRimLightLRAngle: 0
|
||||
mOverrideRimLightDirectionIntensity: 0
|
||||
mGlobalRimLightTintColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
mGlobalRimLightMultipiler: 1
|
||||
mRimLightStartFadeIn: 1
|
||||
mRimLightStartFadeOut: 50
|
||||
mRimLightAndShadowWidthMultiplier: 1
|
||||
mRimLightDepthDiffThresholdOffset: 0.5
|
||||
mAddLightMultipiler: {r: 1, g: 1, b: 1, a: 0.5}
|
||||
mAddLightMaxContribution: {r: 1, g: 1, b: 1, a: 1}
|
||||
mShadowColor: {r: 0, g: 0, b: 0, a: 0.5}
|
||||
mShadowAngle: 70
|
||||
mShadowLength: 1
|
||||
mShadowHeight: 0.1
|
||||
--- !u!114 &4815503470696360763
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 09d572d36a83daa4bb576ae814b31f16, type: 3}
|
||||
m_Name: TAA
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
PreviewInSceneView: 0
|
||||
UseMotionVector: 0
|
||||
RenderingMode: 0
|
||||
WorkOnPrepass: 0
|
||||
IgonreTransparentObject: 1
|
||||
Use32Bit: 0
|
||||
Shader: {fileID: 4800000, guid: 8c0d9373e5d33404ba27de953fd7a309, type: 3}
|
||||
--- !u!114 &7287650270393091027
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: -1608182031, guid: 58d7e77cafa53d146af19f551ff0a3f7, type: 3}
|
||||
m_Name: SceneRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
Settings:
|
||||
SceneParameters:
|
||||
CopyColor: 0
|
||||
ShadowColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
|
||||
BakedShadowStrength: 1
|
||||
GlobalBrightness: 1
|
||||
SceneDebug: 0
|
||||
NormalDebug: 0
|
||||
VertexColorDebug: 0
|
||||
DebugCustomValue:
|
||||
FogParameters:
|
||||
OpenFog: 1
|
||||
LineFogColor: {r: 0.5, g: 0, b: 0, a: 1}
|
||||
LineFogStartDistance: 10
|
||||
LineFogEndDistance: 100
|
||||
LineFogStrength: 0
|
||||
HeightFogColor: {r: 0.5, g: 0, b: 0, a: 1}
|
||||
HeightFogStartHeight: 0
|
||||
HeightFogFalloff: 0
|
||||
HeightFogDensity: 0
|
||||
HeightFogStartDistance: 0
|
||||
HeightFogMinOpacity: 0
|
||||
FogAmbient: 0
|
||||
--- !u!114 &8702178449611039958
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: -929177382, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: SceneRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
SSRComputeShader: {fileID: 7200000, guid: cf8964bc444045b4c830d86205bf1838, type: 3}
|
||||
Settings:
|
||||
SceneParameters:
|
||||
CopyColor: 0
|
||||
ShadowColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
|
||||
BakedShadowStrength: 1
|
||||
GlobalBrightness: 1
|
||||
SceneDebug: 0
|
||||
NormalDebug: 0
|
||||
VertexColorDebug: 0
|
||||
DebugCustomValue:
|
||||
FogParameters:
|
||||
OpenFog: 1
|
||||
LineFogColor: {r: 0.5, g: 0, b: 0, a: 1}
|
||||
LineFogStartDistance: 10
|
||||
LineFogEndDistance: 100
|
||||
LineFogStrength: 0
|
||||
HeightFogColor: {r: 0.5, g: 0, b: 0, a: 1}
|
||||
HeightFogStartHeight: 0
|
||||
HeightFogFalloff: 0
|
||||
HeightFogDensity: 0
|
||||
HeightFogStartDistance: 0
|
||||
HeightFogMinOpacity: 0
|
||||
FogAmbient: 0
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ad8f6453bdbe88e409d8fd14942f700d
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,575 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &-6867683717461137263
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1137749194, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: OutlineFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
settings:
|
||||
profilerTag: Selection Outline
|
||||
targetLayer:
|
||||
serializedVersion: 2
|
||||
m_Bits: 2048
|
||||
outlineMaterial: {fileID: 2100000, guid: 19d53d4eaf4ccbf479c715921ed8d533, type: 2}
|
||||
outlineColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
outlineWidth: 1.5
|
||||
renderPassEvent: 550
|
||||
--- !u!114 &-5451962332241528892
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 09d572d36a83daa4bb576ae814b31f16, type: 3}
|
||||
m_Name: TAA
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
PreviewInSceneView: 0
|
||||
UseMotionVector: 0
|
||||
RenderingMode: 0
|
||||
WorkOnPrepass: 0
|
||||
IgonreTransparentObject: 1
|
||||
Use32Bit: 0
|
||||
Shader: {fileID: 4800000, guid: 8c0d9373e5d33404ba27de953fd7a309, type: 3}
|
||||
--- !u!114 &-5192553248712847449
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: a5c14bbf9359ff549afb7ea0f09037df, type: 3}
|
||||
m_Name: NiloToonCharacterRendererFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
mCharacterRenderSetting:
|
||||
mOutlineSetting:
|
||||
ShouldRenderOutline: 1
|
||||
UseBigSceneOutline: 0
|
||||
BigSceneOutlineAtten: 250
|
||||
outlineWidthMultiplier: 1
|
||||
outlineTintColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
outlineWidthAutoAdjustToCameraDistanceAndFOV: 1
|
||||
mSelfShadowSetting:
|
||||
mEnableSelfShadow: 1
|
||||
mShadowUDAngle: 30
|
||||
mShadowLRAngle: 0
|
||||
mShadowMapSize: 512
|
||||
mMaxShadowDistance: 50
|
||||
mShadowDepthBias: 1
|
||||
mUseNDotLFix: 1
|
||||
mPerfectCullingForShadowCasters: 0
|
||||
mDebugShadowCameraVisibleBox: 0
|
||||
mMiscParamsSetting:
|
||||
mFinalColorMultiplier: {r: 1, g: 1, b: 1, a: 1}
|
||||
mRenderDepthNormal: 0
|
||||
mFinalColorIntensity: 1
|
||||
mCharacterValueMultiplier: 1
|
||||
mCharacterSaturateMultiplier: 1
|
||||
mGlobalMainDirectionalLightMultiplier: 1
|
||||
mOverridedLightUpDownAngle: 0
|
||||
mOverridedLightLRAngle: 0
|
||||
mOverrideLightDirectionIntensity: 0
|
||||
mSpecularIntensity: 1
|
||||
mSpecularColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
mUseMainLightDirForSpecular: 1
|
||||
mIndirecLightMinColor: {r: 0.01, g: 0.01, b: 0.01, a: 1}
|
||||
mIndirecLightMultiplier: 1
|
||||
mOverridedRimLightUpDownAngle: 0
|
||||
mOverridedRimLightLRAngle: 0
|
||||
mOverrideRimLightDirectionIntensity: 0
|
||||
mGlobalRimLightTintColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
mGlobalRimLightMultipiler: 1
|
||||
mRimLightStartFadeIn: 1
|
||||
mRimLightStartFadeOut: 50
|
||||
mRimLightAndShadowWidthMultiplier: 1
|
||||
mRimLightDepthDiffThresholdOffset: 0.5
|
||||
mAddLightMultipiler: {r: 1, g: 1, b: 1, a: 0.5}
|
||||
mAddLightMaxContribution: {r: 1, g: 1, b: 1, a: 1}
|
||||
mShadowColor: {r: 0, g: 0, b: 0, a: 0.5}
|
||||
mShadowAngle: 70
|
||||
mShadowLength: 1
|
||||
mShadowHeight: 0.1
|
||||
--- !u!114 &-4124891324505445878
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1733187461, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: FogOfWarRendererFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
settings:
|
||||
material: {fileID: 2100000, guid: 7fbd1a5d99ab35f40b6c59ea18b4d4f6, type: 2}
|
||||
renderPassEvent: 500
|
||||
--- !u!114 &-2417408525049291374
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1918578592, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: UIGrabPassFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
settings:
|
||||
renderScale: 1
|
||||
uiCameraName: CameraUi
|
||||
blurredMaterial: {fileID: 0}
|
||||
--- !u!114 &-2278940370034410917
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 770177407, guid: 38d7f5c1b6979824ab378dfa57dcc73e, type: 3}
|
||||
m_Name: GlobalScanFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
settings: {scanMaterial: {fileID: 2100000, guid: d67115296155f3c41ab11dbf400eaadf, type: 2}, renderPassEvent: 500}
|
||||
--- !u!114 &-1168925892722199119
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1749369493, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: EffectRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
RenderTargetName: _ScreenColorTexture
|
||||
SamplingShader: {fileID: 4800000, guid: 04c410c9937594faa893a11dceb85f7e, type: 3}
|
||||
BlitShader: {fileID: 4800000, guid: c17132b1f77d20942aa75f8429c0f8bc, type: 3}
|
||||
UberEffectPostShader: {fileID: 4800000, guid: 0f60273b12723a047a568dca50178e02, type: 3}
|
||||
UberEffectPostMaterial: {fileID: 0}
|
||||
RenderTargetSampling: 0
|
||||
UberEffectPostShaderName: Engine/Effect/UberEffectPost
|
||||
--- !u!114 &-448008844506635923
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1729826526, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: AfterTransparentColorAndCloakFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
m_Settings:
|
||||
afterTransparentColorTextureName: _AfterTransparentColorTex
|
||||
downsample: 0.5
|
||||
skipSceneView: 1
|
||||
--- !u!114 &-386890362932611403
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1918578592, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: UIGrabPassFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
settings:
|
||||
renderScale: 1
|
||||
uiCameraName: CameraUi
|
||||
blurredMaterial: {fileID: 0}
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: de640fe3d0db1804a85f9fc8f5cadab6, type: 3}
|
||||
m_Name: ForwardRenderer 4VeryHighQuality
|
||||
m_EditorClassIdentifier:
|
||||
debugShaders:
|
||||
debugReplacementPS: {fileID: 4800000, guid: cf852408f2e174538bcd9b7fda1c5ae7, type: 3}
|
||||
hdrDebugViewPS: {fileID: 4800000, guid: 573620ae32aec764abd4d728906d2587, type: 3}
|
||||
m_RendererFeatures: []
|
||||
m_RendererFeatureMap:
|
||||
m_UseNativeRenderPass: 0
|
||||
postProcessData: {fileID: 11400000, guid: 41439944d30ece34e96484bdb6645b55, type: 2}
|
||||
xrSystemData: {fileID: 11400000, guid: 60e1133243b97e347b653163a8c01b64, type: 2}
|
||||
shaders:
|
||||
blitPS: {fileID: 4800000, guid: c17132b1f77d20942aa75f8429c0f8bc, type: 3}
|
||||
copyDepthPS: {fileID: 4800000, guid: d6dae50ee9e1bfa4db75f19f99355220, type: 3}
|
||||
screenSpaceShadowPS: {fileID: 4800000, guid: 0f854b35a0cf61a429bd5dcfea30eddd, type: 3}
|
||||
samplingPS: {fileID: 4800000, guid: 04c410c9937594faa893a11dceb85f7e, type: 3}
|
||||
stencilDeferredPS: {fileID: 4800000, guid: e9155b26e1bc55942a41e518703fe304, type: 3}
|
||||
fallbackErrorPS: {fileID: 4800000, guid: e6e9a19c3678ded42a3bc431ebef7dbd, type: 3}
|
||||
fallbackLoadingPS: {fileID: 4800000, guid: 7f888aff2ac86494babad1c2c5daeee2, type: 3}
|
||||
materialErrorPS: {fileID: 4800000, guid: 5fd9a8feb75a4b5894c241777f519d4e, type: 3}
|
||||
coreBlitPS: {fileID: 4800000, guid: 93446b5c5339d4f00b85c159e1159b7c, type: 3}
|
||||
coreBlitColorAndDepthPS: {fileID: 4800000, guid: d104b2fc1ca6445babb8e90b0758136b, type: 3}
|
||||
blitHDROverlay: {fileID: 4800000, guid: a89bee29cffa951418fc1e2da94d1959, type: 3}
|
||||
cameraMotionVector: {fileID: 4800000, guid: c56b7e0d4c7cb484e959caeeedae9bbf, type: 3}
|
||||
objectMotionVector: {fileID: 4800000, guid: 7b3ede40266cd49a395def176e1bc486, type: 3}
|
||||
dataDrivenLensFlare: {fileID: 4800000, guid: 6cda457ac28612740adb23da5d39ea92, type: 3}
|
||||
terrainDetailLitPS: {fileID: 4800000, guid: f6783ab646d374f94b199774402a5144, type: 3}
|
||||
terrainDetailGrassPS: {fileID: 4800000, guid: e507fdfead5ca47e8b9a768b51c291a1, type: 3}
|
||||
terrainDetailGrassBillboardPS: {fileID: 4800000, guid: 29868e73b638e48ca99a19ea58c48d90, type: 3}
|
||||
m_AssetVersion: 2
|
||||
m_OpaqueLayerMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_TransparentLayerMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_DefaultStencilState:
|
||||
overrideStencilState: 0
|
||||
stencilReference: 0
|
||||
stencilCompareFunction: 8
|
||||
passOperation: 2
|
||||
failOperation: 0
|
||||
zFailOperation: 0
|
||||
m_ShadowTransparentReceive: 1
|
||||
m_RenderingMode: 0
|
||||
m_DepthPrimingMode: 0
|
||||
m_CopyDepthMode: 0
|
||||
m_AccurateGbufferNormals: 0
|
||||
m_IntermediateTextureMode: 1
|
||||
--- !u!114 &82592129937281022
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 88650573, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: CharacterRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
--- !u!114 &204869185421428586
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1749369493, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: EffectRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
RenderTargetName: _ScreenColorTexture
|
||||
SamplingShader: {fileID: 4800000, guid: 04c410c9937594faa893a11dceb85f7e, type: 3}
|
||||
BlitShader: {fileID: 4800000, guid: c17132b1f77d20942aa75f8429c0f8bc, type: 3}
|
||||
UberEffectPostShader: {fileID: 4800000, guid: 0f60273b12723a047a568dca50178e02, type: 3}
|
||||
UberEffectPostMaterial: {fileID: 0}
|
||||
RenderTargetSampling: 0
|
||||
UberEffectPostShaderName: Engine/Effect/UberEffectPost
|
||||
--- !u!114 &725774101322325403
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1749369493, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: EffectRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
RenderTargetName: _ScreenColorTexture
|
||||
SamplingShader: {fileID: 4800000, guid: 04c410c9937594faa893a11dceb85f7e, type: 3}
|
||||
BlitShader: {fileID: 4800000, guid: c17132b1f77d20942aa75f8429c0f8bc, type: 3}
|
||||
UberEffectPostShader: {fileID: 4800000, guid: 0f60273b12723a047a568dca50178e02, type: 3}
|
||||
UberEffectPostMaterial: {fileID: 0}
|
||||
RenderTargetSampling: 0
|
||||
UberEffectPostShaderName: Engine/Effect/UberEffectPost
|
||||
--- !u!114 &1268361228661498312
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1741494071, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: GlobalScanFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
settings:
|
||||
scanMaterial: {fileID: 2100000, guid: d67115296155f3c41ab11dbf400eaadf, type: 2}
|
||||
renderPassEvent: 500
|
||||
--- !u!114 &1282094438684808781
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: de6450284375eea4aa7e52135fd4e340, type: 3}
|
||||
m_Name: GlobalScanFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
settings: {scanMaterial: {fileID: 2100000, guid: d67115296155f3c41ab11dbf400eaadf, type: 2}, renderPassEvent: 500}
|
||||
--- !u!114 &1305107786418458812
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: -1181071007, guid: 58d7e77cafa53d146af19f551ff0a3f7, type: 3}
|
||||
m_Name: NiloToonCharacterRendererFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
mCharacterRenderSetting:
|
||||
mOutlineSetting:
|
||||
ShouldRenderOutline: 1
|
||||
UseBigSceneOutline: 0
|
||||
BigSceneOutlineAtten: 250
|
||||
outlineWidthMultiplier: 4
|
||||
outlineTintColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
outlineWidthAutoAdjustToCameraDistanceAndFOV: 1
|
||||
mSelfShadowSetting:
|
||||
mEnableSelfShadow: 0
|
||||
mShadowUDAngle: 20
|
||||
mShadowLRAngle: 1.4
|
||||
mShadowMapSize: 2048
|
||||
mMaxShadowDistance: 100
|
||||
mShadowDepthBias: 0.78
|
||||
mUseNDotLFix: 1
|
||||
mPerfectCullingForShadowCasters: 0
|
||||
mDebugShadowCameraVisibleBox: 0
|
||||
mMiscParamsSetting:
|
||||
mFinalColorMultiplier: {r: 1, g: 1, b: 1, a: 1}
|
||||
mRenderDepthNormal: 1
|
||||
mFinalColorIntensity: 1
|
||||
mCharacterValueMultiplier: 1
|
||||
mCharacterSaturateMultiplier: 0.2
|
||||
mGlobalMainDirectionalLightMultiplier: 0.75
|
||||
mOverridedLightUpDownAngle: -30
|
||||
mOverridedLightLRAngle: 30
|
||||
mOverrideLightDirectionIntensity: 1
|
||||
mSpecularIntensity: 1
|
||||
mSpecularColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
mUseMainLightDirForSpecular: 1
|
||||
mIndirecLightMinColor: {r: 0.01, g: 0.01, b: 0.01, a: 1}
|
||||
mIndirecLightMultiplier: 1
|
||||
mOverridedRimLightUpDownAngle: 180
|
||||
mOverridedRimLightLRAngle: -66
|
||||
mOverrideRimLightDirectionIntensity: 1
|
||||
mGlobalRimLightTintColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
mGlobalRimLightMultipiler: 4
|
||||
mRimLightStartFadeIn: 0
|
||||
mRimLightStartFadeOut: 100
|
||||
mRimLightAndShadowWidthMultiplier: 2
|
||||
mRimLightDepthDiffThresholdOffset: 0.5
|
||||
mAddLightMultipiler: {r: 1, g: 1, b: 1, a: 0.5}
|
||||
mAddLightMaxContribution: {r: 1, g: 1, b: 1, a: 1}
|
||||
mShadowColor: {r: 0, g: 0, b: 0, a: 0.5}
|
||||
mShadowAngle: 311
|
||||
mShadowLength: 0.89
|
||||
mShadowHeight: 0.1
|
||||
--- !u!114 &1346129852619499677
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: -1467291068, guid: 58d7e77cafa53d146af19f551ff0a3f7, type: 3}
|
||||
m_Name: EffectRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
RenderTargetName: _ScreenColorTexture
|
||||
SamplingShader: {fileID: 4800000, guid: 04c410c9937594faa893a11dceb85f7e, type: 3}
|
||||
BlitShader: {fileID: 4800000, guid: c17132b1f77d20942aa75f8429c0f8bc, type: 3}
|
||||
UberEffectPostShader: {fileID: 4800000, guid: e4dc0f5e3a376594b807a6e398748f7b, type: 3}
|
||||
UberEffectPostMaterial: {fileID: 0}
|
||||
RenderTargetSampling: 0
|
||||
UberEffectPostShaderName: GYGame/Effect/UberEffectPost
|
||||
--- !u!114 &2194802274330149393
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1918578592, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: UIGrabPassFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
settings:
|
||||
renderScale: 1
|
||||
uiCameraName: CameraUi
|
||||
blurredMaterial: {fileID: 0}
|
||||
--- !u!114 &3140139178374968523
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 88650573, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: CharacterRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
--- !u!114 &3838470855858450216
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: a319187780713924f9f1ad5521975bbe, type: 3}
|
||||
m_Name: AfterTransparentColorAndCloakFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
m_Settings:
|
||||
afterTransparentColorTextureName: _AfterTransparentColorTex
|
||||
downsample: 0.5
|
||||
skipSceneView: 1
|
||||
--- !u!114 &5386121282085986449
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1749369493, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: EffectRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
RenderTargetName: _ScreenColorTexture
|
||||
SamplingShader: {fileID: 4800000, guid: 04c410c9937594faa893a11dceb85f7e, type: 3}
|
||||
BlitShader: {fileID: 4800000, guid: c17132b1f77d20942aa75f8429c0f8bc, type: 3}
|
||||
UberEffectPostShader: {fileID: 4800000, guid: 0f60273b12723a047a568dca50178e02, type: 3}
|
||||
UberEffectPostMaterial: {fileID: 0}
|
||||
RenderTargetSampling: 0
|
||||
UberEffectPostShaderName: Engine/Effect/UberEffectPost
|
||||
--- !u!114 &6856233958429584178
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: -1608182031, guid: 58d7e77cafa53d146af19f551ff0a3f7, type: 3}
|
||||
m_Name: SceneRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
Settings:
|
||||
SceneParameters:
|
||||
CopyColor: 1
|
||||
ShadowColor: {r: 0.2719384, g: 0.35710105, b: 0.5943396, a: 1}
|
||||
BakedShadowStrength: 0.822
|
||||
GlobalBrightness: 1
|
||||
SceneDebug: 0
|
||||
NormalDebug: 0
|
||||
VertexColorDebug: 0
|
||||
DebugCustomValue:
|
||||
FogParameters:
|
||||
OpenFog: 1
|
||||
LineFogColor: {r: 0.2776789, g: 0.7451238, b: 0.9056604, a: 1}
|
||||
LineFogStartDistance: 75.6
|
||||
LineFogEndDistance: 163.8
|
||||
LineFogStrength: 0
|
||||
HeightFogColor: {r: 0.19215687, g: 0.5803922, b: 0.6117647, a: 1}
|
||||
HeightFogStartHeight: -0.62
|
||||
HeightFogFalloff: 1.974
|
||||
HeightFogDensity: 0.0072
|
||||
HeightFogStartDistance: 75.25
|
||||
HeightFogMinOpacity: 1
|
||||
FogAmbient: 1
|
||||
--- !u!114 &6876166720411419037
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1918578592, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: UIGrabPassFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
settings:
|
||||
renderScale: 1
|
||||
uiCameraName: CameraUi
|
||||
blurredMaterial: {fileID: 0}
|
||||
--- !u!114 &7225584861622257479
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 88650573, guid: d69daf564475a6b48a9faa2993a03538, type: 3}
|
||||
m_Name: CharacterRenderFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
--- !u!114 &9176359278972220443
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 0ef3e8d453aa10e459b89fc52b664d0e, type: 3}
|
||||
m_Name: GlobalScanFeature
|
||||
m_EditorClassIdentifier:
|
||||
m_Active: 1
|
||||
settings: {scanMaterial: {fileID: 2100000, guid: d67115296155f3c41ab11dbf400eaadf, type: 2}, renderPassEvent: 500}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ba4e2173e1767649a1a39195e744d4f
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,114 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: bf2edee5c58d82540a51f03df9d42094, type: 3}
|
||||
m_Name: UniversalRP-0VeryLowQuality
|
||||
m_EditorClassIdentifier:
|
||||
k_AssetVersion: 11
|
||||
k_AssetPreviousVersion: 11
|
||||
m_RendererType: 1
|
||||
m_RendererData: {fileID: 0}
|
||||
m_RendererDataList:
|
||||
- {fileID: 11400000, guid: 39a007eb403d850479271fda4ed87835, type: 2}
|
||||
m_DefaultRendererIndex: 0
|
||||
m_RequireDepthTexture: 0
|
||||
m_RequireOpaqueTexture: 0
|
||||
m_OpaqueDownsampling: 1
|
||||
m_SupportsTerrainHoles: 0
|
||||
m_SupportsHDR: 0
|
||||
m_HDRColorBufferPrecision: 0
|
||||
m_MSAA: 1
|
||||
m_RenderScale: 0.85
|
||||
m_UpscalingFilter: 0
|
||||
m_FsrOverrideSharpness: 0
|
||||
m_FsrSharpness: 0.92
|
||||
m_EnableLODCrossFade: 1
|
||||
m_LODCrossFadeDitheringType: 1
|
||||
m_ShEvalMode: 0
|
||||
m_MainLightRenderingMode: 1
|
||||
m_MainLightShadowsSupported: 0
|
||||
m_MainLightShadowmapResolution: 1024
|
||||
m_AdditionalLightsRenderingMode: 2
|
||||
m_AdditionalLightsPerObjectLimit: 4
|
||||
m_AdditionalLightShadowsSupported: 1
|
||||
m_AdditionalLightsShadowmapResolution: 1024
|
||||
m_AdditionalLightsShadowResolutionTierLow: 256
|
||||
m_AdditionalLightsShadowResolutionTierMedium: 512
|
||||
m_AdditionalLightsShadowResolutionTierHigh: 1024
|
||||
m_ReflectionProbeBlending: 0
|
||||
m_ReflectionProbeBoxProjection: 0
|
||||
m_ShadowDistance: 10
|
||||
m_ShadowCascadeCount: 1
|
||||
m_Cascade2Split: 0.16666667
|
||||
m_Cascade3Split: {x: 0.25800002, y: 0.5776}
|
||||
m_Cascade4Split: {x: 0.067, y: 0.2, z: 0.46700004}
|
||||
m_CascadeBorder: 0
|
||||
m_ShadowDepthBias: 1
|
||||
m_ShadowNormalBias: 1
|
||||
m_AnyShadowsSupported: 1
|
||||
m_SoftShadowsSupported: 0
|
||||
m_ConservativeEnclosingSphere: 0
|
||||
m_NumIterationsEnclosingSphere: 64
|
||||
m_SoftShadowQuality: 2
|
||||
m_AdditionalLightsCookieResolution: 256
|
||||
m_AdditionalLightsCookieFormat: 3
|
||||
m_UseSRPBatcher: 1
|
||||
m_SupportsDynamicBatching: 0
|
||||
m_MixedLightingSupported: 1
|
||||
m_SupportsLightCookies: 1
|
||||
m_SupportsLightLayers: 0
|
||||
m_DebugLevel: 0
|
||||
m_StoreActionsOptimization: 0
|
||||
m_EnableRenderGraph: 0
|
||||
m_UseAdaptivePerformance: 1
|
||||
m_ColorGradingMode: 0
|
||||
m_ColorGradingLutSize: 32
|
||||
m_UseFastSRGBLinearConversion: 0
|
||||
m_SupportDataDrivenLensFlare: 1
|
||||
m_ShadowType: 1
|
||||
m_LocalShadowsSupported: 0
|
||||
m_LocalShadowsAtlasResolution: 256
|
||||
m_MaxPixelLights: 0
|
||||
m_ShadowAtlasResolution: 256
|
||||
m_VolumeFrameworkUpdateMode: 0
|
||||
m_Textures:
|
||||
blueNoise64LTex: {fileID: 2800000, guid: e3d24661c1e055f45a7560c033dbb837, type: 3}
|
||||
bayerMatrixTex: {fileID: 2800000, guid: f9ee4ed84c1d10c49aabb9b210b0fc44, type: 3}
|
||||
m_PrefilteringModeMainLightShadows: 0
|
||||
m_PrefilteringModeAdditionalLight: 1
|
||||
m_PrefilteringModeAdditionalLightShadows: 0
|
||||
m_PrefilterXRKeywords: 1
|
||||
m_PrefilteringModeForwardPlus: 0
|
||||
m_PrefilteringModeDeferredRendering: 0
|
||||
m_PrefilteringModeScreenSpaceOcclusion: 0
|
||||
m_PrefilterDebugKeywords: 1
|
||||
m_PrefilterWriteRenderingLayers: 1
|
||||
m_PrefilterHDROutput: 1
|
||||
m_PrefilterSSAODepthNormals: 1
|
||||
m_PrefilterSSAOSourceDepthLow: 1
|
||||
m_PrefilterSSAOSourceDepthMedium: 1
|
||||
m_PrefilterSSAOSourceDepthHigh: 1
|
||||
m_PrefilterSSAOInterleaved: 1
|
||||
m_PrefilterSSAOBlueNoise: 1
|
||||
m_PrefilterSSAOSampleCountLow: 1
|
||||
m_PrefilterSSAOSampleCountMedium: 1
|
||||
m_PrefilterSSAOSampleCountHigh: 1
|
||||
m_PrefilterDBufferMRT1: 1
|
||||
m_PrefilterDBufferMRT2: 1
|
||||
m_PrefilterDBufferMRT3: 1
|
||||
m_PrefilterSoftShadowsQualityLow: 1
|
||||
m_PrefilterSoftShadowsQualityMedium: 1
|
||||
m_PrefilterSoftShadowsQualityHigh: 1
|
||||
m_PrefilterSoftShadows: 0
|
||||
m_PrefilterScreenCoord: 1
|
||||
m_PrefilterNativeRenderPass: 1
|
||||
m_ShaderVariantLogLevel: 0
|
||||
m_ShadowCascades: 1
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3d8cd8eb10307ce4b9685eccaedfb19d
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,114 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: bf2edee5c58d82540a51f03df9d42094, type: 3}
|
||||
m_Name: UniversalRP-1LowQuality
|
||||
m_EditorClassIdentifier:
|
||||
k_AssetVersion: 11
|
||||
k_AssetPreviousVersion: 11
|
||||
m_RendererType: 1
|
||||
m_RendererData: {fileID: 0}
|
||||
m_RendererDataList:
|
||||
- {fileID: 11400000, guid: ab43e90c602bfdb4e97e042f15d35169, type: 2}
|
||||
m_DefaultRendererIndex: 0
|
||||
m_RequireDepthTexture: 0
|
||||
m_RequireOpaqueTexture: 0
|
||||
m_OpaqueDownsampling: 1
|
||||
m_SupportsTerrainHoles: 0
|
||||
m_SupportsHDR: 1
|
||||
m_HDRColorBufferPrecision: 0
|
||||
m_MSAA: 1
|
||||
m_RenderScale: 1
|
||||
m_UpscalingFilter: 0
|
||||
m_FsrOverrideSharpness: 0
|
||||
m_FsrSharpness: 0.92
|
||||
m_EnableLODCrossFade: 1
|
||||
m_LODCrossFadeDitheringType: 1
|
||||
m_ShEvalMode: 0
|
||||
m_MainLightRenderingMode: 1
|
||||
m_MainLightShadowsSupported: 0
|
||||
m_MainLightShadowmapResolution: 1024
|
||||
m_AdditionalLightsRenderingMode: 1
|
||||
m_AdditionalLightsPerObjectLimit: 2
|
||||
m_AdditionalLightShadowsSupported: 1
|
||||
m_AdditionalLightsShadowmapResolution: 1024
|
||||
m_AdditionalLightsShadowResolutionTierLow: 256
|
||||
m_AdditionalLightsShadowResolutionTierMedium: 512
|
||||
m_AdditionalLightsShadowResolutionTierHigh: 1024
|
||||
m_ReflectionProbeBlending: 0
|
||||
m_ReflectionProbeBoxProjection: 0
|
||||
m_ShadowDistance: 10
|
||||
m_ShadowCascadeCount: 1
|
||||
m_Cascade2Split: 0.16666667
|
||||
m_Cascade3Split: {x: 0.25800002, y: 0.5776}
|
||||
m_Cascade4Split: {x: 0.067, y: 0.2, z: 0.46700004}
|
||||
m_CascadeBorder: 0
|
||||
m_ShadowDepthBias: 1
|
||||
m_ShadowNormalBias: 1
|
||||
m_AnyShadowsSupported: 1
|
||||
m_SoftShadowsSupported: 1
|
||||
m_ConservativeEnclosingSphere: 0
|
||||
m_NumIterationsEnclosingSphere: 64
|
||||
m_SoftShadowQuality: 2
|
||||
m_AdditionalLightsCookieResolution: 512
|
||||
m_AdditionalLightsCookieFormat: 3
|
||||
m_UseSRPBatcher: 1
|
||||
m_SupportsDynamicBatching: 0
|
||||
m_MixedLightingSupported: 1
|
||||
m_SupportsLightCookies: 1
|
||||
m_SupportsLightLayers: 0
|
||||
m_DebugLevel: 0
|
||||
m_StoreActionsOptimization: 0
|
||||
m_EnableRenderGraph: 0
|
||||
m_UseAdaptivePerformance: 1
|
||||
m_ColorGradingMode: 1
|
||||
m_ColorGradingLutSize: 32
|
||||
m_UseFastSRGBLinearConversion: 0
|
||||
m_SupportDataDrivenLensFlare: 1
|
||||
m_ShadowType: 1
|
||||
m_LocalShadowsSupported: 0
|
||||
m_LocalShadowsAtlasResolution: 256
|
||||
m_MaxPixelLights: 0
|
||||
m_ShadowAtlasResolution: 256
|
||||
m_VolumeFrameworkUpdateMode: 0
|
||||
m_Textures:
|
||||
blueNoise64LTex: {fileID: 2800000, guid: e3d24661c1e055f45a7560c033dbb837, type: 3}
|
||||
bayerMatrixTex: {fileID: 2800000, guid: f9ee4ed84c1d10c49aabb9b210b0fc44, type: 3}
|
||||
m_PrefilteringModeMainLightShadows: 0
|
||||
m_PrefilteringModeAdditionalLight: 3
|
||||
m_PrefilteringModeAdditionalLightShadows: 2
|
||||
m_PrefilterXRKeywords: 1
|
||||
m_PrefilteringModeForwardPlus: 0
|
||||
m_PrefilteringModeDeferredRendering: 0
|
||||
m_PrefilteringModeScreenSpaceOcclusion: 0
|
||||
m_PrefilterDebugKeywords: 1
|
||||
m_PrefilterWriteRenderingLayers: 1
|
||||
m_PrefilterHDROutput: 1
|
||||
m_PrefilterSSAODepthNormals: 1
|
||||
m_PrefilterSSAOSourceDepthLow: 1
|
||||
m_PrefilterSSAOSourceDepthMedium: 1
|
||||
m_PrefilterSSAOSourceDepthHigh: 1
|
||||
m_PrefilterSSAOInterleaved: 1
|
||||
m_PrefilterSSAOBlueNoise: 1
|
||||
m_PrefilterSSAOSampleCountLow: 1
|
||||
m_PrefilterSSAOSampleCountMedium: 1
|
||||
m_PrefilterSSAOSampleCountHigh: 1
|
||||
m_PrefilterDBufferMRT1: 1
|
||||
m_PrefilterDBufferMRT2: 1
|
||||
m_PrefilterDBufferMRT3: 1
|
||||
m_PrefilterSoftShadowsQualityLow: 1
|
||||
m_PrefilterSoftShadowsQualityMedium: 1
|
||||
m_PrefilterSoftShadowsQualityHigh: 1
|
||||
m_PrefilterSoftShadows: 0
|
||||
m_PrefilterScreenCoord: 1
|
||||
m_PrefilterNativeRenderPass: 1
|
||||
m_ShaderVariantLogLevel: 0
|
||||
m_ShadowCascades: 1
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 81260592605bd4049b035d0093204b55
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,114 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: bf2edee5c58d82540a51f03df9d42094, type: 3}
|
||||
m_Name: UniversalRP-2MediumQuality
|
||||
m_EditorClassIdentifier:
|
||||
k_AssetVersion: 11
|
||||
k_AssetPreviousVersion: 11
|
||||
m_RendererType: 1
|
||||
m_RendererData: {fileID: 0}
|
||||
m_RendererDataList:
|
||||
- {fileID: 11400000, guid: f454285041681a44999cc258865df961, type: 2}
|
||||
m_DefaultRendererIndex: 0
|
||||
m_RequireDepthTexture: 0
|
||||
m_RequireOpaqueTexture: 0
|
||||
m_OpaqueDownsampling: 1
|
||||
m_SupportsTerrainHoles: 0
|
||||
m_SupportsHDR: 1
|
||||
m_HDRColorBufferPrecision: 0
|
||||
m_MSAA: 1
|
||||
m_RenderScale: 1
|
||||
m_UpscalingFilter: 0
|
||||
m_FsrOverrideSharpness: 0
|
||||
m_FsrSharpness: 0.92
|
||||
m_EnableLODCrossFade: 1
|
||||
m_LODCrossFadeDitheringType: 1
|
||||
m_ShEvalMode: 0
|
||||
m_MainLightRenderingMode: 1
|
||||
m_MainLightShadowsSupported: 0
|
||||
m_MainLightShadowmapResolution: 512
|
||||
m_AdditionalLightsRenderingMode: 1
|
||||
m_AdditionalLightsPerObjectLimit: 4
|
||||
m_AdditionalLightShadowsSupported: 0
|
||||
m_AdditionalLightsShadowmapResolution: 512
|
||||
m_AdditionalLightsShadowResolutionTierLow: 256
|
||||
m_AdditionalLightsShadowResolutionTierMedium: 512
|
||||
m_AdditionalLightsShadowResolutionTierHigh: 1024
|
||||
m_ReflectionProbeBlending: 0
|
||||
m_ReflectionProbeBoxProjection: 0
|
||||
m_ShadowDistance: 15
|
||||
m_ShadowCascadeCount: 1
|
||||
m_Cascade2Split: 0.16666667
|
||||
m_Cascade3Split: {x: 0.25800002, y: 0.5776}
|
||||
m_Cascade4Split: {x: 0.067, y: 0.2, z: 0.46700004}
|
||||
m_CascadeBorder: 0
|
||||
m_ShadowDepthBias: 1
|
||||
m_ShadowNormalBias: 1
|
||||
m_AnyShadowsSupported: 1
|
||||
m_SoftShadowsSupported: 0
|
||||
m_ConservativeEnclosingSphere: 0
|
||||
m_NumIterationsEnclosingSphere: 64
|
||||
m_SoftShadowQuality: 2
|
||||
m_AdditionalLightsCookieResolution: 512
|
||||
m_AdditionalLightsCookieFormat: 3
|
||||
m_UseSRPBatcher: 1
|
||||
m_SupportsDynamicBatching: 0
|
||||
m_MixedLightingSupported: 1
|
||||
m_SupportsLightCookies: 1
|
||||
m_SupportsLightLayers: 0
|
||||
m_DebugLevel: 0
|
||||
m_StoreActionsOptimization: 0
|
||||
m_EnableRenderGraph: 0
|
||||
m_UseAdaptivePerformance: 1
|
||||
m_ColorGradingMode: 1
|
||||
m_ColorGradingLutSize: 32
|
||||
m_UseFastSRGBLinearConversion: 0
|
||||
m_SupportDataDrivenLensFlare: 1
|
||||
m_ShadowType: 1
|
||||
m_LocalShadowsSupported: 0
|
||||
m_LocalShadowsAtlasResolution: 256
|
||||
m_MaxPixelLights: 0
|
||||
m_ShadowAtlasResolution: 256
|
||||
m_VolumeFrameworkUpdateMode: 0
|
||||
m_Textures:
|
||||
blueNoise64LTex: {fileID: 2800000, guid: e3d24661c1e055f45a7560c033dbb837, type: 3}
|
||||
bayerMatrixTex: {fileID: 2800000, guid: f9ee4ed84c1d10c49aabb9b210b0fc44, type: 3}
|
||||
m_PrefilteringModeMainLightShadows: 0
|
||||
m_PrefilteringModeAdditionalLight: 3
|
||||
m_PrefilteringModeAdditionalLightShadows: 0
|
||||
m_PrefilterXRKeywords: 1
|
||||
m_PrefilteringModeForwardPlus: 0
|
||||
m_PrefilteringModeDeferredRendering: 0
|
||||
m_PrefilteringModeScreenSpaceOcclusion: 0
|
||||
m_PrefilterDebugKeywords: 1
|
||||
m_PrefilterWriteRenderingLayers: 1
|
||||
m_PrefilterHDROutput: 1
|
||||
m_PrefilterSSAODepthNormals: 1
|
||||
m_PrefilterSSAOSourceDepthLow: 1
|
||||
m_PrefilterSSAOSourceDepthMedium: 1
|
||||
m_PrefilterSSAOSourceDepthHigh: 1
|
||||
m_PrefilterSSAOInterleaved: 1
|
||||
m_PrefilterSSAOBlueNoise: 1
|
||||
m_PrefilterSSAOSampleCountLow: 1
|
||||
m_PrefilterSSAOSampleCountMedium: 1
|
||||
m_PrefilterSSAOSampleCountHigh: 1
|
||||
m_PrefilterDBufferMRT1: 1
|
||||
m_PrefilterDBufferMRT2: 1
|
||||
m_PrefilterDBufferMRT3: 1
|
||||
m_PrefilterSoftShadowsQualityLow: 1
|
||||
m_PrefilterSoftShadowsQualityMedium: 1
|
||||
m_PrefilterSoftShadowsQualityHigh: 1
|
||||
m_PrefilterSoftShadows: 0
|
||||
m_PrefilterScreenCoord: 1
|
||||
m_PrefilterNativeRenderPass: 1
|
||||
m_ShaderVariantLogLevel: 0
|
||||
m_ShadowCascades: 1
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7c88b4d5ed0b098458268b323ad17abb
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,114 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: bf2edee5c58d82540a51f03df9d42094, type: 3}
|
||||
m_Name: UniversalRP-3HighQuality
|
||||
m_EditorClassIdentifier:
|
||||
k_AssetVersion: 11
|
||||
k_AssetPreviousVersion: 11
|
||||
m_RendererType: 1
|
||||
m_RendererData: {fileID: 0}
|
||||
m_RendererDataList:
|
||||
- {fileID: 11400000, guid: ad8f6453bdbe88e409d8fd14942f700d, type: 2}
|
||||
m_DefaultRendererIndex: 0
|
||||
m_RequireDepthTexture: 1
|
||||
m_RequireOpaqueTexture: 1
|
||||
m_OpaqueDownsampling: 0
|
||||
m_SupportsTerrainHoles: 0
|
||||
m_SupportsHDR: 1
|
||||
m_HDRColorBufferPrecision: 0
|
||||
m_MSAA: 1
|
||||
m_RenderScale: 1
|
||||
m_UpscalingFilter: 0
|
||||
m_FsrOverrideSharpness: 0
|
||||
m_FsrSharpness: 0.92
|
||||
m_EnableLODCrossFade: 1
|
||||
m_LODCrossFadeDitheringType: 1
|
||||
m_ShEvalMode: 0
|
||||
m_MainLightRenderingMode: 1
|
||||
m_MainLightShadowsSupported: 1
|
||||
m_MainLightShadowmapResolution: 1024
|
||||
m_AdditionalLightsRenderingMode: 1
|
||||
m_AdditionalLightsPerObjectLimit: 4
|
||||
m_AdditionalLightShadowsSupported: 0
|
||||
m_AdditionalLightsShadowmapResolution: 1024
|
||||
m_AdditionalLightsShadowResolutionTierLow: 256
|
||||
m_AdditionalLightsShadowResolutionTierMedium: 512
|
||||
m_AdditionalLightsShadowResolutionTierHigh: 1024
|
||||
m_ReflectionProbeBlending: 0
|
||||
m_ReflectionProbeBoxProjection: 0
|
||||
m_ShadowDistance: 15
|
||||
m_ShadowCascadeCount: 2
|
||||
m_Cascade2Split: 0.26666668
|
||||
m_Cascade3Split: {x: 0.25800002, y: 0.5776}
|
||||
m_Cascade4Split: {x: 0.067, y: 0.2, z: 0.46700004}
|
||||
m_CascadeBorder: 0
|
||||
m_ShadowDepthBias: 1
|
||||
m_ShadowNormalBias: 1
|
||||
m_AnyShadowsSupported: 1
|
||||
m_SoftShadowsSupported: 0
|
||||
m_ConservativeEnclosingSphere: 0
|
||||
m_NumIterationsEnclosingSphere: 64
|
||||
m_SoftShadowQuality: 2
|
||||
m_AdditionalLightsCookieResolution: 1024
|
||||
m_AdditionalLightsCookieFormat: 3
|
||||
m_UseSRPBatcher: 1
|
||||
m_SupportsDynamicBatching: 0
|
||||
m_MixedLightingSupported: 1
|
||||
m_SupportsLightCookies: 1
|
||||
m_SupportsLightLayers: 0
|
||||
m_DebugLevel: 0
|
||||
m_StoreActionsOptimization: 0
|
||||
m_EnableRenderGraph: 0
|
||||
m_UseAdaptivePerformance: 1
|
||||
m_ColorGradingMode: 1
|
||||
m_ColorGradingLutSize: 32
|
||||
m_UseFastSRGBLinearConversion: 0
|
||||
m_SupportDataDrivenLensFlare: 1
|
||||
m_ShadowType: 1
|
||||
m_LocalShadowsSupported: 0
|
||||
m_LocalShadowsAtlasResolution: 256
|
||||
m_MaxPixelLights: 0
|
||||
m_ShadowAtlasResolution: 256
|
||||
m_VolumeFrameworkUpdateMode: 0
|
||||
m_Textures:
|
||||
blueNoise64LTex: {fileID: 2800000, guid: e3d24661c1e055f45a7560c033dbb837, type: 3}
|
||||
bayerMatrixTex: {fileID: 2800000, guid: f9ee4ed84c1d10c49aabb9b210b0fc44, type: 3}
|
||||
m_PrefilteringModeMainLightShadows: 3
|
||||
m_PrefilteringModeAdditionalLight: 3
|
||||
m_PrefilteringModeAdditionalLightShadows: 0
|
||||
m_PrefilterXRKeywords: 1
|
||||
m_PrefilteringModeForwardPlus: 0
|
||||
m_PrefilteringModeDeferredRendering: 0
|
||||
m_PrefilteringModeScreenSpaceOcclusion: 0
|
||||
m_PrefilterDebugKeywords: 1
|
||||
m_PrefilterWriteRenderingLayers: 1
|
||||
m_PrefilterHDROutput: 1
|
||||
m_PrefilterSSAODepthNormals: 1
|
||||
m_PrefilterSSAOSourceDepthLow: 1
|
||||
m_PrefilterSSAOSourceDepthMedium: 1
|
||||
m_PrefilterSSAOSourceDepthHigh: 1
|
||||
m_PrefilterSSAOInterleaved: 1
|
||||
m_PrefilterSSAOBlueNoise: 1
|
||||
m_PrefilterSSAOSampleCountLow: 1
|
||||
m_PrefilterSSAOSampleCountMedium: 1
|
||||
m_PrefilterSSAOSampleCountHigh: 1
|
||||
m_PrefilterDBufferMRT1: 1
|
||||
m_PrefilterDBufferMRT2: 1
|
||||
m_PrefilterDBufferMRT3: 1
|
||||
m_PrefilterSoftShadowsQualityLow: 1
|
||||
m_PrefilterSoftShadowsQualityMedium: 1
|
||||
m_PrefilterSoftShadowsQualityHigh: 1
|
||||
m_PrefilterSoftShadows: 0
|
||||
m_PrefilterScreenCoord: 1
|
||||
m_PrefilterNativeRenderPass: 1
|
||||
m_ShaderVariantLogLevel: 0
|
||||
m_ShadowCascades: 1
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 76ed49667b45f35418709c0859652e70
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,114 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: bf2edee5c58d82540a51f03df9d42094, type: 3}
|
||||
m_Name: UniversalRP-4VeryHighQuality
|
||||
m_EditorClassIdentifier:
|
||||
k_AssetVersion: 11
|
||||
k_AssetPreviousVersion: 11
|
||||
m_RendererType: 1
|
||||
m_RendererData: {fileID: 0}
|
||||
m_RendererDataList:
|
||||
- {fileID: 11400000, guid: 8ba4e2173e1767649a1a39195e744d4f, type: 2}
|
||||
m_DefaultRendererIndex: 0
|
||||
m_RequireDepthTexture: 1
|
||||
m_RequireOpaqueTexture: 1
|
||||
m_OpaqueDownsampling: 0
|
||||
m_SupportsTerrainHoles: 0
|
||||
m_SupportsHDR: 1
|
||||
m_HDRColorBufferPrecision: 0
|
||||
m_MSAA: 1
|
||||
m_RenderScale: 1
|
||||
m_UpscalingFilter: 0
|
||||
m_FsrOverrideSharpness: 0
|
||||
m_FsrSharpness: 0.92
|
||||
m_EnableLODCrossFade: 1
|
||||
m_LODCrossFadeDitheringType: 1
|
||||
m_ShEvalMode: 0
|
||||
m_MainLightRenderingMode: 1
|
||||
m_MainLightShadowsSupported: 1
|
||||
m_MainLightShadowmapResolution: 2048
|
||||
m_AdditionalLightsRenderingMode: 1
|
||||
m_AdditionalLightsPerObjectLimit: 4
|
||||
m_AdditionalLightShadowsSupported: 1
|
||||
m_AdditionalLightsShadowmapResolution: 1024
|
||||
m_AdditionalLightsShadowResolutionTierLow: 256
|
||||
m_AdditionalLightsShadowResolutionTierMedium: 512
|
||||
m_AdditionalLightsShadowResolutionTierHigh: 1024
|
||||
m_ReflectionProbeBlending: 0
|
||||
m_ReflectionProbeBoxProjection: 0
|
||||
m_ShadowDistance: 30
|
||||
m_ShadowCascadeCount: 2
|
||||
m_Cascade2Split: 0.2
|
||||
m_Cascade3Split: {x: 0.10000001, y: 0.3}
|
||||
m_Cascade4Split: {x: 0, y: 0.001, z: 0.24375}
|
||||
m_CascadeBorder: 0
|
||||
m_ShadowDepthBias: 1
|
||||
m_ShadowNormalBias: 0
|
||||
m_AnyShadowsSupported: 1
|
||||
m_SoftShadowsSupported: 1
|
||||
m_ConservativeEnclosingSphere: 0
|
||||
m_NumIterationsEnclosingSphere: 64
|
||||
m_SoftShadowQuality: 2
|
||||
m_AdditionalLightsCookieResolution: 1024
|
||||
m_AdditionalLightsCookieFormat: 3
|
||||
m_UseSRPBatcher: 1
|
||||
m_SupportsDynamicBatching: 0
|
||||
m_MixedLightingSupported: 1
|
||||
m_SupportsLightCookies: 1
|
||||
m_SupportsLightLayers: 0
|
||||
m_DebugLevel: 0
|
||||
m_StoreActionsOptimization: 0
|
||||
m_EnableRenderGraph: 0
|
||||
m_UseAdaptivePerformance: 1
|
||||
m_ColorGradingMode: 1
|
||||
m_ColorGradingLutSize: 32
|
||||
m_UseFastSRGBLinearConversion: 0
|
||||
m_SupportDataDrivenLensFlare: 1
|
||||
m_ShadowType: 1
|
||||
m_LocalShadowsSupported: 0
|
||||
m_LocalShadowsAtlasResolution: 256
|
||||
m_MaxPixelLights: 0
|
||||
m_ShadowAtlasResolution: 256
|
||||
m_VolumeFrameworkUpdateMode: 0
|
||||
m_Textures:
|
||||
blueNoise64LTex: {fileID: 2800000, guid: e3d24661c1e055f45a7560c033dbb837, type: 3}
|
||||
bayerMatrixTex: {fileID: 2800000, guid: f9ee4ed84c1d10c49aabb9b210b0fc44, type: 3}
|
||||
m_PrefilteringModeMainLightShadows: 3
|
||||
m_PrefilteringModeAdditionalLight: 3
|
||||
m_PrefilteringModeAdditionalLightShadows: 2
|
||||
m_PrefilterXRKeywords: 1
|
||||
m_PrefilteringModeForwardPlus: 0
|
||||
m_PrefilteringModeDeferredRendering: 0
|
||||
m_PrefilteringModeScreenSpaceOcclusion: 0
|
||||
m_PrefilterDebugKeywords: 1
|
||||
m_PrefilterWriteRenderingLayers: 1
|
||||
m_PrefilterHDROutput: 1
|
||||
m_PrefilterSSAODepthNormals: 1
|
||||
m_PrefilterSSAOSourceDepthLow: 1
|
||||
m_PrefilterSSAOSourceDepthMedium: 1
|
||||
m_PrefilterSSAOSourceDepthHigh: 1
|
||||
m_PrefilterSSAOInterleaved: 1
|
||||
m_PrefilterSSAOBlueNoise: 1
|
||||
m_PrefilterSSAOSampleCountLow: 1
|
||||
m_PrefilterSSAOSampleCountMedium: 1
|
||||
m_PrefilterSSAOSampleCountHigh: 1
|
||||
m_PrefilterDBufferMRT1: 1
|
||||
m_PrefilterDBufferMRT2: 1
|
||||
m_PrefilterDBufferMRT3: 1
|
||||
m_PrefilterSoftShadowsQualityLow: 1
|
||||
m_PrefilterSoftShadowsQualityMedium: 1
|
||||
m_PrefilterSoftShadowsQualityHigh: 1
|
||||
m_PrefilterSoftShadows: 0
|
||||
m_PrefilterScreenCoord: 1
|
||||
m_PrefilterNativeRenderPass: 1
|
||||
m_ShaderVariantLogLevel: 0
|
||||
m_ShadowCascades: 1
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f3546fc18618b9a488f828377c92aa47
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,34 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 2ec995e51a6e251468d2a3fd8a686257, type: 3}
|
||||
m_Name: UniversalRenderPipelineGlobalSettings
|
||||
m_EditorClassIdentifier:
|
||||
k_AssetVersion: 3
|
||||
m_RenderingLayerNames:
|
||||
- Default
|
||||
m_ValidRenderingLayers: 1
|
||||
lightLayerName0:
|
||||
lightLayerName1:
|
||||
lightLayerName2:
|
||||
lightLayerName3:
|
||||
lightLayerName4:
|
||||
lightLayerName5:
|
||||
lightLayerName6:
|
||||
lightLayerName7:
|
||||
m_StripDebugVariants: 1
|
||||
m_StripUnusedPostProcessingVariants: 0
|
||||
m_StripUnusedVariants: 1
|
||||
m_StripUnusedLODCrossFadeVariants: 1
|
||||
m_StripScreenCoordOverrideVariants: 1
|
||||
supportRuntimeDebugDisplay: 0
|
||||
m_ShaderVariantLogLevel: 0
|
||||
m_ExportShaderVariants: 1
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d71d555e4685d9542882aaf4f6b61df8
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1 @@
|
||||
refresh 2026-09-21T11:42:00.4196961+08:00
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 617dcf84faf5e0f40920c08af2539308
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,173 @@
|
||||
---
|
||||
name: unity-pipeline
|
||||
description: Drive a running Unity Editor or development Player from the command line via the unity-pipeline package — install the package, keep the editor ticking while unfocused, run the edit→recompile→run_tests loop, evaluate C#, and code-reload files at runtime. Use when an agent needs to control a live Unity instance, automate Unity tests, recompile scripts headlessly, or apply runtime reloads. Assumes the `unity` CLI is on PATH.
|
||||
---
|
||||
|
||||
# Unity Pipeline (agent control)
|
||||
|
||||
Invoke commands with `unity command <name> [args]`. Run `unity command` with no name to
|
||||
list what an instance exposes. Two servers exist: **Editor** (`7800-7849`, auto-starts with
|
||||
the editor) and **Runtime** (`7900-7949`, only in a dev Player build). See the package
|
||||
`README.md` "Commands" reference for the full list and parameters.
|
||||
|
||||
## 1. Install & verify
|
||||
|
||||
```bash
|
||||
unity pipeline install # install into current project (or --project-path)
|
||||
unity pipeline list # confirm the editor instance + server are reachable
|
||||
unity command editor_status # confirm the editor server answers
|
||||
```
|
||||
|
||||
## 2. Autonomous edit loop (Editor)
|
||||
|
||||
This is the core agent workflow: keep the editor alive, change code, recompile, test.
|
||||
|
||||
```bash
|
||||
# 1. Keep the editor ticking even when unfocused/minimized. REQUIRED before headless work —
|
||||
# Unity otherwise throttles or stalls update/compile when it isn't the active app.
|
||||
unity command set_autotick --enable true
|
||||
|
||||
# 2. Edit C# source files on disk normally.
|
||||
|
||||
# 3. Recompile (async: triggers a domain reload, then poll until done).
|
||||
unity command recompile
|
||||
unity command recompile_status # repeat until "completed" or "up_to_date"
|
||||
# Tolerate connection errors while the domain reload is in flight — that is expected.
|
||||
# If recompile_status reports failed=true, read its "errors" array and fix before testing.
|
||||
|
||||
# 4. (Optional) List available tests without running them.
|
||||
unity command list_tests --mode editor # mode: all | editor | playmode
|
||||
|
||||
# 5. Run tests (filter to keep it fast).
|
||||
unity command run_tests --mode editor --filter MyFixture.MyTest
|
||||
```
|
||||
|
||||
`run_tests` modes: `all` | `editor` | `playmode`. `filter_type`: `testName` | `assembly` |
|
||||
`category`. For long runs use `--async_tests true` and poll `unity command test_status`
|
||||
(abort with `unity command cancel_tests`).
|
||||
|
||||
> **Known caveat:** when any selected test *fails*, `run_tests` may surface an opaque
|
||||
> result instead of the failure details. Re-run a narrower `--filter`, or inspect the
|
||||
> editor's Test Runner / logs to get the real failure.
|
||||
|
||||
**Reading exit codes.** They separate "rewrite the invocation" from "the Editor failed", which
|
||||
is the distinction this loop depends on:
|
||||
|
||||
| Exit | Meaning | What to do |
|
||||
|------|---------|------------|
|
||||
| `2` | Bad arguments — a misspelled flag, a wrong type, too many positionals. Nothing ran. | Fix the command line and retry. The error names the problem and often suggests the right flag. |
|
||||
| `6` | The command ran and failed, or the Editor could not service it. | Read the error; retrying the same invocation will not help. |
|
||||
|
||||
Do not retry an exit 2 unchanged, and do not rewrite a command line on an exit 6.
|
||||
|
||||
## 3. Runtime code reload
|
||||
|
||||
Change gameplay code in a **running** game with no domain reload. The game must be live:
|
||||
enter Editor Play Mode (`unity command editor_play`) or run a dev Player. A
|
||||
The runtime Pipeline driver (enabled via Project Settings > Pipeline > Runtime) auto-discovers tagged methods on `Awake` (no manual
|
||||
registration). Mono only — Editor Play Mode and Mono desktop dev builds, not IL2CPP. The token
|
||||
is auto-injected for local requests.
|
||||
|
||||
### `reload_file`
|
||||
|
||||
Edit the method body directly; no separate file, no boilerplate.
|
||||
|
||||
1. Tag the method `[CodeReload]` on the MonoBehaviour.
|
||||
2. Edit its body on disk.
|
||||
3. Apply (re-run to iterate):
|
||||
```bash
|
||||
unity command reload_file --filename Assets/Spinner.cs
|
||||
```
|
||||
Add `--pdb` to make it debuggable — emits a portable PDB mapped to your source so breakpoints in
|
||||
the original file bind (attach the IDE + enable Editor Attaching; compiles unoptimized):
|
||||
```bash
|
||||
unity command reload_file --filename Assets/Spinner.cs --pdb
|
||||
```
|
||||
|
||||
Constraints: `void` instance methods only; **public** members only; debugging requires `--pdb`
|
||||
(the default emits no symbols).
|
||||
|
||||
`unity command codereload_status` shows active overrides. `reload_file` options: `--timeout <ms>`
|
||||
(default 30000), `--assemblyDir <dir>` (persist DLLs instead of in-memory);
|
||||
`cleanup_codereload --assemblyDir <dir>` clears old DLLs.
|
||||
|
||||
## 4. Bulk construction: `run_script` (the builder pattern)
|
||||
|
||||
For bulk work — creating many objects, wiring fields, generating content — put the code in a
|
||||
**versioned project script** and run a named static entry point with `run_script`. No domain
|
||||
reload, no code carried through the protocol; iterating costs an in-memory compile (< ~2s), not
|
||||
a 15–20s recompile.
|
||||
|
||||
```bash
|
||||
# 1. Write the builder OUTSIDE Assets/ (so the write triggers no asset import / domain reload),
|
||||
# e.g. AgentScripts/Build.cs: public static class Build { public static int All() { ... } }
|
||||
# 2. Run it — relative paths resolve against the project root (the parent of Assets/):
|
||||
unity command run_script --file AgentScripts/Build.cs --entry Build.All
|
||||
# 3. Iterate: edit the file, re-run. Useful extras:
|
||||
unity command run_script --file AgentScripts/Build.cs --dry_run true # compile-only check: diagnostics, nothing loaded or executed
|
||||
unity command run_script --file AgentScripts/Build.cs --entry Build.All --args '[3, "Green"]'
|
||||
```
|
||||
|
||||
Rules of thumb:
|
||||
|
||||
- **Code goes in files on disk via `run_script`; `eval` is for genuinely ad-hoc one-liners.**
|
||||
Never ship multi-line escaped C# strings through `eval` — write the file, run the entry.
|
||||
- Compiles see the project's **active editor defines** (`UNITY_EDITOR`, version/platform symbols);
|
||||
`--defines` appends extra symbols on top.
|
||||
- Entry points may be `async Task`/`Task<T>` — they are awaited asynchronously (the editor keeps
|
||||
pumping, so awaits resuming on Unity's context work naturally) and `Task<T>.Result` is returned.
|
||||
The wait is bounded by `timeout_ms`; on expiry the task keeps running detached.
|
||||
- Runtime exceptions come back with `file:line` mapped to your source (a source-mapped PDB is
|
||||
always emitted for executing runs; they compile unoptimized).
|
||||
- `--mode hotpatch` instead applies `[CodeReload]` in-place method replacements (delegates to
|
||||
`reload_file`); `entry`/`args`/`dry_run` are rejected there and `references`/`defines` don't apply.
|
||||
|
||||
## 5. Quick C# eval (Runtime)
|
||||
|
||||
For genuinely ad-hoc one-liners only — anything longer belongs in a file run via `run_script`.
|
||||
|
||||
```bash
|
||||
unity command eval "return 2 + 2;"
|
||||
# Or evaluate a .cs file on disk (contents run through the same path):
|
||||
unity command eval_file Assets/Scratch.cs
|
||||
```
|
||||
|
||||
## 5. Project audit (Editor)
|
||||
|
||||
Static-analysis scan via Project Auditor, producing a CSV of issues to fix. Trigger, poll, read.
|
||||
|
||||
```bash
|
||||
unity command audit # optional: --categories Code,ProjectSetting --output my.csv
|
||||
unity command audit_status # repeat until terminal status
|
||||
```
|
||||
|
||||
`audit_status` is terminal on `completed` (with `csvPath` + `issueCount`), `failed`, `unavailable`, or
|
||||
`interrupted` (a domain reload killed the scan — just re-run `audit`). Polling stays responsive while a
|
||||
Code-category scan compiles assemblies and holds the main thread. One scan at a time: a second `audit`
|
||||
returns `busy`. There is no cancel — stop polling to abandon a scan.
|
||||
|
||||
CSV columns: `Category, Severity, Areas, Description, RelativePath, Line, DescriptorId, Recommendation`
|
||||
(diagnostics only, so every row is something to fix; `Recommendation` says how).
|
||||
|
||||
> **Requires Project Auditor plus its rules.** `unavailable` means the Editor has no Project Auditor,
|
||||
> or it has no analysis rules — in a built-in-module Editor the rules live in the separate
|
||||
> `com.unity.project-auditor-rules` package (`unity command package_add --identifier
|
||||
> com.unity.project-auditor-rules --confirm true`). Read the `message` field; the command never reports
|
||||
> an empty `completed` scan that would look like a clean project.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **`set_autotick` first.** Without it, recompile and tests can hang while the editor is
|
||||
unfocused. The package's watchdog relies on the tick loop staying alive.
|
||||
- **A stuck command may mean a modal dialog is open**, not a hang — a dialog blocks the main
|
||||
thread until dismissed. If a command runs long, check `unity command editor_status` (answers
|
||||
instantly even when blocked); `status: "blocked_by_dialog"` means stop retrying and tell the
|
||||
human what's blocking (its `dialog.title`/`message`/`buttons`) — it can't be clicked over CLI.
|
||||
- **Code reload needs the game running.** `reload_file` applies to a live
|
||||
game — enter Editor Play Mode (`unity command editor_play`) first, or run a dev Player.
|
||||
- **Player-only commands need a dev Player.** `log`, `set_timescale`, `runtime_status`, etc. hit
|
||||
the Runtime server, which does not run in the Editor.
|
||||
- **Async commands poll.** `recompile`→`recompile_status`, `run_tests --async_tests`→
|
||||
`test_status`. Never assume completion from the trigger call's response.
|
||||
- **Target a specific instance** with `--instance host:port` or `--project-path <path>`
|
||||
when more than one is running.
|
||||
@@ -0,0 +1 @@
|
||||
{"timestamp":1789141928,"signature":"crpfmI3LnQCNoOwEW623mwuNDp5ggQAsQ4rqDDXqjY2D8pW41oRle0sYLtZo+w4nEX5ZFjoD5ByM4oJ0obxDdR00UH7Nk+vvh92pckG22pfhohvztPpleny6pFGnTJYaM6smhnyzV5nsA6kplXA0IATP/tWZC/ufFD6L7jrUr/ySyc1n1R0WwIjDyweod+RIrSNZovC7c7j5UFOprIBRqFMr606qLiEkW0vioMNDAOIZ6smTPml6a/s/CJvSr4FzddWGnj76cWWfBoxlhjICwlJ5Wbzr2G44AXLLTAK+1VP8McmY080Nq0eb2OMOUYdWE2DeQ/Wowe+Yo958pOYGe5KNvi2GtlyfFVbHzCCTsUlyZ3+D7XFya/uXZ4+cLacgSHZl9kOSPWskVuyNR30iG9vV24gXyecxJhKbO4orFUb0kUz/DRT1Iq8HMV4U54DAfa6Se0l8lAXbq2g79/NwTMkzVUwKy8bSCrwF9WtWLctAPRN5yetOMpBWDjSDtp0I","publicKey":"LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQm9qQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FZOEFNSUlCaWdLQ0FZRUFzdUhXYUhsZ0I1cVF4ZEJjTlJKSAordHR4SmoxcVY1NTdvMlZaRE1XaXhYRVBkRTBEMVFkT1JIRXNSS1RscmplUXlERU83ZlNQS0ZwZ1A3MU5TTnJCCkFHM2NFSU45aHNQVDhOVmllZmdWem5QTkVMenFkVmdEbFhpb2VpUnV6OERKWFgvblpmU1JWKytwbk9ySTRibG4KS0twelJlNW14OTc1SjhxZ1FvRktKT0NNRlpHdkJMR2MxSzZZaEIzOHJFODZCZzgzbUovWjBEYkVmQjBxZm13cgo2ZDVFUXFsd0E5Y3JZT1YyV1VpWXprSnBLNmJZNzRZNmM1TmpBcEFKeGNiaTFOaDlRVEhUcU44N0ZtMDF0R1ZwCjVNd1pXSWZuYVRUemEvTGZLelR5U0pka0tldEZMVGdkYXpMYlpzUEE2aHBSK0FJRTJhc0tLTi84UUk1N3UzU2cKL2xyMnZKS1IvU2l5eEN1Q20vQWJkYnJMbXk0WjlSdm1jMGdpclA4T0lLQWxBRWZ2TzV5Z2hSKy8vd1RpTFlzUQp1SllDM0V2UE16ZGdKUzdGR2FscnFLZzlPTCsxVzROY05yNWdveVdSUUJ0cktKaWlTZEJVWmVxb0RvSUY5NHpCCndGbzJJT1JFdXFqcU51M3diMWZIM3p1dGdtalFra3IxVjJhd3hmcExLWlROQWdNQkFBRT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg"}
|
||||
@@ -0,0 +1,158 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this package will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.7.0-exp.1] - 2026-09-10
|
||||
|
||||
### Changed
|
||||
|
||||
- [AUTHAPI-71] The Pipeline runtime assemblies and the bundled Roslyn DLLs are no longer included in non-development Player builds. Enabling the runtime server for a release build now has no effect instead of warning. Add the `ENABLE_RUNTIME_PIPELINE` scripting define symbol to include them in a build that is not a Development Build.
|
||||
- [AUTHAPI-71] The code reload attributes moved to their own `Unity.Pipeline.Attributes` assembly, so code tagged with them still compiles for a release build, where they do nothing. An assembly definition that referenced `Unity.Pipeline` to get the attributes must add `Unity.Pipeline.Attributes` to its references; scripts that are not in an assembly definition need no change.
|
||||
- [UUM-151473] `console` is now robust across domain reloads, Editor restarts and play-mode transitions. It reports the capture session alongside its cursor and refuses a cursor it cannot honor rather than returning an empty result; it backfills from the Editor console's own store, so compile errors logged before capture started are visible; and it re-arms itself when Unity drops the log callback. Every response also carries the Editor's own console counts and compile-failure flag, so a client can tell in one call when the buffer and the Console window disagree.
|
||||
- [UUM-151473] `clear_console` moved to the runtime assembly beside `console` and `console_status`, so all three console commands live in one file and `clear_console` now works in player builds too (where it clears the captured buffer; there is no Editor console to clear).
|
||||
|
||||
### Added
|
||||
|
||||
- [AUTHAPI-25] Added server-side condition waiting: a wait command that polls an editor/object state until it holds (or times out), synchronously or in the background with status polling and cancelation.
|
||||
- [AUTHAPI-51] `CommandRegistry.RegisterCommand`/`UnregisterCommand`: register commands at runtime without a `[CliCommand]` attribute, for command sets that depend on runtime state. A dynamic registration behaves exactly like an attribute-declared one — same `/api/commands` metadata, same `[CliArg]` parameter discovery on the handler, same `/api/exec` execution — and additionally accepts an instance-bound delegate, so a command can run against a specific object. Registrations survive a discovery-mechanism swap and last until unregistered or the domain reloads — one made while in Play Mode is dropped when Play Mode exits, so a handler registered from `Awake` re-registers cleanly on the next entry even with domain reload disabled. A name already in use throws rather than shadowing the existing command. See `Documentation~/creating-commands.md`.
|
||||
- [UUM-151473] `console` responses carry `counts` (entries the buffer retains per severity) and `groundTruth` (the Editor's own `compilationFailed`, `compiling`, console entry counts, whether the buffer was seeded, and the sample's `ageMs`), so a client can spot the buffer disagreeing with the Console window in one round trip.
|
||||
- [UUM-151473] New `console_status` command: the same counters and compile-failure flag without the entries, cheap enough to poll while a compile runs.
|
||||
- [UUM-151473] `console` entries carry `logType` (Unity's exact `LogType` name, keeping the Exception/Assert distinction that `level` collapses) and `seeded` (read from the Editor console store rather than captured live, so the timestamp is the read time).
|
||||
- [UUM-151473] `recompile_status` reports `compilationFailed`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- [UUM-151473] Fixed `console` returning an empty result that was indistinguishable from "nothing new" when the `since` cursor came from an earlier Editor session. Sequence numbers restart with the process (the buffer is persisted under `Temp/`, which Unity deletes at every launch), so such a cursor filtered out every entry held. Responses now carry a `session`, `console` accepts `since_session`, and a cursor that cannot be honored returns the tail with `reset=true` and `dropped=true`.
|
||||
- [UUM-151473] Fixed `ConsoleLogBuffer.Load` discarding entries logged since the snapshot it restores, and lowering the sequence counter when the snapshot was stale. It now merges by sequence number and never rewinds a cursor already handed out.
|
||||
- [UUM-151473] Fixed `console` never reporting Editor compile errors logged before capture started. Those are logged as sticky entries that survive every console clear until the next compile completes, so they can stand in the Console window indefinitely; the buffer is now backfilled once per session from the Editor console's own store.
|
||||
- [UUM-151473] Fixed console capture staying dead after Unity unregistered the native log callback with no observable symptom. The Editor now samples the console's own counts and re-arms capture when they move while the buffer's cursor does not.
|
||||
- [UUM-151473] Fixed `recompile` reporting `up_to_date` while compile errors were still standing. A failed editor compile suppresses the domain reload and leaves the errors in place, so a repeat call with nothing changed compiled nothing and erased the recorded failure. Both `recompile` and `recompile_status` now consult Unity's native compile-failure flag, which also covers the status file being deleted with `Temp/` at every Editor launch.
|
||||
- [UUM-151473] Fixed `clear_console` clearing only one of the package's two console buffers, so `console` kept returning entries that had been cleared.
|
||||
- [UUM-151968] The runtime instance descriptor now writes to `Application.persistentDataPath` on Android instead of beside `Application.dataPath`, which is read-only there.
|
||||
- [AUTHAPI-49] `Pipeline_SessionStopped` now fires on `EditorApplication.wantsToQuit` instead of `EditorApplication.quitting`, since the Analytics service is already shutting down by the time `quitting` runs and the event was never actually sent.
|
||||
|
||||
### Removed
|
||||
|
||||
- [UUM-151473] Removed `get_console_logs`, which duplicated `console` over a second buffer of its own — every log line was captured twice, and `clear_console` cleared only one of them. Use `console`; its new `logType` field carries the exact log type `get_console_logs` reported.
|
||||
|
||||
## [0.6.0-exp.1] - 2026-08-31
|
||||
|
||||
### Changed
|
||||
- Hot reload is now called **code reload** throughout. The attributes are `[CodeReload]`, `[OnCodeReload]` and `[CodeReloadOverrideMethod]` in namespace `Unity.Pipeline.CodeReload`; the registry is `CodeReloadRegistry`; the commands `hotreload_status` and `cleanup_hotreload` are now `codereload_status` and `cleanup_codereload` (tag `scripts/codereload`); the sample lives under `Samples~/CodeReload`; the documentation page is `code-reload.md`. The watcher and the folder push now pick up `.cs` files that mention `CodeReload` instead of `HotReload`. The `[HotReload]` family no longer exists, so user code must be updated.
|
||||
- [AUTHAPI-49] `BasePipelineServer.OnTransactionProcessed(requestJson, responseJson)` is replaced by `OnCommandDone(in CommandExecutionInfo)`, which also carries the command that ran, whether it succeeded, and how long it took. A detached job now reports when it actually completes, not only when its job handle was returned.
|
||||
- [UUM-149580] Reorganized the Pipeline Runtime settings page (Project Settings > Pipeline > Runtime) into "Server" and "Runtime Behavior" groups, moved the status/security HelpBox to the top, and added an always-visible feature description when there's nothing to warn about. All fields except Max Work Items Per Frame are now read-only while in Play Mode, since edits to them would silently not apply until the next Play session or build; Max Work Items Per Frame stays live-editable — `RuntimePipelineDriver` polls it fresh every frame (cheaply, gated on the settings file's timestamp), matching the pre-redesign component's behavior for that field.
|
||||
- [UUM-149580] Every field except Enable In Builds itself is now greyed out on the Pipeline Runtime settings page while Enable In Builds is off, instead of staying editable for a server that will not run.
|
||||
- `reload_file` result contract, stated: a reload that compiles but applies NO methods now fails with the per-method reasons (previously a silent success); a file whose `[HotReload]` methods all match the compiled baseline reports an explicit "up to date" success — naming any stale overrides it reverted — instead of "hot reload successful with 0 methods".
|
||||
- [AUTHAPI-26] A non-positive `timeout`/`timeout_ms` on `eval`, `eval_file`, or `run_script` now returns a clear `400 Bad Request` instead of an opaque dispatcher timeout.
|
||||
- Reformatted CHANGELOG.md to more accurately track [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
|
||||
- [AUTHAPI-59] Test fixtures and test-only helper classes under `Tests/` are now `internal` instead of `public` — they were never part of the package's public API surface.
|
||||
- [AUTHAPI-59] Command handler classes and their result/DTO types under `Editor/Commands/` and `Runtime/Commands/` are now `internal` instead of `public`. Command registration and dispatch work via reflection regardless of accessibility (see `Documentation~/creating-commands.md`), so this was never functionally required.
|
||||
- [AUTHAPI-59] `EditorPipelineManager`, `EditorPipelineManagerEditor`, `PipelineServerStartup`, `EditorPipelineServer`, `PipelineRuntimeBuildProcessor`, `HotReloadInPlaceILPostProcessor`, and `SecurityTokenManager` are now `internal` instead of `public`. `[CustomEditor]`, `[InitializeOnLoad]`, the build pipeline, and `ILPostProcessor` discovery all work via reflection regardless of accessibility, and none of these types were referenced outside the package.
|
||||
- [AUTHAPI-59] `RuntimePipelineConfigEditor`, `RuntimePipelineSettingsProvider`, `PushEnvelope`, `PushVerifier`, `PushVerifyStatus`, `RotatingFileBackup`, and the `Unity.Pipeline.Telemetry` eval-usage helper types are now `internal` instead of `public` — same rationale: `[CustomEditor]`/`[SettingsProvider]` discovery is reflection-based, and none of these are referenced outside the package.
|
||||
|
||||
### Removed
|
||||
- The separate-override-file hot reload workflow: the `reload_file_override` command, the `[HotReloadWithOverrides]` attribute, `HotReloadHelper`, and the `HotReload_WithHelpers` sample. Tag methods `[HotReload]` and edit them in place with `reload_file` instead (see `Documentation~/hot-reload.md`). The unused `[HotReloadableComponent]`, `[HotReloadComponent]` and `[HotReloadTarget]` attributes are gone too.
|
||||
|
||||
### Fixed
|
||||
- Code reload: a `var` local whose inferred type is nested in the reloaded class (`var inp = new BufferedInput { ... }`) no longer fails to compile with "The type name 'var' does not exist in the type ..."; a `using` alias for such a type is left alone too.
|
||||
- Code reload: when a reload fails to compile, each diagnostic now names the user's file and line, the compiler error code, and the generated line that failed, and the full generated source is written to `Temp/CodeReload/<assembly>.failed.cs` for inspection.
|
||||
- Code reload in the Editor no longer depends on the runtime Pipeline server being enabled. `[CodeReload]` targets were only discovered by the runtime driver, which is not created when `enableInBuilds` is off (the default), so an in-editor reload compiled but bound 0 methods with a misleading "added after the running build" reason. Entering Play Mode now registers the targets either way, and an empty registry is reported as such.
|
||||
- Interpreter backend: `new S { x = 1 }` and `default(S)` on a host struct bound boxed (the auto-bind default, e.g. a `struct` nested in the reloaded MonoBehaviour) failed at runtime with "Non-static field requires a target". The default box is now created, both for `initobj` and for the initobj-free form Roslyn emits when every field is assigned (`S s; s.x = 1; s.y = 2;`), so object initializers, field-by-field construction and nullable-struct fields work in reloaded bodies. Reading or writing a host field on a null object now reports the field name and source line instead of the bare reflection error, and an exception escaping a reloaded body is logged with its script location before it propagates.
|
||||
- `build`: a scripted build with `Development` in `options` no longer trips the "enableInBuilds without a Development Build" security dialog (the guard only looked at the Build Settings toggle, so a `build` command from the CLI stalled on a modal, or was cancelled).
|
||||
- `reload_file_player_interpreter`: a push made before the player's signing handshake has arrived now waits up to 10 s for it instead of 2 s; the first push to a freshly connected IL2CPP player was dropped at 2 s.
|
||||
- [UUM-149991] AutoStaticsCleanup: Fixed usage-analysis findings.
|
||||
- [UUM-149991] Fixed `CommandRegistry` staying on reflection-based command discovery after exiting Play Mode without a domain reload; `PipelineServerStartup` now restores TypeCache discovery on `EnteredEditMode`.
|
||||
- [UUM-149977] Removed the "Editor is not in automated mode" console warning. Now surfaced as `info` on the instance descriptor instead.
|
||||
- [AUTHAPI-36] Fixed `TestResultCollector.RunFinished` throwing `InvalidOperationException` (double-complete) when a run had already been completed by `SetError`/`Cancel` (e.g. a timed-out or errored run): a late `RunFinished` is now a no-op and the original error/cancellation is preserved.
|
||||
- [UUM-149580] The Pipeline server no longer permanently enables Player Settings "Run In Background" on start; the runtime driver now saves and restores the project's original value around start/stop.
|
||||
- [UUM-149580] Transient `RuntimePipelineConfig`/`RuntimePipelineBuildInfo` assets left behind by an interrupted build are now purged at the start of every build, so a disabled build can no longer ship the runtime Pipeline server.
|
||||
- [UUM-149580] Reading Runtime settings (Project Settings page, `get_runtime_pipeline_settings`, or a refused/dry-run `set_runtime_pipeline_settings`) no longer writes the settings file.
|
||||
- [UUM-149580] `set_runtime_pipeline_settings` now rejects a `port`, `requestTimeoutMs`, or `maxWorkItemsPerFrame` outside the same bounds the Project Settings page enforces, instead of silently persisting an invalid value (a `maxWorkItemsPerFrame` of 0 permanently starved the dispatcher's work queue).
|
||||
|
||||
### Added
|
||||
|
||||
- [AUTHAPI-68] `POST /api/exec` accepts an unparsed command line as `commandLine` (a string the server tokenizes) or `argv` (pre-split tokens), binding positional and `--flag` arguments against the command's declared parameters server-side. Exactly one of `command`, `commandLine` or `argv` may be present, and `parameters` cannot accompany the raw forms. Argument failures return `400` with `errorCode: "INVALID_COMMAND_ARGS"`, a machine-readable `argProblems` array, and the command's `commandSchema`; successful raw-form replies echo the bound `parameters`. Servers advertise `exec.argv` / `exec.commandLine` in `capabilities` on `/api/status` and in the port descriptor. `command` is no longer a JSON-required property, so a body carrying none of the three forms is now rejected by request validation as `400 {"error":"Invalid Request","errorDetails":"Command name is required"}` instead of by deserialization as `400 {"error":"Invalid JSON"}`.
|
||||
- [AUTHAPI-64] Added a new endpoint reporting currently-open modal dialogs, and command execution now reports any dialogs shown while it ran. Main-thread commands are rejected with a busy status while a dialog is open, and status polling reports that a dialog is blocking it. Requires a recent enough trunk build.
|
||||
- [AUTHAPI-29] Instrument `eval`/`eval_file` usage: each call logs a JSONL record (timing, success, statement/expression shape, and an API fingerprint of top-level member accesses) to `<project>/Library/Pipeline/eval-usage.jsonl`, editor-only and local-first — raw source isn't stored unless `Store Eval Source` is enabled. Toggle via `Eval Telemetry Enabled`.
|
||||
- [AUTHAPI-29] New `report_evals` command: ranks logged eval fingerprints by frequency and cross-references the command catalog to surface patterns not yet covered by an existing command.
|
||||
- [AUTHAPI-49] Editor analytics: `Pipeline_SessionStarted` on the first command a client executes, `Pipeline_CommandExecuted` per command executed, and `Pipeline_SessionStopped` when the Editor quits. `isEvalWithExistingCommandAvailable` reuses the AUTHAPI-29 eval fingerprints and coverage map, so it agrees with `report_evals` by construction. Only commands shipped in the package report their name and tags; a project-declared command reports `<customUserCommand>` and no tags. See `Documentation~/analytics.md`.
|
||||
- [UUM-149580] `runtime_status` now reports `pipelineDriver` (isServerRunning, actualPort, and the enableInBuilds/requestTimeoutMs/enableAuditLogging/autoStart/maxWorkItemsPerFrame values actually governing the running driver, not just what Project Settings currently shows) — `null` if no driver was ever bootstrapped.
|
||||
- [UUM-149580] New `get_runtime_pipeline_settings`/`set_runtime_pipeline_settings` commands: structured, `confirm`/`dry_run`-gated read/write access to Pipeline Runtime settings (Project Settings > Pipeline > Runtime), matching the existing Audio/Graphics/Input/Physics/Player/Quality/Time settings command family. Refused while in Play Mode.
|
||||
- [AUTHAPI-27] New `batch` command: run up to 200 ordered operations in one request, with later ops referencing earlier results. Transactional by default (single Undo step, rolled back on failure); supports `on_error`, `dry_run`, `time_budget_ms`, and detached `job` execution. See `Documentation~/commands/batch.md` for details.
|
||||
- [AUTHAPI-26] New `run_script` command: compile a single C# file in memory (no domain reload) and execute a named static entry point. Supports `ephemeral` (compile→run→discard) and `hotpatch` modes, JSON-coerced args, custom references/defines, and `dry_run` compilation. Returns `{ result, diagnostics, compileMs, executeMs, assemblyName }`; runtime errors include source `file:line`.
|
||||
- [AUTHAPI-21] Leaner `/api/exec` responses by default: compact JSON, no null envelope keys, no always-on metadata — a minimal success is just `{"success":true,"result":...}`. Use `"verbose": true` to restore the full envelope, or `"omitNulls": true` to also drop nulls inside `result`. Added `format="value"` to `get_serialized_fields` to return raw values instead of a full descriptor.
|
||||
- [AUTHAPI-21] New `warnings` array on responses for non-fatal corrective guidance (e.g. an ignored/invalid query parameter).
|
||||
- [AUTHAPI-21] `/api/job`, `/api/job/cancel`, and `/api/progress` now include null keys explicitly; pass `omit_nulls=true` to drop them.
|
||||
- [AUTHAPI-21] `POST /api/exec` now rejects a literal JSON `null` body with a structured 400 instead of an internal error.
|
||||
- Add an IL interpreter (IlInterpreter) that works in IL2CPP players and the editor. Opt in with the `reload_file_editor_interpreter` command or by pushing to a connected device with `reload_file_player_interpreter`; the hot-reload watch always uses it. A shipped Roslyn analyzer (MS002–MS023) marks the supported C# subset in `[HotReload]` method bodies.
|
||||
- Add an `[OnHotReload]` attribute to react to code being hot-reloaded.
|
||||
- Add a `.cs` change watcher, enabled from the Pipeline settings, that hot-reloads changed code without a domain reload — in editor play mode, or in a player connected via the profiler connection.
|
||||
|
||||
## [0.5.0-exp.1] - 2026-08-10
|
||||
|
||||
### Added
|
||||
- [CLI-488] New `GET /api/progress` endpoint reporting the currently executing command's progress, served off the main thread so it responds even during a blocking command. Command authors report via `CliProgress.Report(...)` or `CliEditorProgress.DisplayProgressBar`; `UnityEditor.Progress` items are mirrored automatically. Powers live progress bars in the `unity` CLI. (with CLI-335)
|
||||
- [CLI-335] Detached command jobs: `POST /api/exec` with `"job": true` runs in the background and returns a job id immediately; poll `GET /api/job?id=…` for state/progress/result. `POST /api/job/cancel` cancels a queued job or requests cooperative cancellation of a running one via the new `PipelineCancellation` API. Results are retained for 1 hour / last 100 jobs.
|
||||
- [CLI-488] New `audit`/`audit_status` commands: run a Project Auditor static-analysis scan and poll it, producing an issue CSV. Works via reflection so the package still runs without Project Auditor installed; reports `unavailable` with install guidance when analysis rules are missing.
|
||||
- [AUTHAPI-35] Report `settling` on `/api/status` and reject main-thread commands with `503 Server Busy` while the Editor is still importing/compiling after a cold start. Background commands (`recompile_status`, `package_status`, `console`, `editor_status`) stay servable throughout.
|
||||
|
||||
### Changed
|
||||
- [CLI-335] Raised the `eval` timeout cap from 30 seconds to 24 hours.
|
||||
- [CLI-335] The HTTP server now processes requests concurrently — `/api/status` and `/api/progress` answer immediately even during a long-running command. Command execution itself remains strictly serialized.
|
||||
- [CLI-335] Hardening: a command's own timeout (not a fixed 60s) governs `/api/exec`'s wait; `editor_status`/`test_status` queue behind the exec gate; progress/job state is scoped per server instance; detached jobs are capped at 100 concurrent (further submissions get `429`).
|
||||
|
||||
### Fixed
|
||||
- Fixed `executedAt` returning a zero-value instead of the actual execution time for `eval`/`eval_file`, `hot-reload`, and `run_tests`/`test_status`.
|
||||
- [UUM-148802] Fixed `get_console_logs`/`clear_console`/`console` losing console capture after exiting Play Mode by re-subscribing to Unity's log callback on every play-mode transition.
|
||||
- [UUM-148605] `set_autotick`'s enabled/interval state now persists across domain reloads via SessionState. Added a `persist` flag (default `true`) to opt out.
|
||||
- [UUM-149016] Fixed `InvalidOperationException`s after repeated `run_tests` runs caused by a stale `TestResultCollector` still registered with Unity's TestRunnerApi.
|
||||
|
||||
## [0.4.0-exp.1] - 2026-07-23
|
||||
- Persist the pipeline server auth token across editor domain reloads so long-lived clients (MCP/IDE) no longer get `401` after a recompile. (CLI-412)
|
||||
- `capture_game_view` gains a `source` parameter: `camera` (default, unchanged) or `screen`. `source=screen` captures the composited game view backbuffer so Screen Space - Overlay UI (HUDs, menus) is included — the camera path never sees overlay canvases. Screen capture requires Play Mode; in Edit Mode it returns a clear error. (AUTHAPI-10)
|
||||
- `capture_game_view`/`capture_scene_view` with `save_path` now return a path-only result (no base64) so agent tool results stay small; pass `include_inline_image=true` for the old behavior. Add `max_resolution` to cap the inline image size. (AUTHAPI-8)
|
||||
- Object-reference string handles now accept authoring-root-relative asset paths (e.g. `Materials/Floor.mat`, not just `Assets/Materials/Floor.mat`): a relative string with a file extension is treated as an asset path and normalized under the authoring root, and a failed lookup now reports every strategy tried instead of a misleading hierarchy-path-only error. (AUTHAPI-9)
|
||||
|
||||
## [0.3.1-exp.1] - 2026-07-16
|
||||
- Update docs
|
||||
|
||||
## [0.3.0-exp.1] - 2026-07-13
|
||||
|
||||
- Improve security by ensuring token usage and enforcing read control on the token.
|
||||
- Fix all upm-pvp warnings.
|
||||
- Fix Samples installation.
|
||||
- All server works if the App is minimized (in the RunInBackground).
|
||||
- Rework all docs.
|
||||
- Improve connectivity regaridng IPv4 vs IPv6 support.
|
||||
- Add `eval_file` command: evaluate C# code read from a `.cs` file on disk, as a file-based alternative to `eval` (which takes inline `code`). Both commands run the source through the same evaluation path.
|
||||
- Add a large set of Editor automation commands for agentic content-pipeline control:
|
||||
- **Assets & files:** `create_asset`, `import_asset`, `move_asset`, `copy_asset`, `rename_asset`, `delete_asset`, `find_assets`, `create_folder`, `get_import_settings`, `set_import_settings`, `read_text_file`, `write_text_file`.
|
||||
- **Scenes:** `create_scene`, `open_scene`, `save_scene`, `save_all`, `list_open_scenes`, `set_active_scene`, `get_scene_hierarchy`, `add_scene_to_build`, `remove_scene_from_build`.
|
||||
- **GameObjects & components:** `create_gameobject`, `create_gameobjects`, `delete_gameobject`, `find_gameobjects`, `rename_gameobject`, `set_parent`, `set_transform`, `set_active`, `set_tag`, `set_layer`, `add_component`, `remove_component`, `get_component_properties`, `set_component_properties`.
|
||||
- **Prefabs:** `create_prefab`, `create_prefab_variant`, `instantiate_prefab`, `apply_prefab_overrides`, `revert_prefab_overrides`, `unpack_prefab`, `save_prefab_contents`.
|
||||
- **Scripts & serialized fields:** `create_script`, `attach_script`, `get_serialized_fields`, `set_serialized_field`.
|
||||
- **Selection & search:** `get_selection`, `set_selection`, `search`.
|
||||
- **Capture & screenshots:** `screenshot`, `capture_game_view`, `capture_scene_view`, `capture_editor_element`, `capture_runtime_element`.
|
||||
- **Build:** `build`, `build_status`.
|
||||
- **Console & diagnostics:** `console`, `clear_console`, `get_console_logs`, `get_performance_stats`.
|
||||
- **Editor menus & authoring root:** `menu`, `get_authoring_root`, `set_authoring_root`.
|
||||
- **Materials & shaders:** `get_material_properties`, `set_material_properties`, `get_shader_properties`, `list_shaders`.
|
||||
- **Animation & Animator:** `create_animation_clip`, `get_animation_clip`, `set_animation_curve`, `remove_animation_curve`, `create_animator_controller`, `get_animator_controller`, `add_animator_layer`, `add_animator_parameter`, `add_animator_state`, `add_animator_transition`.
|
||||
- **Timeline:** `create_timeline`, `get_timeline`, `add_timeline_track`, `add_timeline_clip`.
|
||||
- **Lighting:** `bake_lighting`, `cancel_lighting_bake`, `lighting_bake_status`, `clear_baked_lighting`, `get_lighting_settings`, `set_lighting_settings`.
|
||||
- **NavMesh:** `bake_navmesh`, `bake_navmesh_surfaces`, `cancel_navmesh_bake`, `navmesh_bake_status`, `clear_navmesh`, `get_navmesh_settings`, `set_navmesh_settings`.
|
||||
- **Occlusion culling:** `bake_occlusion_culling`, `cancel_occlusion_bake`, `occlusion_bake_status`, `clear_occlusion_culling`.
|
||||
- Update Wrench
|
||||
- Warn user if Unity Editor is started in non-automated mode.
|
||||
|
||||
## [0.2.0-exp.2] - 2026-06-24
|
||||
|
||||
- Fix security audit flaws
|
||||
- First official published version
|
||||
|
||||
## [0.1.0-exp.1] - 2026-06-09
|
||||
|
||||
### This is the first release of _Unity Pipeline_.
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 75cd0dccc034c6543afb4f969beca6af
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,64 @@
|
||||
# com.unity.pipeline
|
||||
|
||||
Unity package for **remote-controlling a running Unity Editor (or dev Player) over HTTP**.
|
||||
A client — CLI, CI, or an agent — connects and executes registered commands: recompile,
|
||||
run tests, eval C#, code-reload files, play-mode control, status/heartbeat.
|
||||
|
||||
## Layout (standard UPM package)
|
||||
|
||||
| Folder | Assembly | Notes |
|
||||
|--------|----------|-------|
|
||||
| `Runtime/` | `Unity.Pipeline` | Ships in **development** player builds only (`defineConstraints: UNITY_EDITOR \|\| DEVELOPMENT_BUILD \|\| ENABLE_RUNTIME_PIPELINE`). HTTP server base, command registry, models, runtime commands, code reload, player server. |
|
||||
| `Runtime/Attributes/` | `Unity.Pipeline.Attributes` | The `[CodeReload]` / `[OnCodeReload]` attributes, **unconstrained** so user code tagged with them still compiles for release builds (where they are inert — nothing weaves them). |
|
||||
| `Runtime/IlInterpreter/` | `Unity.Pipeline.IlInterpreter` | IL interpreter (IlInterpreter VM) that executes eval snippets and reloaded method bodies where `Reflection.Emit` is unavailable (IL2CPP). Referenced by both `Unity.Pipeline` and `Unity.Pipeline.Editor`. |
|
||||
| `Runtime/Analyzers/` | — | Shipped `IlInterpreterAnalyzer.dll` — Roslyn analyzer (MS002–MS023) surfacing the interpreter's C# subset on `[CodeReload]` method bodies. Built from `IlInterpreter/analyzer/` at the repo root (see its `README.md`); auto-synced on Release builds — never edit the DLL by hand. |
|
||||
| `Editor/` | `Unity.Pipeline.Editor` | Editor-only. Live server + its owner, settings asset, editor commands, test runner, code-reload watcher, build processors (`Editor/BuildProcessors/`, incl. the code-reload `link.xml` generator). |
|
||||
| `CodeGen/` | `Unity.Pipeline.CodeGen` | ILPostProcessor (Mono.Cecil) for in-place code reload. Root-level by Unity convention (cf. Burst/Entities) — leave as-is. |
|
||||
| `Tests/Editor/` | `Unity.Pipeline.Tests.Editor` | **EditMode** suite (the main one). |
|
||||
| `Tests/Runtime/` | `Unity.Pipeline.Tests.Runtime` | **PlayMode** suite. |
|
||||
| `Samples~/` | — | Code-reload sample scenes. |
|
||||
|
||||
## Architecture entry points
|
||||
|
||||
- **`Runtime/Common/BasePipelineServer.cs`** — shared HTTP listener + routing (`/api/exec`,
|
||||
`/api/status`, `/api/commands`, …). Subclassed by `Editor/EditorPipelineServer.cs` and
|
||||
`Runtime/PlayerSupport/RuntimePipelineServer.cs`.
|
||||
- **`Editor/EditorPipelineStartup.cs`** (`PipelineServerStartup`) — `[InitializeOnLoad]` static
|
||||
owner of the live editor server; survives domain reloads; auto-enables autotick.
|
||||
- **`Editor/EditorPipelineManager.cs`** — inspectable settings asset (`Window/Pipeline/Settings...` menu).
|
||||
- Commands are discovered via `[CliCommand]` attributes. Editor command groups live under
|
||||
`Editor/Commands/` (Assets, Scenes, GameObjects, Prefabs, Scripts, Build, PackageManager,
|
||||
ProjectSettings, …); runtime commands under `Runtime/`.
|
||||
- **Interpreter path (IL2CPP-safe eval & code reload)** — `Runtime/IlInterpreter/ScriptInterpreter.cs`
|
||||
is the VM core; `Runtime/Compilation/IlInterpreterHostBindings.cs` (`CreateStandard()`) builds the
|
||||
host surface it may call — consumed by eval, code reload, and the `link.xml` generator
|
||||
(`Editor/BuildProcessors/CodeReloadLinkXmlGenerator.cs`); see the repo-root `CONTEXT.md` glossary
|
||||
for the drift invariant. `Runtime/Compilation/InterpreterCodeReloadExecutor.cs` routes reloaded
|
||||
method bodies into the VM; `Editor/EditorCodeReloadWatcher.cs` owns the file-watch → push-reload
|
||||
loop.
|
||||
- **`Editor/Authoring/`** — helpers shared by state-changing commands: `AuthoringUndoScope`
|
||||
(collapses a command's `Undo`-registered mutations into one step) and `ProjectPaths`/`ObjectResolver`
|
||||
(authoring-root path resolution + object handles).
|
||||
Destructive/overwriting commands gate on `confirm`/`dry_run` inline (see `delete_asset`). Structured
|
||||
multi-field command args implement `IStructuredCommandInput` (`Runtime/Common/`). See
|
||||
`Documentation~/safety-and-mutations.md`, `Documentation~/authoring-commands.md`, and
|
||||
`Documentation~/creating-commands.md`.
|
||||
|
||||
## Driving & verifying (agents)
|
||||
|
||||
Use the **`unity-pipeline` skill**, which drives the live editor via the `unity` CLI.
|
||||
Canonical verb is `command`; the auth token is the `evalToken` field inside the port file
|
||||
`<liveProject>/Library/Pipeline/.unity-pipeline-port`, sent as `Authorization: Bearer <token>`.
|
||||
|
||||
Edit→verify loop: make a logical change (may span several files) → `command recompile` →
|
||||
poll `command recompile_status` → `command run_tests --filter <TestClass>`. The server keeps the
|
||||
editor ticking while unfocused, so compiles proceed even when focus is elsewhere.
|
||||
|
||||
## Conventions
|
||||
|
||||
- Private instance fields: `m_PascalCase`. Private static fields: `s_PascalCase`. Consts: `PascalCase`.
|
||||
Exception: `Runtime/IlInterpreter/` keeps its `_camelCase` style.
|
||||
- Don't `git commit`/`push` without an explicit request.
|
||||
- Changelog entries: `[JIRA-KEY] <one short clause>` in Unreleased. No justification/why — state what changed, stop.
|
||||
Plain English, minimal code snippets/API names — describe the change, don't quote the implementation.
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6433e36ff8e1bc44fb3cde740ee86080
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,2 @@
|
||||
# see https://github.com/Unity-Technologies/com.unity.pipeline for more information
|
||||
* @Unity-Technologies/core-authoring
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cbc57dbad3c6f114d94ced75a1f9737d
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b1d193bfb6e52d44975ae298a3c060a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,460 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using Mono.Cecil.Rocks;
|
||||
using Unity.CompilationPipeline.Common.Diagnostics;
|
||||
using Unity.CompilationPipeline.Common.ILPostProcessing;
|
||||
|
||||
namespace Unity.Pipeline.CodeGen
|
||||
{
|
||||
/// <summary>
|
||||
/// Weaves a code-reload dispatch prologue into every method tagged [CodeReload] at compile
|
||||
/// time, so a running method can route to a reloaded override with no domain reload. The
|
||||
/// injected prologue is:
|
||||
///
|
||||
/// if (CodeReloadRegistry.TryInvokeCodeReload("Type.Method", this, args)) return;
|
||||
/// // ... original body ...
|
||||
///
|
||||
/// Weaves instance AND static methods (a static method dispatches with a null instance),
|
||||
/// returning void, a value (via TryInvokeCodeReloadResult), or a System.Collections.IEnumerator
|
||||
/// coroutine. Parameters are supported (boxed into object[]). Only IEnumerable/generic
|
||||
/// iterator returns are skipped with a diagnostic.
|
||||
/// </summary>
|
||||
class CodeReloadInPlaceILPostProcessor : ILPostProcessor
|
||||
{
|
||||
private const string RuntimeAssemblyName = "Unity.Pipeline";
|
||||
// Full name: a user attribute merely named CodeReloadAttribute must not get a dispatch
|
||||
// prologue — runtime discovery checks the real type, so its overrides would never bind.
|
||||
private const string AttributeFullName = "Unity.Pipeline.CodeReload.CodeReloadAttribute";
|
||||
private const string RegistryTypeFullName = "Unity.Pipeline.CodeReload.CodeReloadRegistry";
|
||||
private const string DispatchMethodName = "TryInvokeCodeReload";
|
||||
private const string ResultDispatchMethodName = "TryInvokeCodeReloadResult";
|
||||
private const string HasOverrideMethodName = "HasOverride";
|
||||
private const string ThrowNullResultMethodName = "ThrowOverrideReturnedNull";
|
||||
|
||||
/// <summary>Unity requires a fresh instance per compilation; this processor is stateless, so returns itself.</summary>
|
||||
/// <returns>This instance.</returns>
|
||||
public override ILPostProcessor GetInstance() => this;
|
||||
|
||||
/// <summary>Whether this assembly should be woven: only those referencing the runtime assembly can use [CodeReload].</summary>
|
||||
/// <param name="compiledAssembly">The assembly about to be processed.</param>
|
||||
/// <returns>True if the assembly references the Unity.Pipeline runtime assembly.</returns>
|
||||
public override bool WillProcess(ICompiledAssembly compiledAssembly)
|
||||
{
|
||||
// Only assemblies that reference the runtime assembly can use [CodeReload] or call
|
||||
// the registry. This naturally excludes the runtime assembly itself (it does not
|
||||
// reference itself) and the CodeGen assembly (no runtime reference).
|
||||
return compiledAssembly.References.Any(
|
||||
r => Path.GetFileNameWithoutExtension(r) == RuntimeAssemblyName);
|
||||
}
|
||||
|
||||
/// <summary>Weave the code-reload dispatch prologue into every [CodeReload] method in the assembly.</summary>
|
||||
/// <param name="compiledAssembly">The assembly to process.</param>
|
||||
/// <returns>The woven assembly bytes plus any diagnostics.</returns>
|
||||
public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
|
||||
{
|
||||
var diagnostics = new List<DiagnosticMessage>();
|
||||
|
||||
using (var resolver = new PostProcessorAssemblyResolver(compiledAssembly.References))
|
||||
using (var assembly = ReadAssembly(compiledAssembly, resolver))
|
||||
{
|
||||
var module = assembly.MainModule;
|
||||
|
||||
var targets = module.GetTypes()
|
||||
.SelectMany(t => t.Methods)
|
||||
.Where(m => m.HasBody && m.CustomAttributes.Any(a => a.AttributeType.FullName == AttributeFullName))
|
||||
.ToList();
|
||||
|
||||
// A user attribute merely NAMED CodeReloadAttribute never weaves (full-name match
|
||||
// above) — say so, or the author of a [CodeReload] imported from the wrong
|
||||
// namespace debugs a silent no-op. Runs before the targets early-out: a module
|
||||
// whose only tagged methods carry the impostor still gets the warning.
|
||||
WarnForeignCodeReloadAttributes(module, diagnostics);
|
||||
|
||||
if (targets.Count == 0)
|
||||
return new ILPostProcessResult(null, diagnostics);
|
||||
|
||||
// Resolve failures below leave every [CodeReload] method unwoven — the per-cause
|
||||
// warning alone reads like a detail, so state the consequence explicitly.
|
||||
var dispatch = ResolveDispatchMethod(module, resolver, diagnostics);
|
||||
var hasOverride = dispatch == null ? null : ResolveHasOverrideMethod(module, resolver, diagnostics);
|
||||
if (dispatch == null || hasOverride == null)
|
||||
{
|
||||
diagnostics.Add(Warn($"{targets.Count} [CodeReload] method(s) in {module.Name} were NOT woven — " +
|
||||
"code reload will not work for this assembly (see the preceding warning for the cause)."));
|
||||
return new ILPostProcessResult(null, diagnostics);
|
||||
}
|
||||
|
||||
// Value-returning [CodeReload] methods dispatch through the out-result overload; may be
|
||||
// absent in an older runtime, in which case those methods are skipped (below).
|
||||
var resultDispatch = ResolveResultDispatchMethod(module, resolver, diagnostics);
|
||||
|
||||
// Guard helper for value-type returns (see WeaveDispatchPrologue); absent in an
|
||||
// older runtime, in which case the unguarded (pre-existing) IL is woven.
|
||||
var throwNullResult = ResolveThrowNullResultMethod(module, resolver, diagnostics);
|
||||
|
||||
int wovenCount = 0;
|
||||
foreach (var method in targets)
|
||||
{
|
||||
bool isVoid = method.ReturnType.MetadataType == MetadataType.Void;
|
||||
|
||||
// Coroutines returning System.Collections.IEnumerator weave like any other
|
||||
// value-returning method: the woven stub dispatches at iterator CREATION
|
||||
// (per StartCoroutine call), and the override's state machine — compiled
|
||||
// (Mono) or interpreted via the ScriptEnumerator bridge (IL2CPP) — is what
|
||||
// the scheduler drives. IEnumerable/generic iterator returns stay excluded:
|
||||
// nothing in the coroutine workflow produces them and the interpreter bridge
|
||||
// only implements non-generic IEnumerator.
|
||||
if (!isVoid && IsExcludedIteratorReturn(method.ReturnType))
|
||||
{
|
||||
diagnostics.Add(Warn($"[CodeReload] '{method.FullName}' returns an IEnumerable or generic iterator and was skipped (coroutines must return System.Collections.IEnumerator)."));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isVoid && resultDispatch == null)
|
||||
{
|
||||
diagnostics.Add(Warn($"[CodeReload] '{method.FullName}' returns a value but the runtime has no {ResultDispatchMethodName}; skipped."));
|
||||
continue;
|
||||
}
|
||||
|
||||
WeaveDispatchPrologue(method, dispatch, resultDispatch, hasOverride, throwNullResult);
|
||||
wovenCount++;
|
||||
}
|
||||
|
||||
if (wovenCount == 0)
|
||||
return new ILPostProcessResult(null, diagnostics);
|
||||
|
||||
return new ILPostProcessResult(WriteAssembly(assembly), diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Warn for every method tagged with an attribute that shares the simple name
|
||||
/// CodeReloadAttribute but is not the Unity.Pipeline one: it gets no dispatch prologue by
|
||||
/// design (runtime discovery checks the real type), and without the warning that reads as
|
||||
/// code reload silently not working.
|
||||
/// </summary>
|
||||
private static void WarnForeignCodeReloadAttributes(ModuleDefinition module, List<DiagnosticMessage> diagnostics)
|
||||
{
|
||||
foreach (var method in module.GetTypes().SelectMany(t => t.Methods))
|
||||
{
|
||||
if (!method.HasCustomAttributes)
|
||||
continue;
|
||||
var impostor = method.CustomAttributes.FirstOrDefault(a =>
|
||||
a.AttributeType.Name == "CodeReloadAttribute" &&
|
||||
a.AttributeType.FullName != AttributeFullName);
|
||||
if (impostor != null)
|
||||
diagnostics.Add(Warn($"'{method.FullName}' is tagged [{impostor.AttributeType.FullName}], " +
|
||||
$"which is not {AttributeFullName} — no dispatch prologue was woven, code reload will not apply to it."));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>True for System.Nullable`1, whose unbox.any accepts null (empty Nullable).</summary>
|
||||
private static bool IsNullable(TypeReference rt)
|
||||
=> rt is GenericInstanceType git && git.ElementType.FullName == "System.Nullable`1";
|
||||
|
||||
/// <summary>True for the iterator returns that stay excluded from weaving: IEnumerable
|
||||
/// (both) and generic IEnumerator`1. Plain System.Collections.IEnumerator — the Unity
|
||||
/// coroutine shape — weaves through the result dispatch (see the weave site).</summary>
|
||||
private static bool IsExcludedIteratorReturn(TypeReference rt)
|
||||
{
|
||||
var name = rt.GetElementType().FullName;
|
||||
return name == "System.Collections.IEnumerable"
|
||||
|| name == "System.Collections.Generic.IEnumerator`1"
|
||||
|| name == "System.Collections.Generic.IEnumerable`1";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inject, at the very top of the method:
|
||||
/// if (CodeReloadRegistry.HasOverride("Type.Method")
|
||||
/// && CodeReloadRegistry.TryInvokeCodeReload("Type.Method", this, args)) return;
|
||||
///
|
||||
/// The HasOverride guard is a cheap dictionary lookup that runs first, so the (boxed) argument
|
||||
/// array is only built when an override is actually active — a woven method with no override
|
||||
/// allocates nothing per call.
|
||||
/// </summary>
|
||||
private static void WeaveDispatchPrologue(
|
||||
MethodDefinition method, MethodReference dispatch, MethodReference resultDispatch,
|
||||
MethodReference hasOverride, MethodReference throwNullResult)
|
||||
{
|
||||
var body = method.Body;
|
||||
body.SimplifyMacros();
|
||||
|
||||
var il = body.GetILProcessor();
|
||||
var first = body.Instructions[0];
|
||||
var methodId = $"{method.DeclaringType.Name}.{method.Name}";
|
||||
bool isVoid = method.ReturnType.MetadataType == MetadataType.Void;
|
||||
|
||||
// A value-returning method holds the dispatched result in a local, then unboxes it.
|
||||
VariableDefinition resultLocal = null;
|
||||
if (!isVoid)
|
||||
{
|
||||
resultLocal = new VariableDefinition(method.Module.TypeSystem.Object);
|
||||
body.Variables.Add(resultLocal);
|
||||
}
|
||||
|
||||
var prologue = new List<Instruction>
|
||||
{
|
||||
// Guard: skip straight to the original body (no allocation) when no override is active.
|
||||
Instruction.Create(OpCodes.Ldstr, methodId),
|
||||
Instruction.Create(OpCodes.Call, hasOverride),
|
||||
Instruction.Create(OpCodes.Brfalse, first),
|
||||
// An override exists: build the dispatch call and its argument array.
|
||||
Instruction.Create(OpCodes.Ldstr, methodId),
|
||||
// The dispatch's instance argument: `this`, or null for a static method (the
|
||||
// override ABI keeps a leading instance parameter either way; it arrives null).
|
||||
method.IsStatic
|
||||
? Instruction.Create(OpCodes.Ldnull)
|
||||
: Instruction.Create(OpCodes.Ldarg_0),
|
||||
};
|
||||
|
||||
// Build the object[] of parameters (null when parameterless).
|
||||
var ps = method.Parameters;
|
||||
if (ps.Count == 0)
|
||||
{
|
||||
prologue.Add(Instruction.Create(OpCodes.Ldnull));
|
||||
}
|
||||
else
|
||||
{
|
||||
prologue.Add(Instruction.Create(OpCodes.Ldc_I4, ps.Count));
|
||||
prologue.Add(Instruction.Create(OpCodes.Newarr, method.Module.TypeSystem.Object));
|
||||
for (int i = 0; i < ps.Count; i++)
|
||||
{
|
||||
prologue.Add(Instruction.Create(OpCodes.Dup));
|
||||
prologue.Add(Instruction.Create(OpCodes.Ldc_I4, i));
|
||||
prologue.Add(Instruction.Create(OpCodes.Ldarg, ps[i]));
|
||||
if (ps[i].ParameterType.IsValueType || ps[i].ParameterType.IsGenericParameter)
|
||||
prologue.Add(Instruction.Create(OpCodes.Box, ps[i].ParameterType));
|
||||
prologue.Add(Instruction.Create(OpCodes.Stelem_Ref));
|
||||
}
|
||||
}
|
||||
|
||||
if (isVoid)
|
||||
{
|
||||
// if (TryInvokeCodeReload(id, this, args)) return; else <body>
|
||||
prologue.Add(Instruction.Create(OpCodes.Call, dispatch));
|
||||
prologue.Add(Instruction.Create(OpCodes.Brfalse, first)); // no override -> run original body
|
||||
prologue.Add(Instruction.Create(OpCodes.Ret)); // override ran -> skip body
|
||||
}
|
||||
else
|
||||
{
|
||||
// if (TryInvokeCodeReloadResult(id, this, args, out object r)) return (TRet)r; else <body>
|
||||
prologue.Add(Instruction.Create(OpCodes.Ldloca, resultLocal)); // out object result
|
||||
prologue.Add(Instruction.Create(OpCodes.Call, resultDispatch));
|
||||
prologue.Add(Instruction.Create(OpCodes.Brfalse, first)); // no override -> run original body
|
||||
var loadResult = Instruction.Create(OpCodes.Ldloc, resultLocal);
|
||||
// A null result cannot unbox to a value type — without this guard the unbox.any
|
||||
// below throws an opaque NullReferenceException at the call site. Nullable<T>
|
||||
// stays unguarded: unbox.any legitimately converts null to an empty Nullable.
|
||||
if (throwNullResult != null && method.ReturnType.IsValueType && !IsNullable(method.ReturnType))
|
||||
{
|
||||
prologue.Add(Instruction.Create(OpCodes.Ldloc, resultLocal));
|
||||
prologue.Add(Instruction.Create(OpCodes.Brtrue, loadResult));
|
||||
prologue.Add(Instruction.Create(OpCodes.Ldstr, methodId));
|
||||
prologue.Add(Instruction.Create(OpCodes.Call, throwNullResult)); // never returns
|
||||
}
|
||||
prologue.Add(loadResult);
|
||||
// unbox.any handles both value types (unbox) and reference types (castclass).
|
||||
prologue.Add(Instruction.Create(OpCodes.Unbox_Any, method.ReturnType));
|
||||
prologue.Add(Instruction.Create(OpCodes.Ret)); // override ran -> return its result
|
||||
}
|
||||
|
||||
foreach (var instr in prologue)
|
||||
il.InsertBefore(first, instr);
|
||||
|
||||
body.OptimizeMacros();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolve a CodeReloadRegistry method (static, non-generic; <paramref name="match"/> picks
|
||||
/// the overload) from the runtime assembly and import it into the target module.
|
||||
/// <paramref name="missingMessage"/> is the warning added when the registry or the method
|
||||
/// can't be found; null makes absence silent — for overloads an older runtime may not have.
|
||||
/// </summary>
|
||||
private static MethodReference ResolveRegistryMethod(
|
||||
ModuleDefinition module, PostProcessorAssemblyResolver resolver, List<DiagnosticMessage> diagnostics,
|
||||
Func<MethodDefinition, bool> match, string missingMessage)
|
||||
{
|
||||
var runtimeRef = module.AssemblyReferences.FirstOrDefault(a => a.Name == RuntimeAssemblyName);
|
||||
if (runtimeRef == null)
|
||||
{
|
||||
if (missingMessage != null)
|
||||
diagnostics.Add(Warn($"Could not find a reference to {RuntimeAssemblyName} while weaving {module.Name}."));
|
||||
return null;
|
||||
}
|
||||
|
||||
var runtime = resolver.Resolve(runtimeRef);
|
||||
var registry = runtime?.MainModule.GetType(RegistryTypeFullName);
|
||||
if (registry == null)
|
||||
{
|
||||
if (missingMessage != null)
|
||||
diagnostics.Add(Warn($"Could not resolve {RegistryTypeFullName} in {RuntimeAssemblyName}."));
|
||||
return null;
|
||||
}
|
||||
|
||||
var method = registry.Methods.FirstOrDefault(m => m.IsStatic && !m.HasGenericParameters && match(m));
|
||||
if (method == null)
|
||||
{
|
||||
if (missingMessage != null)
|
||||
diagnostics.Add(Warn(missingMessage));
|
||||
return null;
|
||||
}
|
||||
|
||||
return module.ImportReference(method);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolve CodeReloadRegistry.TryInvokeCodeReload(string, object, object[]) from the runtime
|
||||
/// assembly and import it into the target module.
|
||||
/// </summary>
|
||||
private static MethodReference ResolveDispatchMethod(
|
||||
ModuleDefinition module, PostProcessorAssemblyResolver resolver, List<DiagnosticMessage> diagnostics)
|
||||
{
|
||||
return ResolveRegistryMethod(module, resolver, diagnostics,
|
||||
m => m.Name == DispatchMethodName &&
|
||||
m.Parameters.Count == 3 &&
|
||||
m.Parameters[0].ParameterType.MetadataType == MetadataType.String &&
|
||||
m.Parameters[1].ParameterType.MetadataType == MetadataType.Object,
|
||||
$"Could not find non-generic {DispatchMethodName}(string, object, object[]) on {RegistryTypeFullName}.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolve CodeReloadRegistry.TryInvokeCodeReloadResult(string, object, object[], out object)
|
||||
/// -> bool for value-returning methods. Returns null (not an error) when the runtime predates
|
||||
/// the overload — callers then skip value-returning methods with a diagnostic.
|
||||
/// </summary>
|
||||
private static MethodReference ResolveResultDispatchMethod(
|
||||
ModuleDefinition module, PostProcessorAssemblyResolver resolver, List<DiagnosticMessage> diagnostics)
|
||||
{
|
||||
return ResolveRegistryMethod(module, resolver, diagnostics,
|
||||
m => m.Name == ResultDispatchMethodName &&
|
||||
m.Parameters.Count == 4 &&
|
||||
m.Parameters[0].ParameterType.MetadataType == MetadataType.String &&
|
||||
m.Parameters[1].ParameterType.MetadataType == MetadataType.Object &&
|
||||
m.Parameters[3].ParameterType.IsByReference,
|
||||
missingMessage: null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolve CodeReloadRegistry.ThrowOverrideReturnedNull(string) — the woven null guard for
|
||||
/// value-type returns. Resolved from the runtime like the dispatch methods (importing
|
||||
/// exception types from the post-processor's own corlib would not resolve in a player).
|
||||
/// Returns null (not an error) when the runtime predates the helper — the unguarded IL is
|
||||
/// woven then, matching the old behavior.
|
||||
/// </summary>
|
||||
private static MethodReference ResolveThrowNullResultMethod(
|
||||
ModuleDefinition module, PostProcessorAssemblyResolver resolver, List<DiagnosticMessage> diagnostics)
|
||||
{
|
||||
return ResolveRegistryMethod(module, resolver, diagnostics,
|
||||
m => m.Name == ThrowNullResultMethodName &&
|
||||
m.Parameters.Count == 1 &&
|
||||
m.Parameters[0].ParameterType.MetadataType == MetadataType.String,
|
||||
missingMessage: null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolve CodeReloadRegistry.HasOverride(string) -> bool from the runtime assembly and import
|
||||
/// it into the target module (used by the woven prologue's no-allocation guard).
|
||||
/// </summary>
|
||||
private static MethodReference ResolveHasOverrideMethod(
|
||||
ModuleDefinition module, PostProcessorAssemblyResolver resolver, List<DiagnosticMessage> diagnostics)
|
||||
{
|
||||
return ResolveRegistryMethod(module, resolver, diagnostics,
|
||||
m => m.Name == HasOverrideMethodName &&
|
||||
m.Parameters.Count == 1 &&
|
||||
m.Parameters[0].ParameterType.MetadataType == MetadataType.String &&
|
||||
m.ReturnType.MetadataType == MetadataType.Boolean,
|
||||
$"Could not find {HasOverrideMethodName}(string) on {RegistryTypeFullName}.");
|
||||
}
|
||||
|
||||
private static AssemblyDefinition ReadAssembly(ICompiledAssembly compiledAssembly, IAssemblyResolver resolver)
|
||||
{
|
||||
var pdb = compiledAssembly.InMemoryAssembly.PdbData;
|
||||
var hasSymbols = pdb != null && pdb.Length > 0;
|
||||
|
||||
var readerParameters = new ReaderParameters
|
||||
{
|
||||
AssemblyResolver = resolver,
|
||||
ReadingMode = ReadingMode.Immediate,
|
||||
ReadWrite = true,
|
||||
ReadSymbols = hasSymbols,
|
||||
SymbolReaderProvider = hasSymbols ? new PortablePdbReaderProvider() : null,
|
||||
SymbolStream = hasSymbols ? new MemoryStream(pdb) : null,
|
||||
};
|
||||
|
||||
var peStream = new MemoryStream(compiledAssembly.InMemoryAssembly.PeData);
|
||||
return AssemblyDefinition.ReadAssembly(peStream, readerParameters);
|
||||
}
|
||||
|
||||
private static InMemoryAssembly WriteAssembly(AssemblyDefinition assembly)
|
||||
{
|
||||
var pe = new MemoryStream();
|
||||
var pdb = new MemoryStream();
|
||||
var writerParameters = new WriterParameters
|
||||
{
|
||||
SymbolWriterProvider = new PortablePdbWriterProvider(),
|
||||
WriteSymbols = true,
|
||||
SymbolStream = pdb,
|
||||
};
|
||||
|
||||
assembly.Write(pe, writerParameters);
|
||||
return new InMemoryAssembly(pe.ToArray(), pdb.ToArray());
|
||||
}
|
||||
|
||||
private static DiagnosticMessage Warn(string message) => new DiagnosticMessage
|
||||
{
|
||||
DiagnosticType = DiagnosticType.Warning,
|
||||
MessageData = "CodeReloadInPlace: " + message,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Minimal IAssemblyResolver that resolves referenced assemblies from the compiled assembly's
|
||||
/// reference paths (which are absolute file paths supplied by the compilation pipeline).
|
||||
/// </summary>
|
||||
internal sealed class PostProcessorAssemblyResolver : IAssemblyResolver
|
||||
{
|
||||
private readonly string[] _referencePaths;
|
||||
private readonly Dictionary<string, AssemblyDefinition> _cache = new Dictionary<string, AssemblyDefinition>();
|
||||
|
||||
public PostProcessorAssemblyResolver(string[] referencePaths)
|
||||
{
|
||||
_referencePaths = referencePaths;
|
||||
}
|
||||
|
||||
public AssemblyDefinition Resolve(AssemblyNameReference name)
|
||||
=> Resolve(name, new ReaderParameters(ReadingMode.Deferred));
|
||||
|
||||
public AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters)
|
||||
{
|
||||
lock (_cache)
|
||||
{
|
||||
if (_cache.TryGetValue(name.Name, out var cached))
|
||||
return cached;
|
||||
|
||||
var path = _referencePaths.FirstOrDefault(
|
||||
r => Path.GetFileNameWithoutExtension(r) == name.Name);
|
||||
if (path == null || !File.Exists(path))
|
||||
return null;
|
||||
|
||||
parameters.AssemblyResolver = this;
|
||||
var definition = AssemblyDefinition.ReadAssembly(path, parameters);
|
||||
_cache[name.Name] = definition;
|
||||
return definition;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (_cache)
|
||||
{
|
||||
foreach (var def in _cache.Values)
|
||||
def.Dispose();
|
||||
_cache.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3eb482c6630e46b98100dd9c13a3f7d3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "Unity.Pipeline.CodeGen",
|
||||
"rootNamespace": "Unity.Pipeline.CodeGen",
|
||||
"references": [],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": true,
|
||||
"precompiledReferences": [
|
||||
"Mono.Cecil.dll",
|
||||
"Mono.Cecil.Mdb.dll",
|
||||
"Mono.Cecil.Pdb.dll",
|
||||
"Mono.Cecil.Rocks.dll"
|
||||
],
|
||||
"autoReferenced": false,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": true
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3d86918fcc1e40b8a17542a4bacf8dbc
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,28 @@
|
||||
* [Home](index.md)
|
||||
* Guides
|
||||
* [Creating commands](creating-commands.md)
|
||||
* [Creating authoring commands](authoring-commands.md)
|
||||
* [Safety & mutations](safety-and-mutations.md)
|
||||
* [Connectivity](connectivity.md)
|
||||
* [Runtime connection & setup](runtime-setup.md)
|
||||
* [Reloading code](code-reload.md)
|
||||
* [Tests architecture](testing.md)
|
||||
* [Analytics](analytics.md)
|
||||
* Command reference
|
||||
* [Batch command](commands/batch.md)
|
||||
* [Asset & file commands](commands/assets-and-files.md)
|
||||
* [Scene commands](commands/scenes.md)
|
||||
* [GameObject & component commands](commands/gameobjects-and-components.md)
|
||||
* [Prefab commands](commands/prefabs.md)
|
||||
* [Script commands](commands/scripts.md)
|
||||
* [Animation commands](commands/animation.md)
|
||||
* [Material & shader commands](commands/materials.md)
|
||||
* [Baking commands](commands/baking.md)
|
||||
* [Navigation & selection commands](commands/navigation.md)
|
||||
* [Capture commands](commands/capture.md)
|
||||
* [Wait commands](commands/wait.md)
|
||||
* [Build, compilation & test commands](commands/build-and-compilation.md)
|
||||
* [Project settings commands](commands/project-settings.md)
|
||||
* [Package Manager commands](commands/package-manager.md)
|
||||
* [Editor lifecycle & observability commands](commands/editor-lifecycle-and-observability.md)
|
||||
* [Runtime commands](commands/runtime.md)
|
||||
@@ -0,0 +1,138 @@
|
||||
# Analytics
|
||||
|
||||
The Editor server reports how the pipeline is used through Unity's editor analytics
|
||||
(`EditorAnalytics.SendAnalytic`). The data answers questions the package has no other way to
|
||||
answer: whether sessions are interactive or automated, which commands clients actually reach for,
|
||||
how often they fail, how long they take, and how much traffic is raw `eval` rather than a
|
||||
concrete command.
|
||||
|
||||
Three things bound what is collected:
|
||||
|
||||
- **Editor only.** A server running in a development Player reports nothing — `EditorAnalytics`
|
||||
does not exist there.
|
||||
- **The editor's own opt-out applies.** With analytics disabled in Unity preferences,
|
||||
`SendAnalytic` does nothing and no event leaves the machine. There is no separate pipeline
|
||||
switch to turn off.
|
||||
- **Nothing about a command's content is collected.** Not its parameters, not the code passed to
|
||||
`eval`, not paths, not results.
|
||||
- **Nothing a project declared about its own commands is collected.** Only commands shipped in the
|
||||
package report their name and tags; anything a project declared itself reports
|
||||
`<customUserCommand>` and no tags — see
|
||||
[What is and is not collected](#what-is-and-is-not-collected).
|
||||
|
||||
## Events
|
||||
|
||||
### `Pipeline_SessionStarted`
|
||||
|
||||
Sent once when a client executes its first command, which is the moment the pipeline actually
|
||||
started handling work — not when the Editor launched or the server bound its port.
|
||||
|
||||
| Field | Meaning |
|
||||
|-------|---------|
|
||||
| `batchmode` | The Editor was launched with `-batchmode`. |
|
||||
| `automated` | The Editor was launched with `-automated`. |
|
||||
| `browserAllowed` | The server accepts sandboxed browser clients (the limited-CORS path). Off unless enabled in the Pipeline settings. |
|
||||
| `dynamicPortAssignation` | The port was auto-assigned from the server's range rather than pinned in the settings asset. |
|
||||
| `hotReloadWatcherEnabled` | The `.cs` change watcher is running. |
|
||||
|
||||
A session spans a Unity **process**. The latch lives in `SessionState`, which survives domain
|
||||
reloads, so a script recompile does not open a second session.
|
||||
|
||||
### `Pipeline_CommandExecuted`
|
||||
|
||||
Sent once per command a client executes.
|
||||
|
||||
| Field | Meaning |
|
||||
|-------|---------|
|
||||
| `commandName` | The registered command name — but only for commands that ship with the package. A project-declared command reports the literal `<customUserCommand>` instead. |
|
||||
| `commandTags` | The command's tags, in declaration order — but only for commands that ship with the package. Empty for an untagged command, and always empty for a project-declared one. |
|
||||
| `commandSuccess` | Whether the command succeeded. False both when it threw and when it returned a result carrying its own failure — `eval`, `reload_file`, `run_script` and `run_tests` all report failure the second way. |
|
||||
| `commandDuration` | Milliseconds spent inside the command, excluding the wait for the one-command-at-a-time gate. |
|
||||
| `isUserDefinedCommand` | The command was declared by the project rather than shipped in the package. |
|
||||
| `isEval` | The command is a code evaluation (tagged `scripts/eval`: `eval`, `eval_file`, `run_script`). |
|
||||
| `isEvalWithExistingCommandAvailable` | Whether the evaluated snippet touches an API that a shipped command already covers — the "you did not need eval for this" signal. Same judgement `report_evals` makes, applied to one invocation: see [How the eval-coverage flag is decided](#how-the-eval-coverage-flag-is-decided). |
|
||||
|
||||
What counts as one execution:
|
||||
|
||||
- One event per `/api/exec` call, including a `job: true` submission — reported when the job
|
||||
actually **finishes**, not when its job handle is returned.
|
||||
- A `batch` reports one event, for `batch` itself. Its sub-operations are not reported
|
||||
individually.
|
||||
- A request rejected before anything ran (oversized body, malformed JSON, unknown command,
|
||||
settling host, full job queue) reports nothing: no command executed.
|
||||
- The internal executions behind `/api/editor_status` and `/api/test-status` report nothing.
|
||||
Clients poll those endpoints, and the polling would swamp everything else.
|
||||
|
||||
### `Pipeline_SessionStopped`
|
||||
|
||||
Sent when the Editor quits, and only if `Pipeline_SessionStarted` was sent — an Editor that never
|
||||
served a command closes no session.
|
||||
|
||||
| Field | Meaning |
|
||||
|-------|---------|
|
||||
| `sessionDuration` | Milliseconds between the session starting and the Editor quitting. |
|
||||
|
||||
## How the eval-coverage flag is decided
|
||||
|
||||
`isEvalWithExistingCommandAvailable` reuses the eval instrumentation from AUTHAPI-29 rather than
|
||||
inventing a second opinion. Per invocation:
|
||||
|
||||
1. `EvalUsageFingerprinter.Analyze` turns the snippet into API fingerprints — dotted member-access
|
||||
paths, with no literals or raw source in them.
|
||||
2. Each fingerprint is looked up in `EvalCoverageMap.Default`, a small curated hint map from API to
|
||||
the command that would cover it.
|
||||
3. The flag is true only if a mapped command is **actually in the live catalog**. A mapping to a
|
||||
command that does not exist yet (`AssetDatabase.Refresh` → `refresh_assets`) is a gap for
|
||||
`report_evals` to surface, not an avoidable eval.
|
||||
|
||||
This is deliberately the same rule as `report_evals`' `covered` list, so the two can never disagree.
|
||||
Two consequences worth knowing when reading the data:
|
||||
|
||||
- **It is a floor, not a measure.** The map has a handful of entries and matching is on the whole
|
||||
fingerprint, so `PlayerSettings.GetScriptingBackend(...)` matches while
|
||||
`UnityEditor.PlayerSettings.GetScriptingBackend(...)` and
|
||||
`PlayerSettings.GetScriptingBackend(...).ToString()` do not — they fingerprint as different paths.
|
||||
A `false` means "no mapped-and-present command matched", never "no command could cover this".
|
||||
- **`run_script` always reports `false`.** It runs a project file through a named entry point rather
|
||||
than a snippet, and the fingerprinter parses script-kind bodies, so it is not fingerprinted.
|
||||
`eval` (inline body) and `eval_file` (body read back from its file) are judged.
|
||||
|
||||
## What is and is not collected
|
||||
|
||||
A command's name and tags are text the project's own authors wrote, so either can name an
|
||||
unannounced feature or an internal system. Tags are constrained in shape
|
||||
(`^[a-z0-9_]+(/[a-z0-9_]+)*$`) and shipped commands must use the documented taxonomy, but nothing
|
||||
holds a project to it, so a tag carries a project-chosen word exactly as a name does.
|
||||
|
||||
So both are withheld. `commandName` and `commandTags` describe only commands that ship in the
|
||||
package (`Unity.Pipeline`, `Unity.Pipeline.Editor`); every other `[CliCommand]` reports the literal
|
||||
`<customUserCommand>` and an empty tag array. The remaining fields — success, duration, `isEval` —
|
||||
are facts about the execution, not about what the project called it, so they are reported for every
|
||||
command.
|
||||
|
||||
What this costs: project-declared traffic is countable but not breakable down by area. That is the
|
||||
deliberate trade — `isUserDefinedCommand` answers how much of the surface is project-declared, which
|
||||
is the question worth asking, and nothing identifying is needed to answer it.
|
||||
|
||||
Command **arguments** are never collected. Neither is anything a command reads or writes.
|
||||
|
||||
## Adding post-command work
|
||||
|
||||
Analytics is one consumer of a single seam rather than a special case. When a command finishes,
|
||||
`BasePipelineServer.OnCommandDone` receives a `CommandExecutionInfo` describing both the raw
|
||||
transaction and the execution. It runs on the HTTP thread, so `EditorPipelineServer` splits by what
|
||||
each consumer needs: the transaction log is appended there directly (thread-safe file I/O by
|
||||
design), and anything needing Unity APIs is handed to the server dispatcher with
|
||||
`Dispatcher.Post`, which runs it in `OnCommandDoneMainThread` on the next main-thread pump.
|
||||
|
||||
New post-command work goes in one of those two, depending on which thread it needs. `Post` is the
|
||||
general fire-and-forget counterpart to `Dispatcher.Invoke`: it returns immediately instead of
|
||||
parking the request thread until the next pump, and swallows-and-logs failures because there is no
|
||||
caller left to throw to. Work still queued when the server stops is dropped, so a report can be
|
||||
lost in the frame the Editor quits or reloads its domain.
|
||||
|
||||
## See also
|
||||
|
||||
- [Creating commands](creating-commands.md) — where a command's name and tags come from.
|
||||
- [Connectivity](connectivity.md) — the port and CORS settings the session event reports.
|
||||
- [Code reload](code-reload.md) — the watcher whose state the session event reports.
|
||||
@@ -0,0 +1,319 @@
|
||||
# Creating authoring commands
|
||||
|
||||
*Authoring commands* are the subset of commands that **create or mutate project content** — assets,
|
||||
scenes, GameObjects, components — on behalf of an agent. They build on the base command API in
|
||||
[Creating commands](creating-commands.md); this page covers the four concerns that are specific to
|
||||
content authoring:
|
||||
|
||||
- **The authoring root** — the sandbox that bare paths resolve against and writes are confined to.
|
||||
- **`ObjectRef`** — how a command *receives* a reference to an existing object.
|
||||
- **`AuthoringResult`** — how a command *returns* the identity of an object it created or touched.
|
||||
- **Undo/redo** — grouping a command's scene mutations into a single, revertible step.
|
||||
|
||||
Together these let one command's output feed the next command's input, so an agent can chain calls
|
||||
(`create_gameobject` → `add_component` → `set_component_properties` → `create_prefab`) without ever
|
||||
handling a raw Unity object itself. If you want to support a content type the package doesn't cover
|
||||
yet (materials, audio, lighting, terrain, …), this is the pattern to follow.
|
||||
|
||||
Read [Creating commands](creating-commands.md) first — this page assumes you know how `[CliCommand]`,
|
||||
`[CliArg]`, `MainThreadRequired`, and the response envelope work.
|
||||
|
||||
## Authoring vs. management commands
|
||||
|
||||
Not every state-changing command is an *authoring* command. This page — and the [checklist](#checklist-for-a-new-authoring-command)
|
||||
at the end — applies to commands that **create or mutate project content** (assets, scenes,
|
||||
GameObjects, components) and hand the agent back an object identity.
|
||||
|
||||
**Management commands** change *configuration* or *drive tooling* rather than author content:
|
||||
project settings (`Editor/Commands/ProjectSettings/`), builds (`Build/`), and packages
|
||||
(`PackageManager/`). They are a **different category** and deliberately do
|
||||
**not** follow the full authoring contract. They share only the cross-cutting safety conventions
|
||||
(the `confirm`/`dry_run` gate and, where relevant, `ObjectResolver`/`ProjectPaths`) — see
|
||||
[Safety & mutations](safety-and-mutations.md).
|
||||
|
||||
| Concern | Authoring command | Management command |
|
||||
|---|---|---|
|
||||
| Object input | `ObjectRef` → `ObjectResolver.TryResolve` | same, when it references an object (e.g. a scene in the build list) |
|
||||
| Path input | `ProjectPaths.Resolve` (confined to the authoring root) | as needed; may be unconfined by design (e.g. a build output path) |
|
||||
| Undo | scene/object mutations wrapped in `AuthoringUndoScope` + registered `Undo` APIs | **none** — settings / AssetDatabase / UPM writes don't participate in Undo, so no scope; note `Not undoable via Ctrl+Z.` in the description instead |
|
||||
| Destructive gate | `confirm`/`dry_run` where destructive | `confirm`/`dry_run` where destructive (same convention) |
|
||||
| Return value | `AuthoringResult` via `ObjectResolver.Describe` | a domain model (e.g. `ProjectSettingsResponse`, `PackageMutationResponse`) or a plain result object |
|
||||
| Failure | **throw** (`ArgumentException` / `InvalidOperationException`) | throw, **or** return a structured `{ success = false, code, error }` object — consistent within the area |
|
||||
|
||||
If you're adding a config/build/package command, follow the management column and the shared safety
|
||||
conventions; the checklist below is for content-authoring commands.
|
||||
|
||||
## The building blocks
|
||||
|
||||
| Type | Namespace | Role |
|
||||
|------|-----------|------|
|
||||
| `ProjectPaths` | `Unity.Pipeline.Editor.Authoring` | Resolve/confine agent-supplied paths to the authoring root. |
|
||||
| `ObjectRef` | `Unity.Pipeline.Models` | Input handle to an existing object (asset or scene object). |
|
||||
| `AuthoringResult` | `Unity.Pipeline.Models` | Output identity of an object your command created or acted on. |
|
||||
| `ObjectResolver` | `Unity.Pipeline.Editor.Authoring` | `TryResolve(ObjectRef …)` (handle → object) and `Describe(object)` (object → `AuthoringResult`). |
|
||||
| `AuthoringUndoScope` | `Unity.Pipeline.Editor.Authoring` | Collapses all `Undo`-registered mutations in its lifetime into one editor undo step. |
|
||||
|
||||
Authoring commands are Editor-only. Put them under `Editor/Commands/<Area>/` in the
|
||||
`Unity.Pipeline.Editor.Commands.<Area>` namespace (see `Editor/Commands/Authoring/AuthoringConfigCommands.cs`).
|
||||
|
||||
## The authoring root (`set_authoring_root`)
|
||||
|
||||
Agents pass **bare, relative paths** (`"Materials/Stone"`), not full project paths. `ProjectPaths`
|
||||
resolves those against a configurable **authoring root** and *confines* every write to it. The root
|
||||
defaults to `Assets` (full project access); an agent can narrow it to a sub-folder to sandbox itself:
|
||||
|
||||
```
|
||||
get_authoring_root → { "root": "Assets" }
|
||||
set_authoring_root --root Assets/AgentWork
|
||||
```
|
||||
|
||||
These are just thin commands over `ProjectPaths.AuthoringRoot` (`Editor/Commands/Authoring/AuthoringConfigCommands.cs`):
|
||||
|
||||
```csharp
|
||||
[CliCommand("set_authoring_root", "Set the base folder (under Assets/) that bare authoring paths resolve against and are confined to. Use 'Assets' for full project access.")]
|
||||
public static object SetAuthoringRoot(
|
||||
[CliArg("root", "Project-relative folder under Assets/, e.g. Assets/AgentWork. Use 'Assets' to allow the whole project.", Required = true)] string root)
|
||||
{
|
||||
// Throws ArgumentException for invalid roots (outside Assets/ or containing ".."); the
|
||||
// server surfaces that message to the caller.
|
||||
ProjectPaths.AuthoringRoot = root;
|
||||
return new { root = ProjectPaths.AuthoringRoot };
|
||||
}
|
||||
```
|
||||
|
||||
**Every path parameter your command accepts must go through `ProjectPaths.Resolve`.** This is the
|
||||
sandbox boundary — it rejects `..` traversal and anything that escapes the root, and it lets callers
|
||||
omit the `Assets/` prefix. Do not build asset paths by string concatenation.
|
||||
|
||||
```csharp
|
||||
var normalized = ProjectPaths.Resolve(path, out var error);
|
||||
if (normalized == null)
|
||||
throw new ArgumentException(error); // e.g. "Path '../secrets' must not contain '..'."
|
||||
// normalized is now a project-relative path guaranteed to live under the authoring root.
|
||||
```
|
||||
|
||||
Resolution rules (`Editor/Authoring/ProjectPaths.cs`):
|
||||
|
||||
- Bare paths (`"Materials/Stone"`) are taken relative to the root → `Assets/AgentWork/Materials/Stone`.
|
||||
- Explicit `Assets/…` / `Packages/…` paths are used as-is (but still confined to the root).
|
||||
- Absolute paths must live under the project root and are converted to project-relative.
|
||||
- `..` anywhere, or a result outside the root, returns `null` + an `error` string.
|
||||
|
||||
## Receiving objects: `ObjectRef`
|
||||
|
||||
When a command needs to act on an object that *already* exists, take an `ObjectRef` parameter. The
|
||||
agent supplies **one** of several forms; `ObjectResolver.TryResolve` tries them in order — `globalId`,
|
||||
`path`, `guid` (+ optional `fileId`), `instanceId`, `hierarchyPath` — and hands you the live object:
|
||||
|
||||
```csharp
|
||||
public static Renderer ResolveRenderer(ObjectRef target)
|
||||
{
|
||||
if (!ObjectResolver.TryResolve(target, out var obj, out var error))
|
||||
throw new ArgumentException(error);
|
||||
|
||||
var go = obj as GameObject ?? (obj as Component)?.gameObject;
|
||||
var renderer = go != null ? go.GetComponent<Renderer>() : null;
|
||||
if (renderer == null)
|
||||
throw new ArgumentException($"Object '{target}' has no Renderer.");
|
||||
return renderer;
|
||||
}
|
||||
```
|
||||
|
||||
Resolve **outside** any undo scope / before mutating, so a bad handle fails before your command
|
||||
changes anything (see `create_gameobject`, which resolves its `parent` before entering the scope).
|
||||
|
||||
When the handle is a plain string (the usual agent input), a value with a file extension and no
|
||||
leading `/` — e.g. `"Materials/Floor.mat"` — is taken as an asset path and normalized under the
|
||||
authoring root, so the `Assets/` prefix is optional (mirroring path-taking commands). A leading `/`
|
||||
or an extension-less value stays a `hierarchyPath`; a dotted scene name like `"Cube.001"` still
|
||||
resolves because the `path` branch falls back to a hierarchy lookup.
|
||||
|
||||
## Returning objects: `AuthoringResult`
|
||||
|
||||
Any command that creates or modifies an object should return its identity so the agent can reference
|
||||
it in a follow-up call. Don't build this by hand — call `ObjectResolver.Describe(obj)`, which fills in
|
||||
the right fields for the object kind:
|
||||
|
||||
- **Assets** get `assetPath`, `guid`, `fileId`, `type` (+ `globalId`).
|
||||
- **Scene / loaded objects** get `instanceId`, `hierarchyPath`, `type` (+ `globalId`).
|
||||
|
||||
```csharp
|
||||
var result = ObjectResolver.Describe(asset) ?? new AuthoringResult { Type = nameof(Material) };
|
||||
result.AssetPath = assetPath; // ensure the path is set even if Describe returned a fresh result
|
||||
return result;
|
||||
```
|
||||
|
||||
`AuthoringResult` is **identity only** — no success flag, no message. Success/failure and timing live
|
||||
in the outer `CommandExecutionResponse` (the server adds them). To report a failure, **throw**
|
||||
(`ArgumentException` for bad input, `InvalidOperationException` for an operation that failed); the
|
||||
server converts the exception into a failure envelope. For a batch, return a model that holds an
|
||||
`AuthoringResult[]` (see `CreateGameObjectsResult`).
|
||||
|
||||
## Undo/redo
|
||||
|
||||
Undo is Unity's native `UnityEditor.Undo`, grouped per command by `AuthoringUndoScope` so a single
|
||||
call reverts as a single Ctrl+Z step. Register each mutation with the matching `Undo` API **inside**
|
||||
the scope:
|
||||
|
||||
```csharp
|
||||
using (new AuthoringUndoScope("Set Material"))
|
||||
{
|
||||
Undo.RecordObject(renderer, "Set Material"); // record BEFORE mutating
|
||||
renderer.sharedMaterial = material;
|
||||
EditorSceneManager.MarkSceneDirty(renderer.gameObject.scene);
|
||||
}
|
||||
```
|
||||
|
||||
Which `Undo` call to use:
|
||||
|
||||
| Mutation | Call |
|
||||
|----------|------|
|
||||
| New scene object | `Undo.RegisterCreatedObjectUndo(obj, name)` |
|
||||
| Change fields on an existing object | `Undo.RecordObject(obj, name)` / `RegisterCompleteObjectUndo` (before the change) |
|
||||
| Add a component | `Undo.AddComponent(go, type)` |
|
||||
| Reparent | `Undo.SetTransformParent(child, parent, name)` |
|
||||
| Serialized properties | `SerializedObject` + `so.ApplyModifiedProperties()` (registers undo itself) |
|
||||
|
||||
> **Important caveat.** `AssetDatabase` operations are **not** part of Unity's undo system. Creating a
|
||||
> folder or asset, importing, `AssetDatabase.CreateAsset`, `SaveAsPrefabAsset`, and file writes are
|
||||
> **not undone** by Ctrl+Z. `AuthoringUndoScope` only covers scene/object mutations. If your command
|
||||
> writes to disk, say so in its description and don't rely on undo to clean up — validate up front and
|
||||
> fail before writing.
|
||||
|
||||
## Worked example — a new content type
|
||||
|
||||
Say the package has no material (rendering) commands and you want to add them. Two commands cover the
|
||||
whole pattern: one that **creates an asset** (path handling + `AuthoringResult` out, no undo because
|
||||
it's an `AssetDatabase` op) and one that **mutates a scene object** (`ObjectRef` in + undo).
|
||||
|
||||
```csharp
|
||||
using System.IO;
|
||||
using Unity.Pipeline.Commands;
|
||||
using Unity.Pipeline.Editor.Authoring;
|
||||
using Unity.Pipeline.Models;
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Pipeline.Editor.Commands.Rendering
|
||||
{
|
||||
/// <summary>Authoring commands for materials (rendering).</summary>
|
||||
public static class MaterialCommands
|
||||
{
|
||||
[CliCommand("create_material",
|
||||
"Create a Material asset (default shader 'Standard') under the authoring root. " +
|
||||
"NOTE: asset creation is not undoable via Ctrl+Z.")]
|
||||
public static AuthoringResult CreateMaterial(
|
||||
[CliArg("path", "Asset path relative to the authoring root; the Assets/ prefix and the .mat extension are optional. e.g. Materials/Stone", Required = true)] string path,
|
||||
[CliArg("shader", "Shader name to assign. Defaults to 'Standard'.")] string shader = "Standard")
|
||||
{
|
||||
// 1. Resolve + confine the path (the sandbox boundary).
|
||||
var normalized = ProjectPaths.Resolve(path, out var error);
|
||||
if (normalized == null)
|
||||
throw new ArgumentException(error);
|
||||
if (!normalized.EndsWith(".mat", System.StringComparison.OrdinalIgnoreCase))
|
||||
normalized += ".mat";
|
||||
|
||||
var found = Shader.Find(shader);
|
||||
if (found == null)
|
||||
throw new ArgumentException($"Shader '{shader}' was not found.");
|
||||
|
||||
// 2. Do the work (AssetDatabase op — no undo scope; it wouldn't be undoable anyway).
|
||||
EnsureParentFolder(normalized);
|
||||
var material = new Material(found);
|
||||
AssetDatabase.CreateAsset(material, normalized);
|
||||
AssetDatabase.SaveAssets();
|
||||
|
||||
// 3. Return the created object's identity so the agent can reference it next.
|
||||
var result = ObjectResolver.Describe(material) ?? new AuthoringResult { Type = nameof(Material) };
|
||||
result.AssetPath = normalized;
|
||||
return result;
|
||||
}
|
||||
|
||||
[CliCommand("set_material", "Assign a material asset to a Renderer on a scene GameObject.")]
|
||||
public static AuthoringResult SetMaterial(
|
||||
[CliArg("target", "Handle of the GameObject (or Renderer) to modify.", Required = true)] ObjectRef target,
|
||||
[CliArg("material", "Handle of the material asset to assign (path/guid/globalId).", Required = true)] ObjectRef material)
|
||||
{
|
||||
// Resolve both handles up front, before mutating, so a bad handle changes nothing.
|
||||
var renderer = ResolveRenderer(target);
|
||||
if (!ObjectResolver.TryResolve(material, out var matObj, out var matError))
|
||||
throw new ArgumentException(matError);
|
||||
if (matObj is not Material mat)
|
||||
throw new ArgumentException($"'{material}' is not a Material.");
|
||||
|
||||
using (new AuthoringUndoScope("Set Material"))
|
||||
{
|
||||
Undo.RecordObject(renderer, "Set Material"); // record BEFORE the change
|
||||
renderer.sharedMaterial = mat;
|
||||
EditorSceneManager.MarkSceneDirty(renderer.gameObject.scene);
|
||||
}
|
||||
|
||||
return ObjectResolver.Describe(renderer);
|
||||
}
|
||||
|
||||
private static Renderer ResolveRenderer(ObjectRef target)
|
||||
{
|
||||
if (!ObjectResolver.TryResolve(target, out var obj, out var error))
|
||||
throw new ArgumentException(error);
|
||||
|
||||
var go = obj as GameObject ?? (obj as Component)?.gameObject;
|
||||
var renderer = go != null ? go.GetComponent<Renderer>() : null;
|
||||
if (renderer == null)
|
||||
throw new ArgumentException($"Object '{target}' has no Renderer.");
|
||||
return renderer;
|
||||
}
|
||||
|
||||
// Mirrors the shared helper used by the built-in asset commands.
|
||||
private static void EnsureParentFolder(string assetPath)
|
||||
{
|
||||
var parent = Path.GetDirectoryName(assetPath)?.Replace('\\', '/');
|
||||
if (string.IsNullOrEmpty(parent) || AssetDatabase.IsValidFolder(parent))
|
||||
return;
|
||||
// Create intermediate folders (see create_folder / CreateFolderRecursive).
|
||||
Directory.CreateDirectory(ProjectPaths.ProjectRoot + "/" + parent);
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
An agent chains them by feeding the first result into the second:
|
||||
|
||||
```
|
||||
create_material --path Materials/Stone --shader Standard
|
||||
→ { "assetPath": "Assets/AgentWork/Materials/Stone.mat", "guid": "…", "type": "Material" }
|
||||
|
||||
set_material --target '{"hierarchyPath":"/Ground"}' --material '{"path":"Assets/AgentWork/Materials/Stone.mat"}'
|
||||
→ { "instanceId": …, "hierarchyPath": "/Ground", "type": "MeshRenderer" }
|
||||
```
|
||||
|
||||
## Checklist for a new authoring command
|
||||
|
||||
For *content-authoring* commands. Config/build/package commands follow the lighter
|
||||
[management-command conventions](#authoring-vs-management-commands) instead.
|
||||
|
||||
- [ ] Editor-only, under `Editor/Commands/<Area>/`, `static` (any accessibility — `public`/`internal`/`private`), tagged `[CliCommand]`.
|
||||
- [ ] Every path parameter resolved through `ProjectPaths.Resolve` (never concatenated).
|
||||
- [ ] Existing-object inputs taken as `ObjectRef`, resolved via `ObjectResolver.TryResolve`; resolve **before** mutating.
|
||||
- [ ] Scene/object mutations wrapped in an `AuthoringUndoScope` and registered with the matching `Undo` API.
|
||||
- [ ] `AssetDatabase` writes noted as non-undoable in the description; validate before writing.
|
||||
- [ ] Returns `AuthoringResult` (via `ObjectResolver.Describe`) or a model containing `AuthoringResult[]`.
|
||||
- [ ] Reports failures by throwing; never build the response envelope yourself.
|
||||
|
||||
## Build & verify
|
||||
|
||||
New commands register automatically after the next recompile (see
|
||||
[Creating commands → Discovery](creating-commands.md#discovery)). Drive the live editor to verify:
|
||||
|
||||
```
|
||||
command recompile
|
||||
command recompile_status # poll until done
|
||||
command create_material --path Materials/Stone
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [Creating commands](creating-commands.md) — the base command API this page builds on.
|
||||
- [Asset & file commands](commands/assets-and-files.md) — reference for the built-in asset commands.
|
||||
- [GameObject & component commands](commands/gameobjects-and-components.md) — the scene-mutation commands.
|
||||
@@ -0,0 +1,177 @@
|
||||
# Reloading Code
|
||||
|
||||
Update C# methods with the `[CodeReload]` attribute without restarting Play Mode in the editor or rebuilding the Player. Two backends are supported:
|
||||
|
||||
| Command | Backend | Where | Language | Pipeline server needed in player |
|
||||
|---------|---------|-------|----------|----------------------------------|
|
||||
| `reload_file` | `Assembly.Load` (default) | Editor Play Mode, Desktop Mono dev builds | Full C# | yes |
|
||||
| `reload_file_editor_interpreter`, `reload_file_player_interpreter` | IlInterpreter | Any dev build, **including IL2CPP** | A C# subset (see [constraints](#interpreter-constraints)) | no |
|
||||
|
||||
The watch UI and device pushes always use the interpreter: an IL2CPP player has no Roslyn and no `Assembly.Load`, so the editor compiles and the device interprets. Code reload is gated to the editor and development builds (`UNITY_EDITOR || DEVELOPMENT_BUILD || ENABLE_RUNTIME_PIPELINE`) — the whole `Unity.Pipeline` assembly carries that define constraint, so a release build contains none of it unless you opt in with `ENABLE_RUNTIME_PIPELINE` (see `runtime-setup.md`). `[CodeReload]` itself lives in the unconstrained `Unity.Pipeline.Attributes` assembly, so a tagged method still compiles for a release build; it simply runs its original body, with no dispatch prologue woven in.
|
||||
|
||||
## Tagging a method
|
||||
|
||||
Tag the method you want to reload with `[CodeReload]`:
|
||||
|
||||
```csharp
|
||||
using Unity.Pipeline.CodeReload;
|
||||
using UnityEngine;
|
||||
|
||||
public class Spinner : MonoBehaviour
|
||||
{
|
||||
public float rotationSpeed = 90f;
|
||||
|
||||
[CodeReload]
|
||||
public void Update()
|
||||
{
|
||||
transform.Rotate(Vector3.up, rotationSpeed * Time.deltaTime);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The attribute must be there before entering Play Mode or building a player, because the dispatch check is woven in at compile time (see [How it works](#how-it-works)). Edit the body of `Update`, then apply the file with one of the commands below. The running instance picks up the new body.
|
||||
|
||||
**Constraints.** The `[CodeReload]` method must be **`public`** (instance **or static**) and must return **`void` or `System.Collections.IEnumerator`** (a coroutine). Methods returning anything else are skipped at build time with a warning. The `Assembly.Load` backend adds one more rule, described in its section: the body may only touch public members of its host type.
|
||||
|
||||
### Supported changes
|
||||
|
||||
**New methods:** a reload can declare a method the compiled type doesn't have yet and call it from a reloaded body (instance and static methods, expression bodies included; not generics, properties, or fields). A new method cannot be a reload *entry point* — nothing compiled calls it until the next real compile — so tagging it `[CodeReload]` registers nothing for it (the reload response says so) while the rest of the file still applies.
|
||||
|
||||
**Method groups and delegates:** a reloaded body can pass a method as a callback — `_root.Add(new Button(Click))`, `button.clicked += Click`, `RegisterCallback<ClickEvent>(OnClick)` — whether the method already exists compiled or was just added, and event `-=` unsubscribes as expected. Lambdas and closures work too, and delegate *values* combine (`a += b` on an `Action` local/field). Limits: at most 4 delegate parameters and no ref/out parameters. One caveat for a method group over a *new* method: the receiver is evaluated when the delegate fires, not captured at subscription, and `-=` of such a group won't match the `+=`.
|
||||
|
||||
**Coroutines:** a `[CodeReload] IEnumerator` method picks up the new body at iterator **creation** — each `StartCoroutine` call uses the latest reload, while coroutines already running keep executing the body they were created from (use `[OnCodeReload]` to stop/restart long-lived ones). `yield return null`, `WaitForSeconds`, and nested coroutines all work; `IEnumerable`/generic iterator returns are not supported. Two caveats: an exception thrown inside a reloaded coroutine body surfaces from the coroutine scheduler with no automatic fallback to the original body (unlike void methods), and on the interpreter backend a `try/finally` around a `yield` runs its `finally` on the normal path and on `Dispose`, but **not** when the body throws. Coroutines are the main use case for the `[OnCodeReload]` callbacks described below.
|
||||
|
||||
## Reload callbacks — `[OnCodeReload]`
|
||||
|
||||
Tag a **parameterless instance method** with `[OnCodeReload]` to run code after a reload lands — a place to re-initialize state the swapped code depends on (think a domain-reload-free `OnEnable`):
|
||||
|
||||
```csharp
|
||||
[OnCodeReload]
|
||||
void OnCodeReloaded()
|
||||
{
|
||||
StopAllCoroutines();
|
||||
StartCoroutine(MainLoop()); // restart so the loop picks up the new body
|
||||
}
|
||||
```
|
||||
|
||||
It fires once per reload that changes at least one method on the declaring type, on **every live instance** (`UnityEngine.Object`-derived types only), on the main thread. Exceptions are logged, not propagated. Both backends and the watcher trigger it. Typical uses: restarting long-lived coroutines, disabling and re-enabling MonoBehaviours, and refreshing caches computed by the old code.
|
||||
|
||||
## `Assembly.Load` backend: the `reload_file` command
|
||||
|
||||
This backend compiles the edited C# in the process that runs it, so reloading in a player does not need a running editor. It does need the runtime Pipeline server in the player, since the command arrives over HTTP.
|
||||
|
||||
```bash
|
||||
unity command reload_file "<absolute path>/Spinner.cs"
|
||||
|
||||
# Emit debug symbols so breakpoints bind (compiles unoptimized):
|
||||
unity command reload_file "<absolute path>/Spinner.cs" --pdb
|
||||
```
|
||||
|
||||
`reload_file` parameters:
|
||||
|
||||
| Arg | Default | Meaning |
|
||||
|-----|---------|---------|
|
||||
| `filename` | *(required)* | Source file containing `[CodeReload]` methods. |
|
||||
| `timeout` | `30000` | Compilation timeout (ms). |
|
||||
| `assemblyDir` | `null` | Optional directory to also save the compiled assembly to disk (default: in-memory only). |
|
||||
| `pdb` | `false` | Emit a portable PDB mapped to the original source so debugger breakpoints bind. Compiles unoptimized. |
|
||||
|
||||
**Public members only.** The reloaded body may only access public members of its host type. The compiled code loads as a separate assembly and Mono enforces accessibility, so a private field or method would fail at dispatch; the reload rejects it up front with the offending members listed.
|
||||
|
||||
## Interpreter backend: the `reload_file_*_interpreter` commands
|
||||
|
||||
The editor compiles the edited file to IL, and the IlInterpreter runs that IL where the reload lands: in the editor process, or in a connected player. No `Assembly.Load` is involved, so this backend works in IL2CPP players. Both commands compile in the editor, so reloading a player needs the editor running and the player connected.
|
||||
|
||||
### `reload_file_editor_interpreter`
|
||||
|
||||
Same as `reload_file`, but the reloaded methods run through the interpreter in the editor process. Use it to check that an edit stays within the interpreter's subset and host surface before pushing it to a device.
|
||||
|
||||
```bash
|
||||
unity command reload_file_editor_interpreter "<absolute path>/Spinner.cs"
|
||||
```
|
||||
|
||||
| Arg | Default | Meaning |
|
||||
|-----|---------|---------|
|
||||
| `filename` | *(required)* | Source file containing `[CodeReload]` methods. |
|
||||
| `timeout` | `30000` | Compilation timeout (ms). |
|
||||
| `assemblyDir` | `null` | Optional directory to also save the compiled IL to disk (default: in-memory only). |
|
||||
| `pdb` | `false` | Emit a portable PDB mapped to the original source. Runtime errors already report source lines on this backend, so this only matters if you also want the `.pdb` on disk. |
|
||||
|
||||
The public-members-only constraint of `reload_file` does not apply: the interpreter reaches private members by reflection.
|
||||
|
||||
### `reload_file_player_interpreter`
|
||||
|
||||
Compiles a file's (or a folder's) `[CodeReload]` methods in the editor and pushes the IL to connected development players over PlayerConnection (the Profiler's channel, so no open port is needed and it tunnels over USB). Folders are walked recursively; a `.cs` file is only compiled if it mentions `CodeReload`, and files that don't are skipped rather than reported as errors.
|
||||
|
||||
```bash
|
||||
# Compile a file's [CodeReload] methods and push to every connected player
|
||||
unity command reload_file_player_interpreter "Assets/Scripts/Player.cs"
|
||||
|
||||
# Push a whole folder, targeting one player by id (-1 = broadcast, the default)
|
||||
unity command reload_file_player_interpreter "Assets/Scripts" --player 2
|
||||
```
|
||||
|
||||
| Arg | Default | Meaning |
|
||||
|-----|---------|---------|
|
||||
| `filename` | *(required)* | Source `.cs` file or a folder containing `[CodeReload]` methods (e.g. `Assets/pong.cs` or `Assets/Scripts`). |
|
||||
| `player` | `-1` | Target connected player id. `-1` broadcasts to all connected players. |
|
||||
|
||||
Points to know:
|
||||
|
||||
- The push compiles with the **player's** preprocessor defines, not the editor's.
|
||||
- The player needs the runtime Pipeline driver enabled (see [Runtime connection & setup](runtime-setup.md)); the HTTP server is not required.
|
||||
- The response reports the compile result and any host-surface members the pushed code uses that the player does not expose (see [Available APIs](#interpreter-constraints)). The player acks the apply asynchronously to the editor console.
|
||||
- Unlike the watcher, the command pushes every tagged method in the file, not just the ones that changed. That is the escape hatch when the connected player was not built from the sources you are editing.
|
||||
|
||||
### Interpreter constraints
|
||||
|
||||
The `Assembly.Load` backend runs plain C# — anything Roslyn compiles works. The interpreter backend does not: it executes a **C# subset** against a **fixed host surface**. The subset is checked before the code runs; the host surface only bites at runtime, on a device.
|
||||
|
||||
**Language subset.** The shipped Roslyn analyzer (diagnostics **MS002–MS023**) marks violations in your IDE, on `[CodeReload]` method bodies only. Not supported:
|
||||
|
||||
- `await` / `Task`, `try`/`catch`/`finally`, `lock`, LINQ query syntax (`from x in …`), `checked`
|
||||
- `decimal`, `dynamic`, reflection types (`Type`, `MethodInfo`, …), threading types
|
||||
- `ref`/`out`/`in` parameters, `ref` returns and locals, multi-dimensional arrays (`int[,]` — jagged `int[][]` is fine), generic methods
|
||||
- declaring static fields, value-type nullables (`T?`), new type declarations inside the file, `yield` in a local function
|
||||
|
||||
`long`, `ulong`, and `double` work (the VM runs them in 64-bit slots). Lambdas, closures, delegates, `goto`, string interpolation, and coroutine `yield` all work — see the delegate and coroutine notes above for their edges.
|
||||
|
||||
**Available APIs.** Interpreted code can only call the UnityEngine/BCL members the interpreter exposes, plus the reloaded type's own members. In the editor, missing members bind on demand; on a device they cannot (IL2CPP strips unreferenced code), so an unexposed member throws when the reloaded code reaches it. A device push checks this up front and lists the missing members as warnings in the push response.
|
||||
|
||||
### Watching for changes
|
||||
|
||||
Instead of running `reload_file_player_interpreter` or `reload_file_editor_interpreter` by hand, let the editor watch for saves: **Window → Pipeline → Settings… → Code Reload Interpreter Watch → Start Watching**. The watch covers the whole Assets tree; a saved `.cs` only compiles if it mentions `CodeReload`.
|
||||
|
||||

|
||||
|
||||
Each save picks its target automatically:
|
||||
|
||||
- **A development player is connected** (Autoconnect Profiler, or attached via Profiler/Console) → the save compiles in the editor and is pushed to every connected player over PlayerConnection, always on the interpreter backend.
|
||||
- **No player** → the save applies in this editor process, also on the interpreter backend.
|
||||
|
||||
Plugging in or disconnecting a device mid-watch reroutes the next save; no restart needed.
|
||||
|
||||
The workflow: start the watch, enter Play Mode, edit `[CodeReload]` bodies, save. Points to know:
|
||||
|
||||
- **Auto-refresh is off while watching**, so a save reloads instead of triggering a domain reload. Other assets won't import until you refresh manually (Cmd/Ctrl+R). **Stop Watching** restores your setting.
|
||||
- Only methods that **changed since the watch started** reload; untouched ones keep running compiled.
|
||||
- The watch **survives domain reloads**, re-applies reloads that a Play Mode domain reload wiped, and re-pushes state to a player that reconnects (a restarted player boots the original code) — governed by the **Re-push On Connect** setting.
|
||||
- The inspector shows liveness: watcher event count, last apply, and the resolved target. Compile errors and binding warnings land in the Console.
|
||||
|
||||
To push once without a watch, run [`reload_file_player_interpreter`](#reload_file_player_interpreter) directly.
|
||||
|
||||
## How it works
|
||||
|
||||
Every `[CodeReload]` method gets a small check woven in when your project builds: on each call it runs the latest reloaded body if one exists, otherwise the original. That is why edits apply without touching call sites, and why a reload only ever changes tagged methods.
|
||||
|
||||
An applied reload compiles the edited source **in memory**; nothing is written to disk unless you pass `assemblyDir`. The result then either loads next to the original assembly (`Assembly.Load` backend, Mono only) or is executed by the interpreter (works everywhere, including IL2CPP).
|
||||
|
||||
## Supporting commands
|
||||
|
||||
- `codereload_status` — list which methods currently have a reload applied.
|
||||
- `cleanup_codereload` — delete saved reload assemblies and revert every method to its compiled body.
|
||||
|
||||
## See also
|
||||
|
||||
- [Runtime connection & setup](runtime-setup.md) — enabling code reload in a Player build.
|
||||
- [Command reference](commands/runtime.md)
|
||||
@@ -0,0 +1,191 @@
|
||||
# Animation commands
|
||||
|
||||
Author animation assets: AnimationClips (and their float curves), AnimatorControllers (parameters, layers, states, transitions), and Timeline assets. Create commands write under the authoring root and follow the shared `confirm`/`dry_run` convention.
|
||||
|
||||
## AnimationClips
|
||||
|
||||
### `create_animation_clip`
|
||||
Create an empty .anim AnimationClip asset under the authoring root, with an optional frame rate and loop flag.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `path` | yes | `–` | Asset path ending in .anim, relative to the authoring root. The Assets/ prefix is optional. |
|
||||
| `frameRate` | no | `60` | Sampling frame rate of the clip. |
|
||||
| `loop` | no | `false` | If true, set the clip's loop-time flag in its AnimationClipSettings. |
|
||||
| `confirm` | no | `false` | Required (true) only when overwriting an existing asset at the path. |
|
||||
| `dry_run` | no | `false` | If true, validate inputs and report what would be created without writing anything. |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
|
||||
### `set_animation_curve`
|
||||
Add or replace a single float curve binding on an AnimationClip (via AnimationUtility.SetEditorCurve). Replacing an existing binding overwrites it rather than duplicating.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `clip` | yes | `–` | Reference to the AnimationClip to edit (path / guid / globalId). |
|
||||
| `path` | no | `""` | GameObject path relative to the animated root the property lives on. Empty string targets the root. |
|
||||
| `type` | yes | `–` | Component type the property lives on, e.g. "Transform", "UnityEngine.Light". Resolved via the component TypeResolver. |
|
||||
| `property` | yes | `–` | Curve property name, e.g. "m_LocalPosition.x", "m_LocalScale.y", "localEulerAnglesRaw.z". |
|
||||
| `keys` | yes | `–` | Keyframes: `[{ time, value, inTangent?, outTangent?, weightedMode?: "None"\|"In"\|"Out"\|"Both" }]`. Omitted tangents default to 0 (flat); this is NOT Unity's Auto tangent mode. |
|
||||
| `dry_run` | no | `false` | If true, validate type/property/keys without writing the curve. |
|
||||
|
||||
**Returns:** `SetAnimationCurveResult`
|
||||
|
||||
### `get_animation_clip`
|
||||
Read an AnimationClip's metadata and all float curve bindings (optionally with keyframes).
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `clip` | yes | `–` | Reference to the AnimationClip to read (path / guid / globalId). |
|
||||
| `includeKeys` | no | `false` | If true, include each binding's keyframes. |
|
||||
|
||||
**Returns:** `AnimationClipInfo`
|
||||
|
||||
### `remove_animation_curve`
|
||||
Remove a float curve binding from an AnimationClip (SetEditorCurve(clip, binding, null)). Destructive: requires confirm=true.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `clip` | yes | `–` | Reference to the AnimationClip to edit (path / guid / globalId). |
|
||||
| `path` | no | `""` | GameObject path relative to the animated root the binding lives on. Empty string targets the root. |
|
||||
| `type` | yes | `–` | Component type of the binding to remove, e.g. "Transform". Resolved via the component TypeResolver. |
|
||||
| `property` | yes | `–` | Curve property name to remove, e.g. "m_LocalPosition.x". |
|
||||
| `confirm` | no | `false` | Must be true to actually remove the binding (destructive guard). |
|
||||
| `dry_run` | no | `false` | If true, report the binding that would be removed without removing it. |
|
||||
|
||||
**Returns:** `SetAnimationCurveResult`
|
||||
|
||||
## AnimatorControllers
|
||||
|
||||
### `create_animator_controller`
|
||||
Create an .controller AnimatorController asset (with a default Base Layer) under the authoring root.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `path` | yes | `–` | Asset path ending in .controller, relative to the authoring root. The Assets/ prefix is optional. |
|
||||
| `confirm` | no | `false` | Required (true) only when overwriting an existing asset at the path. |
|
||||
| `dry_run` | no | `false` | If true, validate inputs and report what would be created without writing anything. |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
|
||||
### `add_animator_parameter`
|
||||
Add a parameter (Float | Int | Bool | Trigger) to an AnimatorController. A duplicate name returns code 'duplicate_parameter'.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `controller` | yes | `–` | Reference to the AnimatorController to edit (path / guid / globalId). |
|
||||
| `name` | yes | `–` | Parameter name. |
|
||||
| `type` | yes | `–` | Parameter type: Float \| Int \| Bool \| Trigger. |
|
||||
| `defaultValue` | no | `–` | Default value for Float/Int/Bool (ignored for Trigger). |
|
||||
| `dry_run` | no | `false` | If true, validate inputs without writing the parameter. |
|
||||
|
||||
**Returns:** `object`
|
||||
|
||||
### `add_animator_layer`
|
||||
Add a layer to an AnimatorController.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `controller` | yes | `–` | Reference to the AnimatorController to edit (path / guid / globalId). |
|
||||
| `name` | yes | `–` | Layer name. |
|
||||
| `weight` | no | `1` | Layer weight. |
|
||||
| `blendingMode` | no | `Override` | Blending mode: Override \| Additive. |
|
||||
| `dry_run` | no | `false` | If true, validate inputs without writing the layer. |
|
||||
|
||||
**Returns:** `object`
|
||||
|
||||
### `add_animator_state`
|
||||
Add a state to a layer, optionally with a motion (AnimationClip or BlendTree) and as the layer default. A layer name with no match returns code 'layer_not_found'.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `controller` | yes | `–` | Reference to the AnimatorController to edit (path / guid / globalId). |
|
||||
| `layer` | no | `0` | Layer index (int) or name (string). Default 0 (Base Layer). |
|
||||
| `name` | yes | `–` | State name. |
|
||||
| `motion` | no | `–` | Optional AnimationClip or BlendTree asset to assign as the state's motion. |
|
||||
| `isDefault` | no | `false` | If true, set this state as the layer's default state. |
|
||||
| `position` | no | `–` | Optional [x, y] node position in the graph (cosmetic). |
|
||||
| `dry_run` | no | `false` | If true, validate inputs without writing the state. |
|
||||
|
||||
**Returns:** `object`
|
||||
|
||||
### `add_animator_transition`
|
||||
Add a transition between two states (or from AnyState/Entry, to Exit) on a layer, with optional conditions. Validates that the states exist and each condition's parameter exists and its mode matches the parameter type.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `controller` | yes | `–` | Reference to the AnimatorController to edit (path / guid / globalId). |
|
||||
| `layer` | no | `0` | Layer index (int) or name (string). Default 0 (Base Layer). |
|
||||
| `fromState` | yes | `–` | Source state name, or the special "AnyState" / "Entry". |
|
||||
| `toState` | yes | `–` | Destination state name, or the special "Exit". |
|
||||
| `conditions` | no | `–` | Optional conditions: `[{ parameter, mode: "If"\|"IfNot"\|"Greater"\|"Less"\|"Equals"\|"NotEqual", threshold? }]`. |
|
||||
| `hasExitTime` | no | `false` | If true, the transition uses exit time. |
|
||||
| `exitTime` | no | `0` | Normalized exit time (0..1) when hasExitTime is set. |
|
||||
| `duration` | no | `0.25` | Transition duration in seconds. |
|
||||
| `hasFixedDuration` | no | `true` | If true, duration is in seconds; otherwise normalized. |
|
||||
| `dry_run` | no | `false` | If true, validate everything (states, parameters, mode/type) without writing the transition. |
|
||||
|
||||
**Returns:** `object`
|
||||
|
||||
### `get_animator_controller`
|
||||
Read an AnimatorController's full structure: parameters, layers, states (with motion / default), and transitions (with conditions).
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `controller` | yes | `–` | Reference to the AnimatorController to read (path / guid / globalId). |
|
||||
|
||||
**Returns:** `AnimatorControllerInfo`
|
||||
|
||||
## Timeline
|
||||
|
||||
The Timeline commands require the `com.unity.timeline` package.
|
||||
|
||||
### `create_timeline`
|
||||
Create a .playable TimelineAsset under the authoring root (optional frame rate).
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `path` | yes | `–` | Asset path ending in .playable, relative to the authoring root. The Assets/ prefix is optional. |
|
||||
| `frameRate` | no | `60` | Timeline frame rate. |
|
||||
| `confirm` | no | `false` | Required (true) only when overwriting an existing asset at the path. |
|
||||
| `dry_run` | no | `false` | If true, validate inputs and report what would be created without writing anything. |
|
||||
|
||||
**Returns:** `object`
|
||||
|
||||
### `add_timeline_track`
|
||||
Add a track (Animation | Audio | Activation | Control | Playable | Signal | Marker) to a TimelineAsset, optionally nested under a parent group/track.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `timeline` | yes | `–` | Reference to the TimelineAsset to edit (path / guid / globalId). |
|
||||
| `trackType` | yes | `–` | Track type: Animation \| Audio \| Activation \| Control \| Playable \| Signal \| Marker. |
|
||||
| `name` | no | `–` | Optional track display name. |
|
||||
| `parentTrack` | no | `–` | Optional name of an existing group/track to nest the new track under. |
|
||||
| `dry_run` | no | `false` | If true, validate inputs without writing the track. |
|
||||
|
||||
**Returns:** `object`
|
||||
|
||||
### `add_timeline_clip`
|
||||
Add a clip to a named track on a TimelineAsset. For Animation tracks pass an AnimationClip asset; for Audio tracks an AudioClip.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `timeline` | yes | `–` | Reference to the TimelineAsset to edit (path / guid / globalId). |
|
||||
| `track` | yes | `–` | Target track name. |
|
||||
| `start` | yes | `–` | Clip start time in seconds. |
|
||||
| `duration` | yes | `–` | Clip duration in seconds. |
|
||||
| `asset` | no | `–` | Source asset: for Animation tracks an AnimationClip; for Audio tracks an AudioClip. Required for those track types. |
|
||||
| `dry_run` | no | `false` | If true, validate inputs without writing the clip. |
|
||||
|
||||
**Returns:** `object`
|
||||
|
||||
### `get_timeline`
|
||||
Read a TimelineAsset's structure: frame rate, duration, and its tracks with their clips.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `timeline` | yes | `–` | Reference to the TimelineAsset to read (path / guid / globalId). |
|
||||
|
||||
**Returns:** `object`
|
||||
|
||||
See [Creating commands](../creating-commands.md) and [Connectivity](../connectivity.md).
|
||||
@@ -0,0 +1,150 @@
|
||||
# Asset & file commands
|
||||
|
||||
Commands for creating, importing, moving, copying, renaming, deleting and finding project assets, plus folder and text-file operations. Every agent-supplied path is funnelled through the authoring-root sandbox (it rejects `../` and out-of-project writes); destructive or overwriting operations require `confirm=true` and support `dry_run`. AssetDatabase create/move/copy/rename/delete are not part of Unity's Undo system.
|
||||
|
||||
### `create_asset`
|
||||
Create a new ScriptableObject (or other UnityEngine.Object) asset of the given type at a path under the authoring root.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `path` | yes | – | Asset path relative to the authoring root, including extension (e.g. Data/Config.asset or Materials/Wall.mat). The Assets/ prefix is optional. |
|
||||
| `type` | yes | – | Fully-qualified or short type name to instantiate (e.g. UnityEngine.Material, MyGame.GameConfig). Must derive from UnityEngine.Object and be creatable. |
|
||||
| `shader` | no | `–` | Material-only (ignored otherwise): shader name to assign (e.g. Standard, "Universal Render Pipeline/Lit"). When omitted, defaults to "Universal Render Pipeline/Lit" if a Scriptable Render Pipeline is active, otherwise the built-in "Standard" shader (falling back to "Standard" if URP/Lit is unavailable). |
|
||||
| `confirm` | no | `false` | Required (true) only when overwriting an existing asset at the path. Ignored when the path is empty. |
|
||||
| `dry_run` | no | `false` | If true, validate inputs and report what would be created without writing anything. |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
**Notes:** `confirm=true` required to overwrite an existing asset; supports `dry_run`.
|
||||
|
||||
### `import_asset`
|
||||
Import an external file (e.g. a texture, model, audio clip) into the project by copying it to a path under the authoring root, then importing it.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `source` | yes | – | Absolute filesystem path to the external file to import. |
|
||||
| `path` | yes | – | Destination asset path relative to the authoring root, including extension. The Assets/ prefix is optional. |
|
||||
| `confirm` | no | `false` | Required (true) only when overwriting an existing asset at the destination path. |
|
||||
| `dry_run` | no | `false` | If true, validate inputs and report what would be imported without writing anything. |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
**Notes:** `confirm=true` required to overwrite an existing asset; supports `dry_run`.
|
||||
|
||||
### `move_asset`
|
||||
Move (or rename via a new path) an asset to a new location under the authoring root. Preserves the asset's GUID.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `asset` | yes | – | Reference to the asset to move (path / guid / globalId). |
|
||||
| `destination` | yes | – | Destination asset path relative to the authoring root, including extension. The Assets/ prefix is optional. |
|
||||
| `dry_run` | no | `false` | If true, validate the move (via AssetDatabase.ValidateMoveAsset) without performing it. |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
**Notes:** Supports `dry_run`.
|
||||
|
||||
### `copy_asset`
|
||||
Copy an asset to a new path under the authoring root. The copy gets a fresh GUID.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `asset` | yes | – | Reference to the asset to copy (path / guid / globalId). |
|
||||
| `destination` | yes | – | Destination asset path relative to the authoring root, including extension. The Assets/ prefix is optional. |
|
||||
| `confirm` | no | `false` | Required (true) only when overwriting an existing asset at the destination path. |
|
||||
| `dry_run` | no | `false` | If true, validate inputs and report what would be copied without writing anything. |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
**Notes:** `confirm=true` required to overwrite an existing asset; supports `dry_run`.
|
||||
|
||||
### `rename_asset`
|
||||
Rename an asset in place (keeps it in the same folder, keeps its GUID).
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `asset` | yes | – | Reference to the asset to rename (path / guid / globalId). |
|
||||
| `new_name` | yes | – | New file name WITHOUT a folder path. The extension is preserved if omitted. |
|
||||
| `dry_run` | no | `false` | If true, validate the rename without performing it. |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
**Notes:** Supports `dry_run`.
|
||||
|
||||
### `delete_asset`
|
||||
Delete an asset from the project. Destructive: requires confirm=true.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `asset` | yes | – | Reference to the asset to delete (path / guid / globalId). |
|
||||
| `confirm` | no | `false` | Must be true to actually delete. Without it the command refuses (destructive guard). |
|
||||
| `dry_run` | no | `false` | If true, report the asset that would be deleted without deleting it. |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
**Notes:** Destructive — `confirm=true` required; supports `dry_run`. Not undoable via Unity's Undo.
|
||||
|
||||
### `find_assets`
|
||||
Find assets by type and/or name and/or label, returning their path, GUID and type. At least one filter is required.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `type` | no | `–` | Type name to filter by (e.g. Material, GameObject, ScriptableObject, MyGame.GameConfig). Resolved to a System.Type and matched against each asset's actual main type. |
|
||||
| `name` | no | `–` | Name substring to filter by (AssetDatabase name filter). |
|
||||
| `label` | no | `–` | Asset label to filter by (AssetDatabase 'l:' filter). |
|
||||
| `search_in` | no | `–` | Folder to scope the search to, relative to the authoring root (default: the authoring root). |
|
||||
| `limit` | no | `200` | Maximum number of results to return (default 200). |
|
||||
|
||||
**Returns:** `FindAssetsResult`
|
||||
**Notes:** At least one of `type`, `name`, or `label` is required.
|
||||
|
||||
### `set_import_settings`
|
||||
Set import settings on an asset's AssetImporter (e.g. a texture's isReadable) and re-import it.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `asset` | yes | – | Reference to the asset whose importer to edit (path / guid / globalId). |
|
||||
| `settings` | yes | – | JSON object of importer property/field names to values, e.g. {"isReadable": true, "textureType": "NormalMap"}. |
|
||||
| `dry_run` | no | `false` | If true, validate which settings would apply (and which are unknown) without writing or re-importing. |
|
||||
|
||||
**Returns:** `SetImportSettingsResult`
|
||||
**Notes:** Supports `dry_run`. Import settings live in the asset's .meta file and are not part of Unity's Undo system.
|
||||
|
||||
### `get_import_settings`
|
||||
Read an asset's import settings, structured by importer type (texture/model/audio), including the default-platform fields and (for textures/audio) one platform override block.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `asset` | yes | – | Reference to the asset whose importer to read (path / guid / globalId). |
|
||||
| `platform` | no | `Default` | Platform whose override to read: Default \| Standalone \| iOS \| Android \| WebGL \| tvOS. |
|
||||
|
||||
**Returns:** `GetImportSettingsResult`
|
||||
|
||||
### `create_folder`
|
||||
Create a folder under the authoring root (creates intermediate folders).
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `path` | yes | – | Folder path relative to the authoring root (default Assets/); the Assets/ prefix is optional. e.g. Gameplay/Enemies or Assets/Gameplay/Enemies |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
|
||||
### `read_text_file`
|
||||
Read a UTF-8 text file under the authoring root and return its contents.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `path` | yes | – | Text file path relative to the authoring root. The Assets/ prefix is optional. |
|
||||
| `max_bytes` | no | `1048576` | Reject files larger than this many bytes (default 1048576 = 1 MiB) to avoid huge payloads. |
|
||||
|
||||
**Returns:** `ReadTextFileResult`
|
||||
**Notes:** `MainThreadRequired = true`.
|
||||
|
||||
### `write_text_file`
|
||||
Write UTF-8 text to a file under the authoring root, then import it. Overwriting an existing file requires confirm=true.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `path` | yes | – | Text file path relative to the authoring root, including extension. The Assets/ prefix is optional. |
|
||||
| `contents` | yes | – | The full text content to write (replaces the file). |
|
||||
| `confirm` | no | `false` | Required (true) only when overwriting an existing file at the path. |
|
||||
| `dry_run` | no | `false` | If true, validate inputs and report what would be written without writing anything. |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
**Notes:** `confirm=true` required to overwrite an existing file; supports `dry_run`. Filesystem writes are not part of Unity's Undo system.
|
||||
|
||||
See [Creating commands](../creating-commands.md) and [Connectivity](../connectivity.md).
|
||||
@@ -0,0 +1,140 @@
|
||||
# Baking commands
|
||||
|
||||
Bake and clear precomputed scene data: lightmaps, NavMesh, and occlusion culling. Each `bake_*` runs asynchronously (returns immediately) — poll the matching `*_bake_status` until `completed`. The `clear_*` commands are destructive and require `confirm=true`. The `get_*_settings` / `set_*_settings` pairs read and tune the bake parameters.
|
||||
|
||||
## Lighting
|
||||
|
||||
### `bake_lighting`
|
||||
Trigger an async lightmap bake of the open scene(s) via Lightmapping.BakeAsync(). Returns immediately; poll `lighting_bake_status` until completed.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `confirm` | no | `false` | Recommended (true): a bake overwrites existing lightmap data. Accepted for parity; not required. |
|
||||
| `dry_run` | no | `false` | If true, validate there is an open bakeable scene and return the current lighting settings without baking. |
|
||||
|
||||
**Returns:** `object`
|
||||
|
||||
### `lighting_bake_status`
|
||||
Get the status of the last lighting bake: idle | baking | completed.
|
||||
|
||||
**Returns:** `string`
|
||||
|
||||
### `cancel_lighting_bake`
|
||||
Cancel an in-progress lighting bake (Lightmapping.Cancel()).
|
||||
|
||||
**Returns:** `object`
|
||||
|
||||
### `clear_baked_lighting`
|
||||
Clear baked lightmap data for the open scene(s). Destructive: requires confirm=true.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `confirm` | no | `false` | Must be true to actually clear (destructive, not undoable via Unity's Undo). |
|
||||
| `include_disk_cache` | no | `false` | If true, also clear the GI disk cache (Lightmapping.ClearDiskCache()). |
|
||||
| `dry_run` | no | `false` | If true, report what would be cleared without clearing. |
|
||||
|
||||
**Returns:** `object`
|
||||
|
||||
### `get_lighting_settings`
|
||||
Read the active LightingSettings (lightmapper, bounces, resolution, directional mode, AO, etc.).
|
||||
|
||||
**Returns:** `LightingSettingsResult`
|
||||
|
||||
### `set_lighting_settings`
|
||||
Apply a subset of lighting settings to the active LightingSettings. Returns `{ applied[], unknown[] }`.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `settings` | yes | `–` | JSON object with a subset of lighting fields to set (same names/enums as get_lighting_settings). |
|
||||
| `dry_run` | no | `false` | If true, validate the keys and report applied/unknown without changing anything. |
|
||||
|
||||
**Returns:** `object`
|
||||
|
||||
## NavMesh
|
||||
|
||||
### `bake_navmesh`
|
||||
Trigger an async legacy NavMesh bake of the open scene(s) via UnityEditor.AI.NavMeshBuilder. Returns immediately; poll `navmesh_bake_status` until completed.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `confirm` | no | `false` | Accepted for parity (a bake overwrites the existing NavMesh); not required. |
|
||||
| `dry_run` | no | `false` | If true, validate there is an open scene and return current NavMesh settings without baking. |
|
||||
|
||||
**Returns:** `object`
|
||||
|
||||
### `navmesh_bake_status`
|
||||
Get the status of the last NavMesh bake: idle | baking | completed.
|
||||
|
||||
**Returns:** `string`
|
||||
|
||||
### `cancel_navmesh_bake`
|
||||
Cancel an in-progress NavMesh bake (NavMeshBuilder.Cancel()).
|
||||
|
||||
**Returns:** `object`
|
||||
|
||||
### `clear_navmesh`
|
||||
Clear the baked NavMesh for the open scene(s). Destructive: requires confirm=true.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `confirm` | no | `false` | Must be true to actually clear (destructive, not undoable via Unity's Undo). |
|
||||
| `dry_run` | no | `false` | If true, report what would be cleared without clearing. |
|
||||
|
||||
**Returns:** `object`
|
||||
|
||||
### `get_navmesh_settings`
|
||||
Read the default agent's legacy NavMesh bake settings (agentRadius/Height/Slope/Climb, minRegionArea, voxelSize).
|
||||
|
||||
**Returns:** `NavMeshSettingsResult`
|
||||
|
||||
### `set_navmesh_settings`
|
||||
Apply a subset of legacy NavMesh bake settings to the default agent. Returns `{ applied[], unknown[] }`.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `settings` | yes | `–` | JSON object with a subset of NavMesh fields to set (same names as get_navmesh_settings). |
|
||||
| `dry_run` | no | `false` | If true, validate the keys and report applied/unknown without changing anything. |
|
||||
|
||||
**Returns:** `object`
|
||||
|
||||
### `bake_navmesh_surfaces`
|
||||
Bake NavMeshSurface components (AI Navigation package). v1 stub: returns package_not_found when the package is absent.
|
||||
|
||||
**Returns:** `object`
|
||||
|
||||
## Occlusion culling
|
||||
|
||||
### `bake_occlusion_culling`
|
||||
Trigger an async occlusion-culling bake of the open scene(s) via StaticOcclusionCulling.GenerateInBackground(). Returns immediately; poll `occlusion_bake_status` until completed.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `smallest_occluder` | no | `–` | Smallest object that will occlude others (meters). Defaults to Unity's current value. |
|
||||
| `smallest_hole` | no | `–` | Smallest gap geometry can have that the view can see through (meters). Defaults to Unity's current value. |
|
||||
| `backface_threshold` | no | `–` | Backface threshold (1-100); lower trims more backfaces. Defaults to Unity's current value. |
|
||||
| `confirm` | no | `false` | Accepted for parity (a bake overwrites existing occlusion data); not required. |
|
||||
| `dry_run` | no | `false` | If true, validate there is an open scene and report the parameters that would be used without baking. |
|
||||
|
||||
**Returns:** `object`
|
||||
|
||||
### `occlusion_bake_status`
|
||||
Get the status of the last occlusion bake: idle | baking | completed.
|
||||
|
||||
**Returns:** `string`
|
||||
|
||||
### `cancel_occlusion_bake`
|
||||
Cancel an in-progress occlusion bake (StaticOcclusionCulling.Cancel()).
|
||||
|
||||
**Returns:** `object`
|
||||
|
||||
### `clear_occlusion_culling`
|
||||
Clear baked occlusion-culling data for the open scene(s). Destructive: requires confirm=true.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `confirm` | no | `false` | Must be true to actually clear (destructive, not undoable via Unity's Undo). |
|
||||
| `dry_run` | no | `false` | If true, report what would be cleared without clearing. |
|
||||
|
||||
**Returns:** `object`
|
||||
|
||||
See [Creating commands](../creating-commands.md) and [Connectivity](../connectivity.md).
|
||||
@@ -0,0 +1,102 @@
|
||||
# Batch command
|
||||
|
||||
The `batch` command runs several existing pipeline commands in a single request, with cross-operation result references, atomic Undo, and dry-run validation. It exists to collapse the round-trips of a "create → add components → wire fields" job (otherwise 10–90 sequential calls) into one request, and to add what `eval` structurally lacks: **transactionality and rollback**, so a failure mid-way never leaves partial state on a user-owned scene.
|
||||
|
||||
**Scope guard:** `batch` is transactional object CRUD with references. It intentionally has **no** `invoke`/loop/conditional op types — arbitrary logic belongs in code-on-disk (`run_script`/`eval`), not in JSON. This keeps the command surface auditable and allowlistable.
|
||||
|
||||
### `batch`
|
||||
Run multiple registered commands in one transactional request.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `operations` | yes | – | Ordered list of operations, each `{ id?, command, params }`. Max 200. |
|
||||
| `transactional` | no | `true` | Group every op into one Editor Undo step and revert all applied ops if any op fails. Forced `false` when `on_error=continue`. Rejects commands whose mutations Undo cannot revert (see [Transactional restrictions](#transactional-restrictions)). |
|
||||
| `on_error` | no | `abort` | `abort`: stop at the first failing op. `continue`: run every op, collecting per-op errors (forces `transactional=false`). |
|
||||
| `dry_run` | no | `false` | Validate command names, parameters, reference topology, and excluded/non-batchable commands without mutating anything. |
|
||||
| `result_fields` | no | – | Optional per-op result projection: a map of op id-or-index → array of result field paths to keep (context economy). |
|
||||
| `time_budget_ms` | no | `50000` | Cooperative time budget for the whole batch, in milliseconds (max 3600000). Checked before each op after the first; when exhausted, the remaining ops are skipped and a transactional batch rolls back. |
|
||||
|
||||
**Returns:** `BatchResult` — `{ results: [{ id?, command, success, result? | error?, skipped?, resultTruncated?, revertible? }], applied, reverted, transactional, onError, error?, undoGroup?, dryRun?, valid? }`. The batch-level `error` is set only for aborts not attributable to a single op ("batch time budget exceeded", "batch canceled").
|
||||
|
||||
**Notes:** Runs on the main thread. Each sub-operation goes through the same dispatch path as `/api/exec`, so its result is shaped identically to running that command standalone.
|
||||
|
||||
#### Operation shape
|
||||
|
||||
Each entry of `operations` is:
|
||||
|
||||
```json
|
||||
{ "id": "createHead", "command": "add_component", "params": { "target": "$root.instanceId", "type": "Rigidbody" } }
|
||||
```
|
||||
|
||||
- `id` (optional) — a name for referencing this op's result from a later op. Must be unique within the batch and match `^[A-Za-z_][A-Za-z0-9_-]*$` (purely numeric ids would be ambiguous with index selectors, and `.` collides with the reference path separator).
|
||||
- `command` — the registered command to run (any command except the non-batchable ones below).
|
||||
- `params` — the command's parameters, same shape as `/api/exec` `parameters`. Operations are **always** `command` + `params`; the `argv`/`commandLine` raw forms are exec-only and are not accepted inside a batch operation.
|
||||
|
||||
#### Cross-operation references
|
||||
|
||||
Any **string** value in a later op's `params` may reference an earlier op's result:
|
||||
|
||||
```
|
||||
"$<id-or-index>.<jsonPath>"
|
||||
```
|
||||
|
||||
- `<id-or-index>` names an earlier op by its `id` or its 0-based index (an `id` match takes precedence).
|
||||
- `<jsonPath>` is a Newtonsoft SelectToken path into that op's result, e.g. `instanceId`, `components[0].instanceId`.
|
||||
- `"$$"` escapes a literal `$` (so `"$$5"` is the literal string `"$5"`).
|
||||
- References are **backward-only** and resolved as **whole values** (the entire string must be the reference). The substituted JSON token keeps its type, so a numeric `instanceId` deserializes into an `ObjectRef` parameter exactly as a literal would. References may appear anywhere in the parameter tree (including inside nested objects/arrays).
|
||||
- Referencing an op that **failed** or was **skipped** errors explicitly (this can happen under `on_error=continue`); referencing a succeeded op that **returned no result** errors with "returned no result". A reference never silently resolves to `null`.
|
||||
- References always resolve against the **full** result of the earlier op, even when the reported copy was truncated or projected via `result_fields`.
|
||||
- **Result size bounds** (both measured in serialized UTF-8 bytes, i.e. wire size): a single op's reported result above **16 KiB** is replaced by a truncation marker (`resultTruncated: true` + a preview), and the **aggregate** of all reported results in one batch is bounded to **256 KiB** — past that, later results are elided the same way (the ops still execute; only the echoed copy is dropped). Use `result_fields` to project results down when a batch reads large objects.
|
||||
- A malformed `result_fields` path never fails its op (the op has already executed); the bad path is reported inside the projection as `invalidResultFields`.
|
||||
|
||||
Example — create a GameObject, add a Rigidbody to it, then set the Rigidbody's mass:
|
||||
|
||||
```json
|
||||
{
|
||||
"operations": [
|
||||
{ "command": "create_gameobject", "params": { "name": "Ball" } },
|
||||
{ "command": "add_component", "params": { "target": "$0.instanceId", "type": "Rigidbody" } },
|
||||
{ "command": "set_serialized_field", "params": { "target": "$1.instanceId", "field": "m_Mass", "value": 2.5 } }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Atomicity
|
||||
|
||||
The whole batch runs inside one `AuthoringUndoScope`, so every op (each of which registers its own Undo operations) collapses into a **single** Editor Undo step. When `transactional` (the default) and the batch aborts — an op fails under `on_error=abort`, the time budget runs out, or the job is canceled — every applied op is reverted with `Undo.RevertAllDownToGroup` (including partial mutations registered by the failing op itself), the scene returns to its pre-batch state, and nothing is left on the Undo stack. `reverted: true` and the failing op's `error` are reported.
|
||||
|
||||
#### Transactional restrictions
|
||||
|
||||
Rollback is only real for **Undo-tracked** mutations (scene objects, components, serialized fields). Commands that mutate state **outside** the Undo system — asset and file writes, scene saves, Build Settings, project settings, baked data — cannot be rolled back, so a transactional batch **rejects** them with a structured `not_batchable_transactional` error. Run them with `transactional: false`, or call them standalone. Their per-op result reports `revertible: false` so the limitation is visible.
|
||||
|
||||
The non-revertible set (v1 — a curated list, pending per-command `Revertible` metadata):
|
||||
|
||||
- **Assets & files:** `create_asset`, `import_asset`, `move_asset`, `copy_asset`, `rename_asset`, `delete_asset`, `create_folder`, `set_import_settings`, `write_text_file`, `create_script`
|
||||
- **Prefab assets:** `create_prefab`, `create_prefab_variant`, `apply_prefab_overrides`, `save_prefab_contents` (scene-side prefab ops — `instantiate_prefab`, `unpack_prefab`, `revert_prefab_overrides` — are Undo-tracked and stay fully batchable)
|
||||
- **Scene persistence & build list:** `save_scene`, `save_all`, `add_scene_to_build`, `remove_scene_from_build`, `set_build_settings`
|
||||
- **Settings:** `set_audio_settings`, `set_graphics_settings`, `set_input_settings`, `set_lighting_settings`, `set_navmesh_settings`, `set_physics_settings`, `set_player_settings`, `set_quality_settings`, `set_tags_layers`, `set_time_settings`, `set_authoring_root`
|
||||
- **Animation / Timeline assets:** `create_animation_clip`, `set_animation_curve`, `remove_animation_curve`, `create_animator_controller`, `add_animator_layer`, `add_animator_parameter`, `add_animator_state`, `add_animator_transition`, `create_timeline`, `add_timeline_track`, `add_timeline_clip`
|
||||
- **Materials:** `set_material_properties` (persists the `.mat` asset to disk)
|
||||
- **Baking:** `bake_lighting`, `clear_baked_lighting`, `bake_navmesh`, `bake_navmesh_surfaces`, `clear_navmesh`, `bake_occlusion_culling`, `clear_occlusion_culling`
|
||||
|
||||
`open_scene` and `create_scene` are **always** rejected inside a batch (even with `transactional: false`): they clear the Editor Undo stack, which would silently destroy the batch's ability to revert anything that ran before them.
|
||||
|
||||
#### Timing, budget and cancellation
|
||||
|
||||
The batch executes in a **single main-thread turn** — the Editor UI is frozen for its duration, bounded by `time_budget_ms` (frame-spanning execution is future work). The elapsed time is checked cooperatively before each op after the first; when the budget is exhausted, the remaining ops are reported `skipped: true`, the batch-level `error` is set to "batch time budget exceeded", and a transactional batch rolls back. For long batches, submit the request as a detached job (`"job": true` on `/api/exec`) and raise `time_budget_ms` — jobs also make the batch cancelable: cooperative cancellation (`POST /api/job/cancel`) is checked between ops and aborts the batch the same way ("batch canceled"). Per-op progress is reported (`op i/n: <command>`) and visible via `GET /api/progress`.
|
||||
|
||||
#### Dry run
|
||||
|
||||
`dry_run: true` validates without executing anything: it catches unknown commands, excluded/non-batchable (`not_batchable`) commands, transactional rejections (`not_batchable_transactional`), unknown/missing parameters, and unresolvable reference topology (unknown or forward references). It mutates nothing and reports `valid` plus a per-op `success`/`error`.
|
||||
|
||||
#### Excluded commands
|
||||
|
||||
The following are rejected with a structured `not_batchable` error:
|
||||
|
||||
- **Out of scope:** `build`, `switch_build_target`, `package_*`, `editor_play`/`editor_stop`/`editor_pause`, `recompile`
|
||||
- **Arbitrary code:** `eval`, `eval_file`, `run_script`, and nested `batch`
|
||||
- **Modal-dialog hazard:** `menu` (menu items can open modal dialogs that wedge an unattended batch)
|
||||
- **Cannot make progress:** synchronous `wait_for` — the batch holds the main thread for its whole turn, so the editor state a condition watches cannot change; the wait would just burn its timeout inside the batch. Run an async `wait_for` before/after the batch instead (`wait_status`/`wait_cancel` are quick reads and stay batchable)
|
||||
- **Undo-stack wipe:** `open_scene`, `create_scene`
|
||||
- **Async commands** (`run_tests`, `list_tests`, and any command returning `Task`): they are completed by `EditorApplication.update` callbacks, which cannot fire while the batch holds the main thread — dispatching one would freeze the Editor permanently
|
||||
- **Runtime-only commands** (e.g. `set_timescale`): they belong to the Player server surface and cannot execute on the editor server
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
# Build, compilation & test commands
|
||||
|
||||
These commands drive the build/compile/test dev loop, plus build configuration: triggering Player builds and reading the resulting BuildReport, switching the active build target, enumerating targets, reading/writing EditorUserBuildSettings, and listing Build Profiles. Several commands are asynchronous: trigger the operation, then poll a matching `*_status` command until it reports completion. The client must tolerate connection errors during a domain reload (recompile, target switch) or a long blocking build.
|
||||
|
||||
### `build`
|
||||
Trigger an async Player build and report the full BuildReport. Returns immediately (queued); poll build_status until status is 'completed'. DetailedBuildReport is included by default unless 'options' is supplied. Use dry_run to validate without building.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `target` | no | `–` | BuildTarget name (e.g. StandaloneWindows64). Defaults to the active target. Must be installed. |
|
||||
| `outputPath` | no | `–` | Output path (absolute, or relative to the project root). Defaults to the last/auto path. |
|
||||
| `profileName` | no | `–` | Build Profile name to activate before building (Unity 6 only; ignored otherwise). |
|
||||
| `options` | no | `–` | BuildOptions names. Omit to get just DetailedBuildReport; supplying any disables that default. |
|
||||
| `scenes` | no | `–` | Scene asset paths to build (e.g. Assets/Scenes/Main.unity). Defaults to EditorBuildSettings. |
|
||||
| `confirm` | no | `false` | Acknowledge and run the build; without it the call is refused. Use `dry_run` to validate only. |
|
||||
| `dry_run` | no | `false` | Validate target/outputPath/scenes without building. |
|
||||
|
||||
Supported `options` values (case-insensitive): `Development`, `AllowDebugging`, `ConnectWithProfiler`, `EnableHeadlessMode`, `SymlinkSources`, `BuildAdditionalStreamedScenes`, `CleanBuildCache`, `DetailedBuildReport`. Any unrecognized value is a validation error.
|
||||
|
||||
**Returns:** `object` (transient payloads: `queued` / `busy` / `dry_run` / `error`; the finished report is read via `build_status`).
|
||||
**Notes:** `MainThreadRequired = false`. Async — validates + queues, returns `queued` immediately, then an editor tick runs the (blocking) build; poll `build_status` until status is `completed`. Only one build at a time (returns `busy` otherwise). Mutating: gated by the `confirm`/`dry_run` convention — pass `confirm=true` for a real build, or `dry_run=true` to validate target/outputPath/scenes without building (nothing is queued on a dry run or on validation failure).
|
||||
|
||||
### `build_status`
|
||||
Status of the current/most recent build: idle | queued | building | completed, with the full BuildReport (files, packedAssets, buildSteps, errors, warnings) once completed. Retained until the next build.
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `string` (JSON). `idle` when no build has run; `queued`/`building` (the latter with live `elapsedMs`) while in progress; a full `BuildReportResult` once `completed`.
|
||||
|
||||
`BuildReportResult` fields (returned when `status` is `completed`):
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `status` | Always `completed` here. |
|
||||
| `buildId` | Id returned by the triggering `build` call. |
|
||||
| `result` | `Succeeded` \| `Failed` \| `Cancelled` \| `Unknown`. |
|
||||
| `platform` | Build target the report is for. |
|
||||
| `outputPath` | Final output location. |
|
||||
| `totalSizeBytes` | Total build size in bytes. |
|
||||
| `buildTimeMs` | Total build duration in milliseconds. |
|
||||
| `buildStartedAt` / `buildEndedAt` | ISO-8601 timestamps (omitted when unset). |
|
||||
| `totalWarnings` / `totalErrors` | Aggregate counts from the report summary. |
|
||||
| `files` | Output files (path, role, sizeBytes). Present only on success. |
|
||||
| `packedAssets` | Packed bundles with per-asset breakdown. Present only on success and requires DetailedBuildReport. |
|
||||
| `buildSteps` | Per-step name, durationMs, depth, and messages. |
|
||||
| `errors` / `warnings` | Build issues, each with `message` and best-effort `file`. |
|
||||
|
||||
**Notes:** `MainThreadRequired = false`. Reads a Temp status file off-thread, so polling keeps working while a build holds the main thread. The file also survives the domain reload a build can incur, so the last report is retained until the next build.
|
||||
|
||||
### `switch_build_target`
|
||||
Switch the active build target (destructive, long-running: triggers a full reimport + domain reload). Requires confirm=true. Returns immediately; poll switch_build_target_status.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `target` | yes | `–` | BuildTarget name to switch to (must be installed; see list_build_targets). |
|
||||
| `confirm` | no | `false` | Apply the switch. Without it the call is refused. |
|
||||
|
||||
**Returns:** `object` (`switching` / `busy` / `completed` / `error`). Returns `completed` immediately if already on the requested target.
|
||||
**Notes:** `MainThreadRequired = false`. Async — validates + queues, returns `switching` immediately, then an editor tick performs the (blocking) switch; poll `switch_build_target_status` until `completed`. Only one switch at a time (returns `busy` otherwise). Mutating and confirm-gated: without `confirm=true` the call is refused. The status file survives the domain reload the switch causes, and is reconciled against the active target on the next load.
|
||||
|
||||
### `switch_build_target_status`
|
||||
Status of the last target switch: idle | switching | completed (with success + activeBuildTarget).
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `string` (JSON). `idle`, `switching`, or `completed` (with `success` and `activeBuildTarget`, or `errors` on failure).
|
||||
**Notes:** `MainThreadRequired = false`. Reads a Temp status file off-thread, so it keeps answering while the switch holds the main thread.
|
||||
|
||||
### `list_build_targets`
|
||||
List the known BuildTarget values with their group and whether build support is installed.
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `object` (list of `BuildTargetInfo`: `name`, `displayName`, `targetGroup`, `isInstalled`), sorted by group then name. Obsolete and sentinel targets are excluded.
|
||||
**Notes:** `MainThreadRequired = true`.
|
||||
|
||||
### `get_build_settings`
|
||||
Read the current build configuration from EditorUserBuildSettings / EditorBuildSettings.
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `object` (`BuildSettingsResult`).
|
||||
|
||||
`BuildSettingsResult` fields:
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `activeBuildTarget` | Active BuildTarget name. |
|
||||
| `activeBuildTargetGroup` | Active BuildTargetGroup name. |
|
||||
| `developmentBuild` | Whether a Development Player is configured. |
|
||||
| `allowDebugging` | Whether script debugging is allowed. |
|
||||
| `connectWithProfiler` | Whether the Profiler auto-connects. |
|
||||
| `buildScriptsOnly` | Whether only scripts are built (skip data). |
|
||||
| `symlinkSources` | Whether runtime/plugin sources are symlinked. |
|
||||
| `il2CppCodeGeneration` | `OptimizeSpeed` \| `OptimizeSize` for the active target. |
|
||||
| `scenes` | Build Settings scene list (each: `path`, `guid`, `enabled`). |
|
||||
|
||||
**Notes:** `MainThreadRequired = true`.
|
||||
|
||||
### `set_build_settings`
|
||||
Set mutable EditorUserBuildSettings fields. Does NOT manage scenes (use add_scene_to_build / remove_scene_from_build) or switch target (use switch_build_target). Use dry_run to preview.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `settings` | no | `–` | Fields to change; omitted fields are left unchanged. |
|
||||
| `confirm` | no | `false` | Apply the changes. Without it the call is refused. |
|
||||
| `dry_run` | no | `false` | Preview the change without applying it. |
|
||||
|
||||
`settings` fields (a structured input DTO; every field is optional — only supplied fields change):
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `developmentBuild` | Build a Development Player (enables the debugger/profiler). |
|
||||
| `allowDebugging` | Allow script debugging (only effective with developmentBuild=true). |
|
||||
| `connectWithProfiler` | Auto-connect the Profiler (only effective with developmentBuild=true). |
|
||||
| `buildScriptsOnly` | Build only the scripts (skip data) for faster iteration. |
|
||||
| `symlinkSources` | Symlink runtime/plugin sources instead of copying (where supported). |
|
||||
| `il2CppCodeGeneration` | IL2CPP code generation for the active target: OptimizeSpeed \| OptimizeSize. |
|
||||
|
||||
**Returns:** `object` (`SetBuildSettingsResult`: `success`, `dryRun`, `applied` map of fields that changed, `skipped` map of supplied fields that already matched, `message`).
|
||||
**Notes:** `MainThreadRequired = true`. Mutating: refused unless `confirm=true` (or `dry_run=true` to preview). Fields already at the requested value are reported under `skipped` rather than re-applied. Fails if no `settings` object is provided.
|
||||
|
||||
### `list_build_profiles`
|
||||
List Build Profile assets in the project (Unity 6 only). Returns feature_unavailable on earlier versions.
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `object` (list of `BuildProfileInfo`: `name`, `guid`, `platform`, `isActive`), or `{ error, code = "feature_unavailable" }` on editors older than Unity 6.
|
||||
**Notes:** `MainThreadRequired = true`.
|
||||
|
||||
### `recompile`
|
||||
Force a script recompile (works while unfocused/minimized). Poll recompile_status for completion.
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `object`
|
||||
**Notes:** `MainThreadRequired = true`. Async — poll `recompile_status` until status is `completed` or `up_to_date`. A successful compile triggers a domain reload, so the triggering request cannot stay open.
|
||||
|
||||
### `recompile_status`
|
||||
Get the status of the last recompile: idle | triggered | compiling | completed | up_to_date.
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `string`
|
||||
**Notes:** `MainThreadRequired = false`.
|
||||
|
||||
### `list_tests`
|
||||
List all available tests (EditMode and/or PlayMode) without running them.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `mode` | no | `all` | Test mode: all, editor, playmode (default: all) |
|
||||
|
||||
**Returns:** `TestListResponse`
|
||||
**Notes:** `MainThreadRequired = true`.
|
||||
|
||||
### `run_tests`
|
||||
Execute Unity tests with filtering options.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `mode` | no | `all` | Test mode: all, editor, playmode (default: all) |
|
||||
| `filter` | no | `–` | Test name filter pattern (case-insensitive partial match) |
|
||||
| `filter_type` | no | `testName` | Filter type: testName, assembly, category (default: testName) |
|
||||
| `include_explicit` | no | `false` | Include tests marked with [Explicit] attribute |
|
||||
| `async_tests` | no | `false` | Run asynchronously - return immediately, poll /test-status for results |
|
||||
| `timeout` | no | `300` | Test execution timeout in seconds (default: 300) |
|
||||
|
||||
**Returns:** `TestExecutionResponse`
|
||||
**Notes:** `MainThreadRequired = true`. Synchronous by default; with `async_tests=true` it returns immediately — poll `test_status` for results.
|
||||
|
||||
### `test_status`
|
||||
Get status of running async test execution.
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `string`
|
||||
**Notes:** `MainThreadRequired = false`.
|
||||
|
||||
### `cancel_tests`
|
||||
Cancel running test execution.
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `object`
|
||||
**Notes:** `MainThreadRequired = true`.
|
||||
|
||||
See [Creating commands](../creating-commands.md) and [Connectivity](../connectivity.md).
|
||||
@@ -0,0 +1,61 @@
|
||||
# Capture commands
|
||||
|
||||
Commands that render a camera, the Scene View, or a UI Toolkit element to a PNG and return it base64-encoded so an agent can "see" the editor (or a running player) without a display.
|
||||
|
||||
### `capture_game_view`
|
||||
Render the game view to a PNG. Returns it inline as base64, unless `save_path` is set — then the result is **path-only** (no base64) so agent tool results stay small; pass `include_inline_image=true` to get both.
|
||||
|
||||
`source` selects what is captured. `camera` (default) renders a camera and **does not include Screen Space - Overlay UI** (HUDs, menus, damage numbers on overlay canvases) — overlay canvases composite straight to the screen and never render through a camera. `screen` captures the composited game view backbuffer, so overlay UI is included; because overlay UI only exists at runtime, `source=screen` **requires Play Mode** (in Edit Mode it returns a clear error — use `source=camera`). The screen capture reads the game view at its true pixel resolution — independent of OS display scaling — and preserves the on-screen color tone, so the PNG matches what the Game window shows.
|
||||
|
||||
Parameter interactions: `include_inline_image` is only meaningful together with `save_path` (without one, the inline image is always returned). `max_resolution` only applies when an inline image is actually returned — i.e. no `save_path`, or `save_path` + `include_inline_image=true`; it has **no effect** when `save_path` is set without `include_inline_image=true`. `camera` is ignored when `source=screen`.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `width` | no | `1280` | Output width in px (default 1280; capped 4096). |
|
||||
| `height` | no | `720` | Output height in px (default 720; capped 4096). |
|
||||
| `camera` | no | `–` | Optional camera name (`source=camera` only); defaults to Camera.main, else the first enabled camera. |
|
||||
| `save_path` | no | `–` | Optional project-relative path to write the PNG (e.g. Screenshots/foo.png). When set, the result omits the inline base64 image unless `include_inline_image=true`. |
|
||||
| `include_inline_image` | no | `false` | Also return the image inline as base64 when `save_path` is set. |
|
||||
| `max_resolution` | no | `0` | Cap on the inline image's longest edge (e.g. 512), preserving aspect ratio. No effect when `save_path` is set without `include_inline_image=true`. The `save_path` file keeps the requested resolution; a downscaled inline copy is reported via `inlineWidth`/`inlineHeight`. |
|
||||
| `source` | no | `camera` | `camera` renders a camera (misses Screen Space - Overlay UI); `screen` captures the composited game view incl. overlay canvases (**Play Mode only**). |
|
||||
|
||||
**Returns:** `CaptureResult`
|
||||
|
||||
### `capture_scene_view`
|
||||
Render the active Scene View to a PNG. Same result contract and parameter interactions as `capture_game_view`: inline base64, or path-only when `save_path` is set (opt back in with `include_inline_image=true`; `max_resolution` has no effect when `save_path` is set without it).
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `width` | no | `1280` | Output width in px (default 1280; capped 4096). |
|
||||
| `height` | no | `720` | Output height in px (default 720; capped 4096). |
|
||||
| `save_path` | no | `–` | Optional project-relative path to write the PNG (e.g. Screenshots/foo.png). When set, the result omits the inline base64 image unless `include_inline_image=true`. |
|
||||
| `include_inline_image` | no | `false` | Also return the image inline as base64 when `save_path` is set. |
|
||||
| `max_resolution` | no | `0` | Cap on the inline image's longest edge (e.g. 512), preserving aspect ratio. No effect when `save_path` is set without `include_inline_image=true`. The `save_path` file keeps the requested resolution; a downscaled inline copy is reported via `inlineWidth`/`inlineHeight`. |
|
||||
|
||||
**Returns:** `CaptureResult`
|
||||
|
||||
### `capture_editor_element`
|
||||
Capture a UI Toolkit VisualElement (by selector) from an EditorWindow to a PNG; returns path + base64.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `window` | yes | `–` | EditorWindow type name (e.g. InspectorWindow) or window title to capture from. |
|
||||
| `selector` | yes | `–` | Element selector: '#name', '.class', a type name (e.g. Button), descendant (space) / child ('>') chains, optional pseudo-states (:checked,:hover,:focus,:active,:enabled,:disabled,:not(...)). |
|
||||
| `output` | no | `–` | Output PNG path (absolute, or relative to the project root). Defaults to a timestamped file under <project>/Temp/pipeline-screenshots/. |
|
||||
|
||||
**Returns:** `CaptureElementResponse`
|
||||
**Notes:** `MainThreadRequired = true`. Unity 6000.7+ only.
|
||||
|
||||
### `capture_runtime_element`
|
||||
Capture a UI Toolkit VisualElement (by selector) from a live runtime panel (UIDocument or PanelRenderer) to a PNG; returns path + base64.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `panel` | no | `–` | Name of the target panel: matches the PanelSettings asset name or the host GameObject name (UIDocument or PanelRenderer). Optional when exactly one panel exists. |
|
||||
| `selector` | yes | `–` | Element selector: '#name', '.class', a type name (e.g. Button), descendant (space) / child ('>') chains, optional pseudo-states (:checked,:hover,:focus,:active,:enabled,:disabled,:not(...)). |
|
||||
| `output` | no | `–` | Output PNG path (absolute, or relative to Application.persistentDataPath). Defaults to a timestamped file under Application.persistentDataPath. |
|
||||
|
||||
**Returns:** `CaptureElementResponse`
|
||||
**Notes:** `MainThreadRequired = true`, `RuntimeOnly`. Unity 6000.7+ only.
|
||||
|
||||
See [Creating commands](../creating-commands.md) and [Connectivity](../connectivity.md).
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
# Editor lifecycle & observability commands
|
||||
|
||||
Commands to control Editor play mode, focus, and menus, and to observe the Editor's state, console, and performance.
|
||||
|
||||
### `editor_play`
|
||||
Enter Unity Editor play mode.
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `string`
|
||||
|
||||
### `editor_stop`
|
||||
Exit Unity Editor play mode.
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `string`
|
||||
|
||||
### `editor_pause`
|
||||
Toggle pause state of Unity Editor play mode (calling it again while paused resumes play mode).
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `string`
|
||||
|
||||
### `editor_status`
|
||||
Get detailed Unity Editor status and state information.
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `StatusResponse`
|
||||
|
||||
### `editor_focus`
|
||||
Bring the Unity Editor window to the foreground.
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `string`
|
||||
**Notes:** `MainThreadRequired = true`.
|
||||
|
||||
### `menu`
|
||||
Execute an Editor menu item by path, or list available items when no path is given.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `path` | no | `–` | Menu item path to execute, e.g. "Assets/Reimport All". Omit to list available menu items. |
|
||||
|
||||
**Returns:** `MenuResponse`
|
||||
**Notes:** `MainThreadRequired = true`.
|
||||
|
||||
### `screenshot`
|
||||
Capture the Scene or Game view as a PNG and return its file path.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `view` | no | `game` | Which view to capture: 'game' (default) or 'scene' |
|
||||
| `output` | no | `–` | Output PNG path (absolute, or relative to the project root). Defaults to a timestamped file under <project>/Temp/pipeline-screenshots/. |
|
||||
| `width` | no | `0` | Output width in pixels. 0 (default) uses the view camera's current width. |
|
||||
| `height` | no | `0` | Output height in pixels. 0 (default) uses the view camera's current height. |
|
||||
|
||||
**Returns:** `ScreenshotResponse`
|
||||
**Notes:** `MainThreadRequired = true`.
|
||||
|
||||
### `set_autotick`
|
||||
Keep the editor ticking while unfocused by forcing EditorApplication.SignalTick at a throttled rate.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `enable` | no | `true` | Enable (true) or disable (false) auto-tick mode |
|
||||
| `interval_ms` | no | `16` | Minimum milliseconds between forced ticks. 0 = every update (max rate, pegs a CPU core). Default 16 (~60Hz). |
|
||||
| `persist` | no | `true` | Persist this choice to SessionState so it survives a domain reload. Set `false` for a one-off/expensive setting (e.g. `interval_ms=0`) that should revert to the last persisted choice (or the default) after the next recompile instead of sticking for the rest of the session. |
|
||||
|
||||
**Returns:** `string`
|
||||
**Notes:** `MainThreadRequired = true`. Persisted in SessionState by default: your enabled/interval choice survives a domain reload (dies with the editor process/session).
|
||||
|
||||
Use `persist=false` for a change you only want in effect until the next unrelated recompile — e.g. a short profiling run at `interval_ms=0` (pegs a CPU core) or a one-off test/CI script — so it can't outlive its purpose and silently burn CPU for the rest of the session. Leave `persist` at its default `true` for a setting you want to remain in effect across recompiles, such as the normal always-on 16ms tick.
|
||||
|
||||
### `get_performance_stats`
|
||||
Read render, memory, and frame-timing stats (structured, read-only).
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `PerformanceStats`
|
||||
|
||||
### `audit`
|
||||
Run a Project Auditor static-analysis scan. Returns immediately; poll `audit_status` until status is `completed`, then read the CSV.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `categories` | no | `–` | Comma-separated issue categories to scan (e.g. `Code,ProjectSetting,Texture`). Default: all categories. An unknown name is rejected with an error listing the valid values. |
|
||||
| `output` | no | `–` | CSV output path (absolute, or relative to the project root). Defaults to `<project>/Temp/pipeline-audit/<scanId>.csv`. |
|
||||
|
||||
**Returns:** `object` — `{ status, scanId, csvPath }` on start, or `{ status: "unavailable" | "error" | "busy", message }`.
|
||||
**Notes:** Requires Project Auditor in the Editor. Only one scan runs at a time (a second call returns `busy`). Not cancellable: stop polling to abandon a scan.
|
||||
|
||||
### `audit_status`
|
||||
Get the status of the last audit: `idle` | `scanning` | `completed` | `failed` | `interrupted` | `unavailable`.
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `AuditStatus` (JSON) — `issueCount` and `csvPath` are populated when `completed`; `error` when `failed`; `message` when `unavailable`.
|
||||
**Notes:** `MainThreadRequired = false`, so it answers while a scan holds the main thread (Code analysis compiles assemblies). `interrupted` means a domain reload killed an in-flight scan — re-run `audit`.
|
||||
|
||||
#### Project Auditor prerequisites
|
||||
|
||||
`audit` needs both Project Auditor **and** its rules:
|
||||
|
||||
- **Project Auditor absent** — `audit` returns `unavailable` immediately.
|
||||
- **Rules absent** — when Project Auditor ships as a built-in editor module, its rules (descriptors,
|
||||
API/obsolete databases, Roslyn analyzers) come from the separate `com.unity.project-auditor-rules`
|
||||
package. Without it Project Auditor registers no analysis modules and cannot analyze anything, so
|
||||
`audit` returns `scanning` and the first `audit_status` poll reports `unavailable` with a message
|
||||
naming the package to install. It never reports an empty `completed` scan, which would read as a
|
||||
clean project.
|
||||
|
||||
The CSV columns are `Category, Severity, Areas, Description, RelativePath, Line, DescriptorId, Recommendation`;
|
||||
only diagnostics (things to fix) are emitted, not raw inventory rows.
|
||||
|
||||
### `report_evals`
|
||||
Aggregate the local eval-usage telemetry into a ranked report: API fingerprint frequency, one-liner percentage, error rate, and command-coverage suggestions (read-only). See [Eval usage telemetry](#eval-usage-telemetry) below.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `top` | no | `50` | Maximum entries in each ranked list: the fingerprint ranking and the uncovered patterns suggested as commands. Accepted range: 0 or greater; negative values fall back to the default. |
|
||||
|
||||
**Returns:** `EvalUsageReport`
|
||||
**Notes:** `MainThreadRequired = false` (stays servable while the Editor is settling). Reads `<project>/Library/Pipeline/eval-usage.jsonl` merged with the rotated `eval-usage.old.jsonl` (oldest first), so the report covers the full retained history — bounded to roughly 2× the size cap. Returns a zeroed report if no evals have been recorded. The report's `distinctFingerprints` field carries the uncapped total of distinct fingerprints so truncation of the ranked list is always visible; coverage suggestions are computed from the full ranking, not the capped list.
|
||||
|
||||
## Eval usage telemetry
|
||||
|
||||
The `eval`/`eval_file` commands are the workhorse of autonomous agent sessions, but they are opaque: raw C# through the protocol, unauditable, and hard to displace with typed commands without knowing what agents actually run. This telemetry closes that loop by recording, **locally**, what each eval does — so the [eval-displacement epic (AUTHAPI-24)](https://jira.unity3d.com/browse/AUTHAPI-24) can be triaged from data instead of anecdotes.
|
||||
|
||||
### What is recorded, and where
|
||||
|
||||
Every `eval`/`eval_file` invocation appends one JSON object (one line, JSONL) to `<project>/Library/Pipeline/eval-usage.jsonl`:
|
||||
|
||||
| Field | Meaning |
|
||||
|-------|---------|
|
||||
| `time` | ISO-8601 UTC timestamp. |
|
||||
| `command` | `eval` or `eval_file`. |
|
||||
| `success` / `error` | Whether it succeeded; short error category on failure. |
|
||||
| `payloadLength` / `lineCount` | Size/shape of the eval body. |
|
||||
| `executionTimeMs` | How long it took. |
|
||||
| `classification` | `single-expression` (a one-liner: one invoke / read / assignment) or `statements` (multi-statement bodies, e.g. polling loops, bulk setup). |
|
||||
| `fingerprints` | The top-level member-access paths from the syntax tree — e.g. `AssetDatabase.Refresh`, `PlayerSettings.SetScriptingBackend`, `Object.FindFirstObjectByType<T>`, `MatchManager.Instance.State`. Rendering is a strict whitelist: the only source text that can appear in a fingerprint is an identifier, type name, or member name. Literals, interpolated strings, and any other expression form are replaced by the neutral `<expr>` placeholder (`"…".Length` records as `<expr>.Length`); object creations record as `new TypeName` (constructor arguments never appear); casts unwrap to the inner expression; generic type arguments normalise to `<T>`. A fingerprint that would be nothing but the placeholder is dropped. |
|
||||
| `source` | The raw eval source — **absent by default**; present only when the opt-in below is enabled, and truncated at 64 K characters with an explicit `…[truncated]` marker (`payloadLength` still reports the full length). |
|
||||
|
||||
**Local-first, privacy-first, editor-only.** Nothing is transmitted off the machine, and nothing is recorded outside the Editor: player builds — including development players, where eval itself is available — never write telemetry (recording compiles to a no-op there). The fingerprint is derived from the C# syntax tree, so the log captures *which APIs* agents reach for without capturing *what data* they operate on. Conditional-access chains (`a?.B.C`) are fingerprinted with `?.` normalised to `.` so they rank with their unconditional form. The fingerprint deliberately undercounts a few forms (arguments nested inside a chain's receiver call, bare object creations) — see the `EvalUsageFingerprinter` class doc for the full blind-spot list before treating absence as proof of non-use.
|
||||
|
||||
**Zero eval latency.** Recording is fire-and-forget: when an eval completes, the source and outcome plus the current settings are snapshotted, and the parse → fingerprint → append runs on a background thread from that snapshot — the eval response is never delayed by telemetry. The JSONL is bounded by a size cap and rotates to `eval-usage.old.jsonl`; the rotated file is merged back into `report_evals`, so retained history is bounded to roughly 2× the cap.
|
||||
|
||||
### Settings
|
||||
|
||||
Configured on the `EditorPipelineManager` settings asset (`Window/Pipeline/Settings...`). Edits apply live from the inspector, and the stored values are re-applied on every server start:
|
||||
|
||||
- **Eval Telemetry Enabled** (default on) — master switch. When off, nothing is recorded (and, as always, nothing is transmitted).
|
||||
- **Store Eval Source** (default off) — the explicit opt-in to also persist the raw eval `source` in each record, for local debugging only.
|
||||
|
||||
### The feedback loop: report → backlog triage
|
||||
|
||||
Run `report_evals` to aggregate the log into an `EvalUsageReport`:
|
||||
|
||||
- **Ranked fingerprints** with counts — what agents actually eval, most-used first.
|
||||
- **One-liner percentage** and **error rate** — the shape of eval usage (the evidence base for AUTHAPI-24 is that ~80% of eval calls are one-liners).
|
||||
- **Coverage suggestions** — each fingerprint is cross-referenced against the live command catalog:
|
||||
- `covered`: patterns already served by an existing command (e.g. `PlayerSettings.SetScriptingBackend` → `set_player_settings`) — these are habit, and should move off eval.
|
||||
- `topUncovered`: the highest-frequency patterns with no covering command (e.g. `AssetDatabase.Refresh` → proposed `refresh_assets`) — these are the next typed commands to build ([AUTHAPI-17](https://jira.unity3d.com/browse/AUTHAPI-17)).
|
||||
|
||||
This turns backlog triage into a lookup: "12× `AssetDatabase.Refresh`, no command → build it; 60× state-read loops → `wait_for`". Re-run across sessions to demonstrate eval's shrinking share as typed commands land.
|
||||
|
||||
### `get_authoring_root`
|
||||
Get the base folder (under Assets/) that bare authoring paths resolve against.
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `object`
|
||||
|
||||
### `set_authoring_root`
|
||||
Set the base folder (under Assets/) that bare authoring paths resolve against and are confined to. Use 'Assets' for full project access.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `root` | yes | `–` | Project-relative folder under Assets/, e.g. Assets/AgentWork. Use 'Assets' to allow the whole project. |
|
||||
|
||||
**Returns:** `object`
|
||||
|
||||
See [Creating commands](../creating-commands.md) and [Connectivity](../connectivity.md).
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
# GameObject & component commands
|
||||
|
||||
Commands for creating GameObjects, querying the scene hierarchy, mutating the core GameObject/Transform surface, and adding/removing components and editing their serialized properties. Scene mutations are wrapped in Unity's Undo system (so a multi-step action reverts as one collapsible step) and are blocked during Play mode. Objects are referenced by an `ObjectRef` handle (globalId/path/guid/instanceId/hierarchyPath), and results come back as `AuthoringResult` identities you can chain into a follow-up call.
|
||||
|
||||
### `create_gameobject`
|
||||
Create an empty GameObject or a built-in primitive (cube/sphere/capsule/cylinder/plane/quad) in the active scene.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `name` | no | `–` | Name for the new GameObject. Defaults to 'GameObject' (or the primitive name). |
|
||||
| `primitive` | no | `–` | Optional primitive type: cube, sphere, capsule, cylinder, plane, quad. Omit for an empty GameObject. |
|
||||
| `parent` | no | `–` | Optional parent handle (globalId/path/guid/instanceId/hierarchyPath). The new object becomes a child of it. |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
**Notes:** Undo-able; blocked during Play mode.
|
||||
|
||||
### `create_gameobjects`
|
||||
Batch-create N empty GameObjects or primitives in one call. Optional positions/rotations/scales are arrays of [x,y,z] (length must equal count). Returns the created identities.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `name` | no | `–` | Base name. With count>1 and no explicit names, objects are suffixed Name1..NameN. |
|
||||
| `primitive` | no | `–` | Optional primitive type: cube, sphere, capsule, cylinder, plane, quad. Omit for empty GameObjects. |
|
||||
| `parent` | no | `–` | Optional parent handle. Every created object becomes a child of it. |
|
||||
| `count` | no | `1` | How many GameObjects to create. Default 1. |
|
||||
| `positions` | no | `–` | Local positions, one [x,y,z] per object. Length must equal count when supplied. |
|
||||
| `rotations` | no | `–` | Local Euler rotations (degrees), one [x,y,z] per object. Length must equal count when supplied. |
|
||||
| `scales` | no | `–` | Local scales, one [x,y,z] per object. Length must equal count when supplied. |
|
||||
|
||||
**Returns:** `CreateGameObjectsResult`
|
||||
**Notes:** Undo-able (the whole batch reverts as one step); blocked during Play mode.
|
||||
|
||||
### `find_gameobjects`
|
||||
Find GameObjects in loaded scenes by name, tag, component type, and/or hierarchy path (filters are combined). Returns structured identities.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `name` | no | `–` | Exact name to match. |
|
||||
| `tag` | no | `–` | Tag to match (e.g. 'Player'). |
|
||||
| `type` | no | `–` | Component type name to match (e.g. 'Rigidbody', 'UnityEngine.Camera'). |
|
||||
| `hierarchy_path` | no | `–` | Exact hierarchy path to match (e.g. '/Root/Child'). |
|
||||
| `include_inactive` | no | `true` | Include inactive GameObjects. Default true. |
|
||||
|
||||
**Returns:** `FindGameObjectsResult`
|
||||
|
||||
### `set_transform`
|
||||
Set a GameObject's local position/rotation(euler)/scale. Omitted channels are left unchanged.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `target` | yes | – | Handle of the GameObject to modify. |
|
||||
| `position` | no | `–` | Local position as [x,y,z]. |
|
||||
| `rotation` | no | `–` | Local rotation as Euler angles [x,y,z] in degrees. |
|
||||
| `scale` | no | `–` | Local scale as [x,y,z]. |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
**Notes:** Undo-able; blocked during Play mode.
|
||||
|
||||
### `set_parent`
|
||||
Reparent a GameObject under a new parent, or detach it to scene root when no parent is given.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `target` | yes | – | Handle of the GameObject to reparent. |
|
||||
| `parent` | no | `–` | Handle of the new parent. Omit (or empty) to move the object to the scene root. |
|
||||
| `world_position_stays` | no | `true` | Keep the object's world position when reparenting. Default true. |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
**Notes:** Undo-able; blocked during Play mode.
|
||||
|
||||
### `set_active`
|
||||
Set a GameObject's active self-state (activeSelf).
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `target` | yes | – | Handle of the GameObject. |
|
||||
| `active` | yes | – | Desired active state. |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
**Notes:** Undo-able; blocked during Play mode.
|
||||
|
||||
### `set_tag`
|
||||
Set a GameObject's tag (the tag must already exist in the project).
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `target` | yes | – | Handle of the GameObject. |
|
||||
| `tag` | yes | – | Tag to assign (must exist in the Tag Manager). |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
**Notes:** Undo-able; blocked during Play mode.
|
||||
|
||||
### `set_layer`
|
||||
Set a GameObject's layer by name or numeric index (0-31).
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `target` | yes | – | Handle of the GameObject. |
|
||||
| `layer` | yes | – | Layer name (e.g. 'UI') or numeric index 0-31. |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
**Notes:** Undo-able; blocked during Play mode.
|
||||
|
||||
### `rename_gameobject`
|
||||
Rename a GameObject.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `target` | yes | – | Handle of the GameObject. |
|
||||
| `name` | yes | – | New name. |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
**Notes:** Undo-able; blocked during Play mode.
|
||||
|
||||
### `delete_gameobject`
|
||||
Delete a GameObject from the scene (reversible via Undo).
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `target` | yes | – | Handle of the GameObject to delete. |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
**Notes:** Undo-able; blocked during Play mode. The result describes the object's identity captured before destruction.
|
||||
|
||||
### `add_component`
|
||||
Add a component (by type name) to a GameObject.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `target` | yes | – | Handle of the GameObject. |
|
||||
| `type` | yes | – | Component type name (e.g. 'Rigidbody' or 'UnityEngine.Camera'). |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
**Notes:** Undo-able; blocked during Play mode.
|
||||
|
||||
### `remove_component`
|
||||
Remove a component from a GameObject. Provide either a component handle (target) or a GameObject handle (target) plus a type name.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `target` | yes | – | Handle of the component to remove, OR of the GameObject when 'type' is given. |
|
||||
| `type` | no | `–` | Component type name to remove from the target GameObject (omit when 'target' already points at a component). |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
**Notes:** Undo-able; blocked during Play mode.
|
||||
|
||||
### `get_component_properties`
|
||||
Get a component's serialized properties as a JSON map. Address the component by handle, or by GameObject handle + type.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `target` | yes | – | Handle of the component, OR of the GameObject when 'type' is given. |
|
||||
| `type` | no | `–` | Component type name on the target GameObject (omit when 'target' is a component handle). |
|
||||
|
||||
**Returns:** `ComponentPropertiesResult`
|
||||
|
||||
### `set_component_properties`
|
||||
Set serialized properties on a component (one Undo step). 'properties' maps property name -> value; object references accept an ObjectRef handle.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `target` | yes | – | Handle of the component, OR of the GameObject when 'type' is given. |
|
||||
| `properties` | yes | – | Map of serialized property name to value. Vectors/colors are arrays; object refs are handle objects. |
|
||||
| `type` | no | `–` | Component type name on the target GameObject (omit when 'target' is a component handle). |
|
||||
|
||||
**Returns:** `ComponentPropertiesResult`
|
||||
**Notes:** Undo-able (one Undo step); blocked during Play mode. An unknown property name fails the whole batch (no partial apply).
|
||||
|
||||
See [Creating commands](../creating-commands.md) and [Connectivity](../connectivity.md).
|
||||
@@ -0,0 +1,55 @@
|
||||
# Material & shader commands
|
||||
|
||||
Read and edit material shader properties, and discover/introspect shaders so an agent can pick a valid shader name and property set.
|
||||
|
||||
## Materials
|
||||
|
||||
### `get_material_properties`
|
||||
Read a material's shader, render queue, enabled keywords, and all shader properties with their current values (Color as [r,g,b,a], Vector as [x,y,z,w], Texture as an object reference).
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `material` | yes | `–` | Reference to the .mat asset (or a loaded material) to read (path / guid / globalId / instanceId). |
|
||||
|
||||
**Returns:** `MaterialPropertiesResult`
|
||||
|
||||
### `set_material_properties`
|
||||
Set shader properties on a material (Float/Range/Int=number; Color=[r,g,b,a] or "#RRGGBBAA" hex; Vector=[x,y,z,w]; Texture=an object reference or null to clear), optionally reassign the shader, set the render queue, and toggle keywords. Unknown names / type mismatches are reported in `unknown[]`.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `material` | yes | `–` | Reference to the .mat asset (or a loaded material) to edit (path / guid / globalId / instanceId). |
|
||||
| `shader` | no | `–` | Reassign the material's shader by name (e.g. "Standard", "Universal Render Pipeline/Lit", or a Shader Graph shader name). Applied before properties so new property names resolve against the new shader. |
|
||||
| `properties` | no | `–` | JSON object of shader property name -> value. Names must include the leading underscore (e.g. _BaseColor). Float/Range/Int=number; Color=[r,g,b,a] or hex string; Vector=[x,y,z,w]; Texture=an object reference {guid/path} or null. |
|
||||
| `renderQueue` | no | `–` | Explicit render queue, or -1 to inherit from the shader. Omit to leave unchanged. |
|
||||
| `enableKeywords` | no | `–` | Shader keywords to enable (e.g. _NORMALMAP, _EMISSION). |
|
||||
| `disableKeywords` | no | `–` | Shader keywords to disable. |
|
||||
| `confirm` | no | `false` | Reserved for parity; editing an existing material is non-destructive and undoable, so it is not required. |
|
||||
| `dry_run` | no | `false` | If true, validate the shader, resolve property names and texture refs, and report applied[]/unknown[] without writing anything. |
|
||||
|
||||
**Returns:** `SetMaterialPropertiesResult`
|
||||
|
||||
## Shaders
|
||||
|
||||
### `list_shaders`
|
||||
Discover available shaders so an agent can pick a valid name for set_material_properties / create_asset. Returns `[{ name, assetPath, isBuiltin, isSupported }]` — `assetPath` is omitted for built-in shaders in the default lean reply (pass `verbose: true` on the request for an explicit `assetPath: null`); use `isBuiltin` to classify.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `filter` | no | `–` | Case-insensitive substring matched against the shader name (e.g. "URP", "Lit"). |
|
||||
| `includeBuiltin` | no | `true` | Include built-in/engine shaders (those with no project asset path). |
|
||||
| `limit` | no | `200` | Maximum number of shaders to return. |
|
||||
|
||||
**Returns:** `object` (list of `ShaderInfo`)
|
||||
|
||||
### `get_shader_properties`
|
||||
Introspect a shader's declared property list (name, description, type Color|Vector|Float|Range|TexEnv|Int, range, textureDimension, flags). Provide `shader` (by name) OR `material` (read the shader off that material).
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `shader` | no | `–` | Shader name (e.g. "Universal Render Pipeline/Lit"). Provide this OR 'material'. |
|
||||
| `material` | no | `–` | Reference to a material to read the shader from instead of naming it. Provide this OR 'shader'. |
|
||||
|
||||
**Returns:** `ShaderPropertiesResult`
|
||||
|
||||
See [Creating commands](../creating-commands.md) and [Connectivity](../connectivity.md).
|
||||
@@ -0,0 +1,32 @@
|
||||
# Navigation & selection commands
|
||||
|
||||
Commands to read and drive the Editor selection, and to run Unity Search queries. All commands are read-only or non-destructive and run on the main thread.
|
||||
|
||||
### `get_selection`
|
||||
Read the current Editor selection as structured object identities.
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `SelectionResult`
|
||||
|
||||
### `set_selection`
|
||||
Set the Editor selection to the given assets/scene objects.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `instance_ids` | no | `–` | Scene/loaded object instance IDs to select. |
|
||||
| `paths` | no | `–` | Asset paths to select (e.g. Assets/Foo.prefab). |
|
||||
|
||||
**Returns:** `SelectionResult`
|
||||
|
||||
### `search`
|
||||
Run a Unity Search query and return structured results.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `query` | yes | `–` | Unity Search query string, e.g. 't:Material', 'p: my asset', 'h: Main Camera'. |
|
||||
| `limit` | no | `50` | Max results to return (capped 200). |
|
||||
|
||||
**Returns:** `SearchResult`
|
||||
|
||||
See [Creating commands](../creating-commands.md) and [Connectivity](../connectivity.md).
|
||||
@@ -0,0 +1,70 @@
|
||||
# Package Manager commands
|
||||
|
||||
These commands query and modify the project's UPM package set (list, search, add, remove, resolve, status) over `UnityEditor.PackageManager`. Read-only queries block until the registry responds and return synchronously; mutating commands (`package_add`/`package_remove`) require `confirm=true`, support `dry_run` previews, and are async by default (poll `package_status`) with an optional `wait=true` to block until done.
|
||||
|
||||
### `package_list`
|
||||
List packages by scope: installed (default) | available (registry) | all (both). Returns the full result synchronously — available/all block until the registry query completes.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `scope` | no | `installed` | Which packages to list: installed (default) | available | all. |
|
||||
| `include_indirect` | no | `true` | Include indirect (transitive) installed dependencies (applies to scope=installed/all). |
|
||||
| `offline` | no | `false` | For available/all: query the local cache instead of the registry. |
|
||||
|
||||
**Returns:** `object`
|
||||
**Notes:** `MainThreadRequired = false`. Synchronous — `available`/`all` start a registry query and block until it completes (up to a 25s timeout); `installed` reads the resolved set inline on the main thread.
|
||||
|
||||
### `package_search`
|
||||
Search packages available in the registry. Provide a name (e.g. com.unity.foo) or omit to list all. Returns the full result synchronously (blocks until the registry query completes).
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `query` | no | `–` | Package name to search for. Omit/empty to list all available packages. |
|
||||
| `offline` | no | `false` | Search the local cache only. |
|
||||
|
||||
**Returns:** `object`
|
||||
**Notes:** `MainThreadRequired = false`. Synchronous — blocks until the registry query completes (up to a 25s timeout). Each result is flagged with whether it is currently installed.
|
||||
|
||||
### `package_add`
|
||||
Add a UPM package by name@version, git URL, or 'file:' local path. Async by default (returns in_progress; poll package_status); pass wait=true to block until added. A recompile/domain reload follows — poll recompile_status. Requires confirm=true; use dry_run to preview.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `identifier` | yes | `–` | Package to add: 'com.unity.foo@1.2.3', a git URL, or 'file:../Path'. |
|
||||
| `confirm` | no | `false` | Apply the change. Without it the call is refused. |
|
||||
| `dry_run` | no | `false` | Preview the change without applying it. |
|
||||
| `wait` | no | `false` | Block until the operation completes and return the result (synchronous). Default: return immediately and poll package_status. |
|
||||
|
||||
**Returns:** `object`
|
||||
**Notes:** `MainThreadRequired = false`. Mutating — requires `confirm=true`; `dry_run=true` returns a preview without applying. Async by default (returns `in_progress`; poll `package_status`); with `wait=true` it blocks until completion (up to a 25s timeout). A successful add triggers a recompile/domain reload — poll `recompile_status`. Returns `busy` if another package operation is already in progress.
|
||||
|
||||
### `package_remove`
|
||||
Remove a UPM package by name. Async by default (returns in_progress; poll package_status); pass wait=true to block until removed. A recompile/domain reload follows — poll recompile_status. Requires confirm=true; use dry_run to preview.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `name` | yes | `–` | Package name to remove (e.g. com.unity.foo). |
|
||||
| `confirm` | no | `false` | Apply the change. Without it the call is refused. |
|
||||
| `dry_run` | no | `false` | Preview the change without applying it. |
|
||||
| `wait` | no | `false` | Block until the operation completes and return the result (synchronous). Default: return immediately and poll package_status. |
|
||||
|
||||
**Returns:** `object`
|
||||
**Notes:** `MainThreadRequired = false`. Mutating — requires `confirm=true`; `dry_run=true` returns a preview without applying. Async by default (returns `in_progress`; poll `package_status`); with `wait=true` it blocks until completion (up to a 25s timeout). A successful remove triggers a recompile/domain reload — poll `recompile_status`; the reload can drop a synchronous reply, so `package_status` confirms the outcome. Returns `busy` if another package operation is already in progress.
|
||||
|
||||
### `package_resolve`
|
||||
Resolve/refresh packages from the manifest (re-fetch and re-link). May trigger a recompile/domain reload — poll recompile_status. Its outcome is recorded for package_status.
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `object`
|
||||
**Notes:** `MainThreadRequired = true`. Fire-and-forget — `Client.Resolve()` schedules a resolve on a later tick, so the response returns before any reload. May trigger a recompile/domain reload — poll `recompile_status`. Its outcome is recorded in the status file, so `package_status` validates it.
|
||||
|
||||
### `package_status`
|
||||
Status of the last async package operation (add/remove/resolve): idle | in_progress | completed | failed, with the added package, manifest, and any error.
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `string`
|
||||
**Notes:** `MainThreadRequired = false`. Reads a Temp status file that survives the domain reload an add/remove/resolve triggers, so a lost synchronous reply or an interrupted async op is recoverable by polling. Returns `{"status":"idle"}` when no operation has run.
|
||||
|
||||
See [Creating commands](../creating-commands.md) and [Connectivity](../connectivity.md).
|
||||
@@ -0,0 +1,84 @@
|
||||
# Prefab commands
|
||||
|
||||
Commands covering the prefab lifecycle an agent needs to build content procedurally: saving a configured GameObject as a prefab asset, instantiating it into a scene, deriving a variant, applying/reverting instance overrides, unpacking, and editing prefab contents through the prefab stage. Asset paths are sandboxed to the authoring root; scene-side mutations are Undo-able, but prefab asset writes are AssetDatabase operations and are not part of Unity's Undo system.
|
||||
|
||||
### `create_prefab`
|
||||
Save a GameObject as a prefab asset at a project path; the source becomes a connected instance.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `source` | yes | – | Reference to the source GameObject to save as a prefab (globalId/path/guid/instanceId/hierarchyPath). |
|
||||
| `path` | yes | – | Prefab asset path relative to the authoring root (the Assets/ prefix is optional and the .prefab extension is added if missing). e.g. Prefabs/Enemy or Prefabs/Enemy.prefab |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
**Notes:** Scene-side effects are Undo-able; the prefab asset write is not undoable.
|
||||
|
||||
### `instantiate_prefab`
|
||||
Instantiate a prefab asset into a loaded scene and return the created instance.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `prefab` | yes | – | Reference to the prefab asset to instantiate (path/guid/globalId). |
|
||||
| `scene_path` | no | `–` | Optional path of a loaded scene to instantiate into; defaults to the active scene. |
|
||||
| `name` | no | `–` | Optional name for the created instance; defaults to the prefab name. |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
**Notes:** Undo-able.
|
||||
|
||||
### `create_prefab_variant`
|
||||
Create a prefab variant asset that inherits from a base prefab.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `base` | yes | – | Reference to the base prefab asset (path/guid/globalId). |
|
||||
| `path` | yes | – | Variant prefab asset path relative to the authoring root (.prefab added if missing). |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
**Notes:** Prefab asset write is not undoable.
|
||||
|
||||
### `apply_prefab_overrides`
|
||||
Apply a prefab instance's overrides back to its source prefab asset.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `instance` | yes | – | Reference to a prefab instance GameObject in a scene (instanceId/hierarchyPath/globalId). |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
**Notes:** Scene-side effects are Undo-able; the asset write is not. The instance's source prefab asset must live under the authoring root.
|
||||
|
||||
### `revert_prefab_overrides`
|
||||
Revert a prefab instance's overrides so it matches its source prefab asset.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `instance` | yes | – | Reference to a prefab instance GameObject in a scene (instanceId/hierarchyPath/globalId). |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
**Notes:** Undo-able.
|
||||
|
||||
### `unpack_prefab`
|
||||
Unpack a prefab instance into plain GameObjects (outermost level or completely).
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `instance` | yes | – | Reference to a prefab instance GameObject in a scene (instanceId/hierarchyPath/globalId). |
|
||||
| `completely` | no | `false` | If true, unpack all nested prefab levels (Completely); if false, only the outermost level (OutermostRoot). |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
**Notes:** Undo-able.
|
||||
|
||||
### `save_prefab_contents`
|
||||
Open a prefab asset in an isolated prefab stage, apply a declarative edit, and save it back (nested-prefab safe).
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `prefab` | yes | – | Reference to the prefab asset to edit (path/guid/globalId). |
|
||||
| `rename_child` | no | `–` | Optional child name (relative path under the root, e.g. 'Body/Head') to rename. |
|
||||
| `new_name` | no | `–` | New name for the child identified by rename_child. |
|
||||
| `set_active_child` | no | `–` | Optional child name (relative path under the root) whose active state to set. |
|
||||
| `active` | no | `true` | Active state to apply when set_active_child is provided. |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
**Notes:** Prefab asset write (prefab-stage save) is not part of Unity's Undo system. The prefab asset must live under the authoring root.
|
||||
|
||||
See [Creating commands](../creating-commands.md) and [Connectivity](../connectivity.md).
|
||||
@@ -0,0 +1,270 @@
|
||||
# Project settings commands
|
||||
|
||||
Read and change a representative slice of Unity's project-wide settings (Audio, Graphics, Input, Physics, Player, Quality, Runtime Pipeline, Tags & Layers, Time). `get_*` commands read a group's current values into a `ProjectSettingsResponse`; `set_*` commands mutate them through the shared `confirm`/`dry_run` convention, so `confirm=true` is required to apply and `dry_run=true` previews the change without applying it. Every command in this group is `MainThreadRequired = true`. On a `set_*`, omitted fields are left unchanged, and the group is re-read after the write so the response reflects the resulting state. Settings writes are not undoable via Ctrl+Z.
|
||||
|
||||
### `get_audio_settings`
|
||||
Read project Audio settings (volume, rolloff scale, doppler factor).
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `ProjectSettingsResponse`
|
||||
**Notes:** `MainThreadRequired = true`.
|
||||
|
||||
### `set_audio_settings`
|
||||
Change project Audio settings. Requires confirm=true; use dry_run to preview.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `settings` | no | `–` | Fields to change; omitted fields are left unchanged (fields below). |
|
||||
| `confirm` | no | `false` | Apply the change. Without it the call is refused. |
|
||||
| `dry_run` | no | `false` | Preview the change without applying it. |
|
||||
|
||||
`settings` fields:
|
||||
|
||||
| Field | Required | Default | Description |
|
||||
|-------|----------|---------|-------------|
|
||||
| `volume` | no | `–` | Global audio volume (0..1). |
|
||||
| `rolloffScale` | no | `–` | Global rolloff scale. |
|
||||
| `dopplerFactor` | no | `–` | Global doppler factor. |
|
||||
|
||||
**Returns:** `ProjectSettingsResponse`
|
||||
**Notes:** `MainThreadRequired = true`. `confirm=true` required to apply; supports `dry_run`.
|
||||
|
||||
### `get_graphics_settings`
|
||||
Read GraphicsSettings (default render pipeline).
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `ProjectSettingsResponse`
|
||||
**Notes:** `MainThreadRequired = true`.
|
||||
|
||||
### `set_graphics_settings`
|
||||
Set the default render pipeline asset. Requires confirm=true; use dry_run to preview.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `settings` | no | `–` | Fields to change; omitted fields are left unchanged (fields below). |
|
||||
| `confirm` | no | `false` | Apply the change. Without it the call is refused. |
|
||||
| `dry_run` | no | `false` | Preview the change without applying it. |
|
||||
|
||||
`settings` fields:
|
||||
|
||||
| Field | Required | Default | Description |
|
||||
|-------|----------|---------|-------------|
|
||||
| `renderPipelineAsset` | no | `–` | Reference (path / guid / globalId) to a RenderPipelineAsset to set as the default. Pass an empty reference (`{}`) to select the built-in pipeline; omit to leave unchanged. |
|
||||
|
||||
**Returns:** `ProjectSettingsResponse`
|
||||
**Notes:** `MainThreadRequired = true`. `confirm=true` required to apply; supports `dry_run`.
|
||||
|
||||
### `get_input_settings`
|
||||
Read the legacy Input Manager axes (names and count).
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `ProjectSettingsResponse`
|
||||
**Notes:** `MainThreadRequired = true`.
|
||||
|
||||
### `set_input_settings`
|
||||
Tune a legacy Input Manager axis (sensitivity/gravity/dead) by name. Requires confirm=true; use dry_run to preview.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `settings` | no | `–` | Axis change. 'axis' selects the axis by name; omitted numeric fields are left unchanged (fields below). |
|
||||
| `confirm` | no | `false` | Apply the change. Without it the call is refused. |
|
||||
| `dry_run` | no | `false` | Preview the change without applying it. |
|
||||
|
||||
`settings` fields:
|
||||
|
||||
| Field | Required | Default | Description |
|
||||
|-------|----------|---------|-------------|
|
||||
| `axis` | yes | – | Name of the axis to modify (e.g. 'Horizontal'). |
|
||||
| `sensitivity` | no | `–` | New sensitivity. |
|
||||
| `gravity` | no | `–` | New gravity. |
|
||||
| `dead` | no | `–` | New dead-zone size. |
|
||||
|
||||
**Returns:** `ProjectSettingsResponse`
|
||||
**Notes:** `MainThreadRequired = true`. `confirm=true` required to apply; supports `dry_run`.
|
||||
|
||||
### `get_physics_settings`
|
||||
Read Physics settings (gravity, solver iterations, bounce threshold).
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `ProjectSettingsResponse`
|
||||
**Notes:** `MainThreadRequired = true`.
|
||||
|
||||
### `set_physics_settings`
|
||||
Change Physics settings. Requires confirm=true; use dry_run to preview.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `settings` | no | `–` | Fields to change; omitted fields are left unchanged (fields below). |
|
||||
| `confirm` | no | `false` | Apply the change. Without it the call is refused. |
|
||||
| `dry_run` | no | `false` | Preview the change without applying it. |
|
||||
|
||||
`settings` fields:
|
||||
|
||||
| Field | Required | Default | Description |
|
||||
|-------|----------|---------|-------------|
|
||||
| `gravityX` | no | `–` | Gravity X component. |
|
||||
| `gravityY` | no | `–` | Gravity Y component (e.g. -9.81). |
|
||||
| `gravityZ` | no | `–` | Gravity Z component. |
|
||||
| `defaultSolverIterations` | no | `–` | Default solver iteration count. |
|
||||
| `bounceThreshold` | no | `–` | Bounce threshold velocity. |
|
||||
|
||||
**Returns:** `ProjectSettingsResponse`
|
||||
**Notes:** `MainThreadRequired = true`. `confirm=true` required to apply; supports `dry_run`.
|
||||
|
||||
### `get_player_settings`
|
||||
Read PlayerSettings (company/product/version, scripting backend, API level).
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `ProjectSettingsResponse`
|
||||
**Notes:** `MainThreadRequired = true`.
|
||||
|
||||
### `set_player_settings`
|
||||
Change PlayerSettings. Requires confirm=true; use dry_run to preview. Scripting backend / API level changes trigger a domain reload.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `settings` | no | `–` | Fields to change; omitted fields are left unchanged (fields below). |
|
||||
| `confirm` | no | `false` | Apply the change. Without it the call is refused. |
|
||||
| `dry_run` | no | `false` | Preview the change without applying it. |
|
||||
|
||||
`settings` fields:
|
||||
|
||||
| Field | Required | Default | Description |
|
||||
|-------|----------|---------|-------------|
|
||||
| `companyName` | no | `–` | Company name. |
|
||||
| `productName` | no | `–` | Product name. |
|
||||
| `bundleVersion` | no | `–` | Bundle/application version string. |
|
||||
| `scriptingBackend` | no | `–` | Scripting backend (e.g. Mono2x, IL2CPP). Triggers a domain reload. |
|
||||
| `apiCompatibilityLevel` | no | `–` | API compatibility level (e.g. NET_Standard_2_0, NET_Unity_4_8). Triggers a domain reload. |
|
||||
|
||||
**Returns:** `ProjectSettingsResponse`
|
||||
**Notes:** `MainThreadRequired = true`. `confirm=true` required to apply; supports `dry_run`. Changing `scriptingBackend` or `apiCompatibilityLevel` triggers a domain reload (`requiresDomainReload`); scripting-backend / API-level changes are read and written against the active build target.
|
||||
|
||||
### `get_quality_settings`
|
||||
Read QualitySettings (current level, level names, vSync, anti-aliasing).
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `ProjectSettingsResponse`
|
||||
**Notes:** `MainThreadRequired = true`.
|
||||
|
||||
### `set_quality_settings`
|
||||
Change QualitySettings. Requires confirm=true; use dry_run to preview.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `settings` | no | `–` | Fields to change; omitted fields are left unchanged (fields below). |
|
||||
| `confirm` | no | `false` | Apply the change. Without it the call is refused. |
|
||||
| `dry_run` | no | `false` | Preview the change without applying it. |
|
||||
|
||||
`settings` fields:
|
||||
|
||||
| Field | Required | Default | Description |
|
||||
|-------|----------|---------|-------------|
|
||||
| `level` | no | `–` | Quality level index (see levelNames from get_quality_settings). |
|
||||
| `vSyncCount` | no | `–` | VSync count (0 = off, 1, 2). |
|
||||
| `antiAliasing` | no | `–` | MSAA sample count (0, 2, 4, 8). |
|
||||
|
||||
**Returns:** `ProjectSettingsResponse`
|
||||
**Notes:** `MainThreadRequired = true`. `confirm=true` required to apply; supports `dry_run`.
|
||||
|
||||
### `get_runtime_pipeline_settings`
|
||||
Read Pipeline Runtime settings (enableInBuilds, port, requestTimeoutMs, enableAuditLogging, autoStart, maxWorkItemsPerFrame). Reports the settings in effect, falling back to built-in defaults when none have been authored; never creates the settings file.
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `ProjectSettingsResponse`
|
||||
**Notes:** `MainThreadRequired = true`.
|
||||
|
||||
### `set_runtime_pipeline_settings`
|
||||
Change Pipeline Runtime settings. Requires confirm=true; use dry_run to preview.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `settings` | no | `–` | Fields to change; omitted fields are left unchanged (fields below). |
|
||||
| `confirm` | no | `false` | Apply the change. Without it the call is refused. |
|
||||
| `dry_run` | no | `false` | Preview the change without applying it. |
|
||||
|
||||
`settings` fields:
|
||||
|
||||
| Field | Required | Default | Description |
|
||||
|-------|----------|---------|-------------|
|
||||
| `enableInBuilds` | no | `–` | Enable the Pipeline HTTP server in Player builds. SECURITY WARNING: only enable in development/QA builds. |
|
||||
| `port` | no | `–` | HTTP port for the Pipeline server. 0 = auto-assign from range 7900-7999. |
|
||||
| `requestTimeoutMs` | no | `–` | Request timeout in milliseconds. |
|
||||
| `enableAuditLogging` | no | `–` | Log every remote request for security auditing. |
|
||||
| `autoStart` | no | `–` | Start the server automatically when the Player boots (or Play Mode is entered). |
|
||||
| `maxWorkItemsPerFrame` | no | `–` | Maximum work items the dispatcher processes per frame. |
|
||||
|
||||
**Returns:** `ProjectSettingsResponse`
|
||||
**Notes:** `MainThreadRequired = true`. `confirm=true` required to apply; supports `dry_run`. Refused while in Play Mode — the running driver already loaded its own settings, so a change would silently not apply until the next Play session or build. `port` (0-65535), `requestTimeoutMs` (1000-60000), and `maxWorkItemsPerFrame` (1-50) are rejected outright with a `Fail` response if outside these bounds — the same ones the Project Settings page enforces — rather than being clamped or silently applied.
|
||||
|
||||
### `get_tags_layers`
|
||||
Read the project's tags and (named) layers.
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `ProjectSettingsResponse`
|
||||
**Notes:** `MainThreadRequired = true`.
|
||||
|
||||
### `set_tags_layers`
|
||||
Add/remove tags and assign user layer names (index 8-31). Requires confirm=true; use dry_run to preview.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `settings` | no | `–` | Tag/layer changes to make (fields below). |
|
||||
| `confirm` | no | `false` | Apply the change. Without it the call is refused. |
|
||||
| `dry_run` | no | `false` | Preview the change without applying it. |
|
||||
|
||||
`settings` fields:
|
||||
|
||||
| Field | Required | Default | Description |
|
||||
|-------|----------|---------|-------------|
|
||||
| `addTags` | no | `–` | Tag names to add. |
|
||||
| `removeTags` | no | `–` | Tag names to remove. |
|
||||
| `setLayers` | no | `–` | User layer assignments (index 8-31); array of layer-assignment objects (fields below). |
|
||||
|
||||
`setLayers[]` (layer-assignment) fields:
|
||||
|
||||
| Field | Required | Default | Description |
|
||||
|-------|----------|---------|-------------|
|
||||
| `index` | yes | – | Layer index (8-31 for user layers). |
|
||||
| `name` | yes | – | Layer name (empty string clears the slot). |
|
||||
|
||||
**Returns:** `ProjectSettingsResponse`
|
||||
**Notes:** `MainThreadRequired = true`. `confirm=true` required to apply; supports `dry_run`. User layers are indices 8-31 (0-7 are reserved).
|
||||
|
||||
### `get_time_settings`
|
||||
Read Time settings (fixedDeltaTime, maximumDeltaTime, timeScale).
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `ProjectSettingsResponse`
|
||||
**Notes:** `MainThreadRequired = true`.
|
||||
|
||||
### `set_time_settings`
|
||||
Change Time settings. Requires confirm=true; use dry_run to preview.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `settings` | no | `–` | Fields to change; omitted fields are left unchanged (fields below). |
|
||||
| `confirm` | no | `false` | Apply the change. Without it the call is refused. |
|
||||
| `dry_run` | no | `false` | Preview the change without applying it. |
|
||||
|
||||
`settings` fields:
|
||||
|
||||
| Field | Required | Default | Description |
|
||||
|-------|----------|---------|-------------|
|
||||
| `fixedDeltaTime` | no | `–` | Fixed timestep in seconds (e.g. 0.02). |
|
||||
| `maximumDeltaTime` | no | `–` | Maximum allowed timestep in seconds. |
|
||||
| `timeScale` | no | `–` | Time scale (1 = real-time). |
|
||||
|
||||
**Returns:** `ProjectSettingsResponse`
|
||||
**Notes:** `MainThreadRequired = true`. `confirm=true` required to apply; supports `dry_run`.
|
||||
|
||||
See [Creating commands](../creating-commands.md) and [Connectivity](../connectivity.md).
|
||||
@@ -0,0 +1,213 @@
|
||||
# Runtime commands
|
||||
|
||||
Commands served by the Player / dev-build server. Many require a Development build with the runtime Pipeline enabled — see [Runtime setup](../runtime-setup.md). Commands marked `RuntimeOnly` are only registered in a player build.
|
||||
|
||||
### `runtime_status`
|
||||
Get comprehensive runtime application status.
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `RuntimeStatusResponse`
|
||||
**Notes:** `MainThreadRequired = true`, `RuntimeOnly`.
|
||||
|
||||
### `quit`
|
||||
Gracefully quit the Unity application.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `exitCode` | no | `0` | Exit code for the application |
|
||||
|
||||
**Returns:** `string`
|
||||
**Notes:** `MainThreadRequired = true`, `RuntimeOnly`.
|
||||
|
||||
### `set_target_framerate`
|
||||
Set the target frame rate for the application.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `frameRate` | yes | `–` | Target frame rate (-1 for platform default, 0 for unlimited) |
|
||||
|
||||
**Returns:** `string`
|
||||
**Notes:** `MainThreadRequired = true`, `RuntimeOnly`.
|
||||
|
||||
### `set_timescale`
|
||||
Set the time scale for the application.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `scale` | yes | `–` | Time scale multiplier (0.0 to pause, 1.0 for normal speed) |
|
||||
|
||||
**Returns:** `string`
|
||||
**Notes:** `MainThreadRequired = true`, `RuntimeOnly`.
|
||||
|
||||
### `simulate_key`
|
||||
Simulate a keyboard key event (Input System). Drives the running app.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `key` | yes | `–` | Input System Key name, e.g. Space, W, Enter, LeftArrow. |
|
||||
| `action` | no | `press` | down \| up \| press (down+up). |
|
||||
|
||||
**Returns:** `InputSimulationResponse`
|
||||
**Notes:** `MainThreadRequired = true`, `RuntimeOnly`. Requires the Input System package (`ENABLE_INPUT_SYSTEM`); reports unavailable otherwise.
|
||||
|
||||
### `simulate_pointer`
|
||||
Simulate a mouse/pointer event at screen coordinates (Input System).
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `x` | yes | `–` | Screen X in pixels (origin bottom-left). |
|
||||
| `y` | yes | `–` | Screen Y in pixels (origin bottom-left). |
|
||||
| `action` | no | `click` | move \| down \| up \| click (down+up). |
|
||||
| `button` | no | `left` | left \| right \| middle. |
|
||||
|
||||
**Returns:** `InputSimulationResponse`
|
||||
**Notes:** `MainThreadRequired = true`, `RuntimeOnly`. Requires the Input System package (`ENABLE_INPUT_SYSTEM`); reports unavailable otherwise.
|
||||
|
||||
### `log`
|
||||
Write a message to Unity console.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `message` | yes | `–` | Message to log to console |
|
||||
| `level` | no | `info` | Log level: info, warning, error |
|
||||
|
||||
**Returns:** `string`
|
||||
**Notes:** `MainThreadRequired = true`, `RuntimeOnly`.
|
||||
|
||||
### `console`
|
||||
Get captured Unity console output (Editor or Player; supports tail, level filtering, and follow via a cursor).
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `tail` | no | `100` | Maximum number of most-recent entries to return |
|
||||
| `level` | no | `log` | Minimum severity to include: log | warn | error |
|
||||
| `since` | no | `-1` | Cursor: only return entries newer than this seq. Use the 'cursor' from a previous response to follow. |
|
||||
| `since_session` | no | `–` | Session the `since` cursor came from: pass the `session` from a previous response. A cursor from another session is refused and the tail returned with `reset=true`. |
|
||||
|
||||
**Returns:** `ConsoleLogResponse`
|
||||
**Notes:** `MainThreadRequired = false`. Available in both the Editor and player builds.
|
||||
|
||||
A cursor is only meaningful paired with the `session` that issued it. Sequence numbers restart from
|
||||
zero when the Editor restarts (the buffer is persisted under `Temp/`, which Unity deletes at every
|
||||
launch), so a cursor from an earlier session names different entries. Always echo `session` back as
|
||||
`since_session`; when the pair cannot be honored the response sets `reset=true`, returns the tail
|
||||
instead of nothing, and gives you a fresh `cursor`/`session` to adopt.
|
||||
|
||||
Beyond the entries, the response carries:
|
||||
|
||||
- `counts` — errors/warnings/logs the buffer currently retains, before the level filter and `tail`.
|
||||
- `groundTruth` — what the Editor itself reports: `compilationFailed`, `compiling`, the Editor
|
||||
console's own `consoleErrors`/`consoleWarnings`/`consoleLogs`, whether the buffer has been
|
||||
`seeded` from the console's store, and `ageMs` for how stale the sample is. Null in a player.
|
||||
|
||||
`groundTruth.consoleErrors` exceeding `counts.error` means the Editor console is showing entries the
|
||||
buffer does not hold. That is expected for output produced before the package was first imported;
|
||||
otherwise it is the signal that something was missed.
|
||||
|
||||
### `console_status`
|
||||
Console ground truth and buffer counters without pulling entries: compile-failure flag, Editor
|
||||
console counts, and the buffer's retained counts and cursor.
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `ConsoleLogResponse` with an empty `entries` array.
|
||||
**Notes:** `MainThreadRequired = false`. Cheap enough to poll in a loop while a compile runs; use
|
||||
`console` when you want the entries themselves.
|
||||
|
||||
### `clear_console`
|
||||
Clear the captured log buffer and the Unity Editor console.
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `object`
|
||||
**Notes:** Available in both the Editor and player builds; in a player only the captured buffer is
|
||||
cleared, since there is no Editor console. Editor compile errors are logged as sticky entries, which
|
||||
Unity's own clear skips, so they remain in the Console window until the next compile completes.
|
||||
Clearing keeps the cursor, so a client following `console` sees `dropped=true` on its next poll
|
||||
rather than silently skipping the window that was cleared.
|
||||
|
||||
### `eval`
|
||||
Evaluate C# code dynamically using Roslyn compiler.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `code` | yes | `–` | C# code to evaluate |
|
||||
| `timeout` | no | `5000` | Timeout in milliseconds |
|
||||
|
||||
**Returns:** `EvalResponse`
|
||||
**Notes:** `MainThreadRequired = true`. Available on both editor and runtime.
|
||||
|
||||
### `eval_file`
|
||||
Evaluate C# code read from a `.cs` file on disk. A convenience alternative to `eval` for code
|
||||
too long to pass inline — edit the file, then evaluate it. The resolved source is run through the
|
||||
same evaluation path as `eval`.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `file` | yes | `–` | Path to a `.cs` file to evaluate |
|
||||
| `timeout` | no | `5000` | Timeout in milliseconds |
|
||||
|
||||
The `file` is read on the Unity side (relative paths resolve against the Unity process working
|
||||
directory) and must end in `.cs`. A missing file, a non-`.cs` extension, or an empty file returns
|
||||
a `Bad Request`.
|
||||
|
||||
**Returns:** `EvalResponse`
|
||||
**Notes:** `MainThreadRequired = true`. Available on both editor and runtime.
|
||||
|
||||
> For anything beyond an ad-hoc one-liner — bulk construction, builder scripts — prefer the
|
||||
> Editor-side [`run_script`](scripts.md#run_script) command, which compiles a versioned project
|
||||
> `.cs` file in memory and runs a named entry point instead of carrying code through the protocol.
|
||||
|
||||
### `reload_file`
|
||||
Compile and apply [CodeReload] edits from a source file.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `filename` | yes | `–` | Source file containing [CodeReload] methods (e.g. Assets/Scripts/Player.cs) |
|
||||
| `timeout` | no | `30000` | Compilation timeout in milliseconds |
|
||||
| `assemblyDir` | no | `–` | Directory to save compiled assemblies to disk (optional, default is in-memory only) |
|
||||
| `pdb` | no | `false` | Emit debug symbols (portable PDB) mapped to the original source so breakpoints bind in your editor. Compiles unoptimized. |
|
||||
|
||||
**Returns:** `CodeReloadResponse`
|
||||
**Notes:** `MainThreadRequired = true`. See [Reloading code](../code-reload.md).
|
||||
|
||||
### `reload_file_editor_interpreter`
|
||||
Compile [CodeReload] edits and run them through the IlInterpreter VM in this process instead of `Assembly.Load` (IL2CPP-safe; only a minimal host API + the target type are available).
|
||||
|
||||
Same parameters as [`reload_file`](#reload_file).
|
||||
|
||||
**Returns:** `CodeReloadResponse`
|
||||
**Notes:** `MainThreadRequired = true`. See [Reloading code](../code-reload.md).
|
||||
|
||||
### `reload_file_player_interpreter`
|
||||
Compile a file's (or folder's) [CodeReload] methods in the Editor and push the IL to connected development players over PlayerConnection (IL2CPP-safe). Folders are walked recursively; files that do not mention `CodeReload` are skipped.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `filename` | yes | `–` | Source `.cs` file or a folder containing [CodeReload] methods (e.g. Assets/pong.cs or Assets/Scripts) |
|
||||
| `player` | no | `-1` | Target connected player id; `-1` broadcasts to all connected players |
|
||||
|
||||
**Returns:** `{ success, message, diagnostics }` for a file; `{ success, message, pushed, failed }` for a folder.
|
||||
**Notes:** `MainThreadRequired = true`. Served by the **Editor** server (it needs Roslyn and PlayerConnection), not the player; the player only needs the runtime Pipeline driver, not its HTTP server. See [Reloading code](../code-reload.md#reload_file_player_interpreter).
|
||||
|
||||
### `codereload_status`
|
||||
Show current code reload registry status and statistics.
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `CodeReloadResponse`
|
||||
**Notes:** `MainThreadRequired = true`, `RuntimeOnly`.
|
||||
|
||||
### `cleanup_codereload`
|
||||
Remove old code reload DLL versions and clear registry.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `assemblyDir` | yes | `–` | Directory containing assemblies to cleanup |
|
||||
| `force_domain_reload` | no | `true` | Force Unity domain reload after cleanup |
|
||||
|
||||
**Returns:** `CodeReloadResponse`
|
||||
**Notes:** `MainThreadRequired = true`, `RuntimeOnly`.
|
||||
|
||||
See [Creating commands](../creating-commands.md) and [Connectivity](../connectivity.md).
|
||||
@@ -0,0 +1,93 @@
|
||||
# Scene commands
|
||||
|
||||
Commands for creating, opening, saving and inspecting scenes, controlling the active scene, snapshotting a scene's hierarchy, and managing the Build Settings scene list. Mutating scene operations are blocked while the editor is entering or in play mode (they fail with a recoverable error before touching any state); read-only commands are safe in play mode.
|
||||
|
||||
### `create_scene`
|
||||
Create a new scene and save it to the given path under the authoring root.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `path` | yes | – | Scene path relative to the authoring root (default Assets/); the Assets/ prefix and the .unity extension are optional. e.g. Scenes/Level1 |
|
||||
| `additive` | no | `false` | Open the new scene additively alongside currently open scenes instead of replacing them. |
|
||||
| `template` | no | `empty` | Initial contents: 'empty' (default) for a blank scene, or 'default' to seed a Main Camera + Directional Light matching Unity's built-in 3D template. |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
**Notes:** Blocked during Play mode.
|
||||
|
||||
### `open_scene`
|
||||
Open an existing scene from the given path.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `path` | yes | – | Scene path relative to the authoring root (default Assets/); the Assets/ prefix and the .unity extension are optional. |
|
||||
| `additive` | no | `false` | Open additively alongside currently open scenes instead of replacing them. |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
**Notes:** Blocked during Play mode.
|
||||
|
||||
### `save_scene`
|
||||
Save an open scene. Saves the active scene when no path is given.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `path` | no | `–` | Path of the open scene to save (authoring-root relative; Assets/ prefix and .unity optional). Omit to save the active scene. |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
**Notes:** Blocked during Play mode.
|
||||
|
||||
### `save_all`
|
||||
Save all open scenes that have unsaved changes.
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `object`
|
||||
**Notes:** Blocked during Play mode.
|
||||
|
||||
### `list_open_scenes`
|
||||
List all currently open scenes with their load/active/dirty state.
|
||||
|
||||
No parameters.
|
||||
|
||||
**Returns:** `object`
|
||||
|
||||
### `set_active_scene`
|
||||
Set which open scene is the active scene (new objects are created in the active scene).
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `path` | yes | – | Path of an already-open scene to make active (authoring-root relative; Assets/ prefix and .unity optional). |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
**Notes:** Blocked during Play mode.
|
||||
|
||||
### `get_scene_hierarchy`
|
||||
Return the GameObject tree of an open scene (or the active scene). Each node carries instanceId + hierarchyPath usable by GameObject commands.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `path` | no | `–` | Path of the open scene to snapshot (authoring-root relative; Assets/ prefix and .unity optional). Omit for the active scene. |
|
||||
|
||||
**Returns:** `SceneHierarchy`
|
||||
|
||||
### `add_scene_to_build`
|
||||
Add a scene to the Build Settings scene list (idempotent). Optionally enable it.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `path` | yes | – | Scene path to add (authoring-root relative; Assets/ prefix and .unity optional). |
|
||||
| `enabled` | no | `true` | Whether the scene is enabled in the build list. |
|
||||
|
||||
**Returns:** `object`
|
||||
**Notes:** Blocked during Play mode.
|
||||
|
||||
### `remove_scene_from_build`
|
||||
Remove a scene from the Build Settings scene list (idempotent).
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `path` | yes | – | Scene path to remove (authoring-root relative; Assets/ prefix and .unity optional). |
|
||||
|
||||
**Returns:** `object`
|
||||
**Notes:** Blocked during Play mode.
|
||||
|
||||
See [Creating commands](../creating-commands.md) and [Connectivity](../connectivity.md).
|
||||
@@ -0,0 +1,111 @@
|
||||
# Script commands
|
||||
|
||||
Commands for creating C# script files, attaching MonoBehaviours to GameObjects, reading/writing serialized fields, and running compiled project entry points in memory. Writing a `.cs` file does not make its type available — Unity must import and compile it (a domain reload) first. The flow is: `create_script` → `recompile` → poll `recompile_status` (until completed/up_to_date) → `attach_script`. For bulk construction that does **not** need a new persistent project type, skip the recompile entirely: [`run_script`](#run_script) compiles a file in memory and runs it with no asset import and no domain reload.
|
||||
|
||||
### `create_script`
|
||||
Create a new C# script (default base class MonoBehaviour) from a template under the authoring root. NOTE: the type does not exist until a recompile completes — to attach it, call recompile, poll recompile_status, then attach_script.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `name` | yes | – | Class/file name without extension, e.g. PlayerController. Must be a valid C# identifier. |
|
||||
| `path` | no | `–` | Folder (relative to the authoring root; the Assets/ prefix is optional) to write the .cs into. Defaults to the authoring root. |
|
||||
| `namespace` | no | `–` | Optional namespace to wrap the class in. Omit for the global namespace. |
|
||||
| `base_class` | no | `MonoBehaviour` | Base class to derive from. Defaults to MonoBehaviour. |
|
||||
| `overwrite` | no | `false` | Overwrite the file if it already exists. Defaults to false (an existing file is an error). |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
**Notes:** Does not trigger a recompile; the created type is not usable until a recompile completes (poll `recompile_status`).
|
||||
|
||||
### `attach_script`
|
||||
Add a MonoBehaviour to a GameObject by its (compiled) type name OR by its script asset path. Provide exactly one of 'type' or 'script'. If the type isn't compiled yet, returns a recoverable error: recompile, poll recompile_status, then retry.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `target` | yes | – | Reference to the GameObject to add the component to (globalId/path/guid/instanceId/hierarchyPath). |
|
||||
| `type` | no | `–` | Component type name to add, e.g. PlayerController or Game.Player.PlayerController. Must already be compiled. Mutually exclusive with 'script'. |
|
||||
| `script` | no | `–` | Script asset path, e.g. 'Assets/Pool/Scripts/CueShooter.cs'. The backing class is resolved via MonoScript.GetClass(), so the class name may differ from the filename. Mutually exclusive with 'type'. |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
**Notes:** Undo-able. Provide exactly one of `type` or `script`. A not-yet-compiled type returns a recoverable error (recompile, poll `recompile_status`, then retry).
|
||||
|
||||
### `set_serialized_field`
|
||||
Set a serialized field on a component/asset. Supports primitives, enums, Vector/Color/Rect/Bounds, object references (value = an ObjectRef: asset by guid/fileId/path or scene object by instanceId/hierarchyPath), and array elements via 'name.Array.data[i]' (or 'name.Array.size' to resize).
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `target` | yes | – | Reference to the component or asset to modify (globalId/path/guid/instanceId/hierarchyPath). May be a GameObject when 'component' is given. |
|
||||
| `field` | yes | – | SerializedProperty path, e.g. 'speed', 'settings.speed', or 'waypoints.Array.data[0]'. |
|
||||
| `value` | yes | – | JSON value to assign. For object references pass an ObjectRef object (or null to clear). For enums pass the value name. |
|
||||
| `component` | no | `–` | Component type name on the target GameObject (e.g. 'Rigidbody'). Use when 'target' is a GameObject; omit when 'target' is already a component handle. |
|
||||
|
||||
**Returns:** `AuthoringResult`
|
||||
**Notes:** Undo-able.
|
||||
|
||||
### `get_serialized_fields`
|
||||
Read serialized fields of a component/asset. Returns each top-level field's name, type and value (object references are returned as re-usable handles). Pass 'field' to read a single SerializedProperty path.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `target` | yes | – | Reference to the component or asset to read (globalId/path/guid/instanceId/hierarchyPath). May be a GameObject when 'component' is given. |
|
||||
| `field` | no | `–` | Optional single SerializedProperty path to read (e.g. 'speed' or 'items.Array.data[0]'). Omit to read all top-level fields. |
|
||||
| `component` | no | `–` | Component type name on the target GameObject (e.g. 'Rigidbody'). Use when 'target' is a GameObject; omit when 'target' is already a component handle. |
|
||||
|
||||
**Returns:** `object`
|
||||
|
||||
### `run_script`
|
||||
Compile a single project `.cs` file in memory (no domain reload, no code carried through the
|
||||
protocol) and execute a named `static` entry point. This is the **builder-pattern** path for bulk
|
||||
construction — see the note below. Editor command; still arbitrary code execution, so it is grouped
|
||||
under the same `scripts/eval` tag as `eval` and is disabled wherever the eval family is disabled.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `file` | yes | `–` | Path to a `.cs` file. Relative paths resolve against the **project root** (the parent of `Assets/`), not the process working directory. May live **outside `Assets/`** (e.g. `AgentScripts/`) so writing it never triggers an asset import or domain reload. |
|
||||
| `entry` | no | `–` | Entry point as `Namespace.Type.Method` (or `Type.Method`, or a bare `Method`). Nested types may be written with dots (`Outer.Inner.Method`). Default: the single public static method if unambiguous, else a method named `Main`. Static, non-generic methods only in v1. Ephemeral mode only — rejected with `mode=hotpatch`. |
|
||||
| `args` | no | `–` | JSON array of arguments, coerced to the entry's parameter types: primitives, enum names, `string[]`, and a string/object handle (`ObjectRef`) for `UnityEngine.Object` parameters. JSON `null` is only valid for reference-type and nullable parameters (never a silent `default(T)`). Ephemeral mode only — rejected with `mode=hotpatch`. |
|
||||
| `mode` | no | `ephemeral` | `ephemeral`: compile to an in-memory assembly, run the entry, discard. `hotpatch`: apply in-place `[CodeReload]` method replacements to already-loaded types (delegates to `reload_file`). |
|
||||
| `references` | no | `–` | Extra assembly name prefixes to reference. In the Editor all loaded assemblies are already referenced, so this is effectively a no-op there. Ephemeral mode only; **not applied** by hotpatch (the compile is `reload_file`'s). |
|
||||
| `defines` | no | `–` | Extra scripting define symbols, **appended to the project's active editor defines** (`EditorUserBuildSettings.activeScriptCompilationDefines`) — so `UNITY_EDITOR`, version and platform symbols are already set and sources behave like project code; your defines extend that set rather than replace it. Ephemeral mode only; **not applied** by hotpatch. |
|
||||
| `pdb` | no | `false` | Emit a portable PDB mapped to the source file so breakpoints bind. Ephemeral runs always emit source-mapped symbols so exception stack traces carry `file:line`. Compiles unoptimized. |
|
||||
| `timeout_ms` | no | `30000` | Timeout in milliseconds (governs the main-thread wait budget end-to-end, like `eval`). |
|
||||
| `dry_run` | no | `false` | Compile only: return diagnostics; the emitted assembly is **not loaded** into the domain and nothing executes (the response carries no `assemblyName`). Rejected with `mode=hotpatch` — a hotpatch "dry run" would still apply live method replacements. |
|
||||
|
||||
**Returns:** `RunScriptResponse` — `{ result, diagnostics, compileMs, executeMs, assemblyName }` on top
|
||||
of the standard envelope. The result carries no echo of the source (path + diagnostics only). Compile
|
||||
errors return full Roslyn diagnostics (with line/column) and execute nothing; runtime exceptions
|
||||
surface as structured errors with `file:line` mapped to the source file.
|
||||
**Notes:** `MainThreadRequired = true`. Editor command.
|
||||
|
||||
- Executing runs always compile **unoptimized (Debug codegen)** so the emitted symbols keep full
|
||||
sequence points for `file:line` mapping — expect the generated code to run somewhat slower than
|
||||
regular project assemblies.
|
||||
- **Async entry points** (`async Task` / `Task<T>`) are awaited to completion; `Task<T>.Result`
|
||||
becomes the command result and exceptions in the async work surface as runtime errors. The await
|
||||
is **asynchronous** — the Editor main thread keeps pumping, so awaits that resume on Unity's
|
||||
main-thread context (`Task.Yield()`, `Task.Delay()`, Awaitable, …) work naturally; no
|
||||
`ConfigureAwait(false)` contortions are required. The wait is bounded by what remains of
|
||||
`timeout_ms`; on expiry the command returns a `Timeout` error and the task keeps running
|
||||
detached (its effects may still land), like an abandoned `eval`.
|
||||
- **hotpatch specifics:** `entry`, `args` and `dry_run` are rejected (Bad Request); `references` and
|
||||
`defines` are not applied. Path resolution follows `reload_file`, which probes under `Assets/`
|
||||
for bare filenames (unlike ephemeral's project-root resolution). Timing: `executeMs` carries the
|
||||
whole reload duration (compile + apply, not split by `reload_file`); `compileMs` is `0`. Advisory
|
||||
reload diagnostics are surfaced as severity-`warning` entries on success.
|
||||
- Under Unity's Mono runtime the in-memory assembly emitted by an executing `ephemeral` run is
|
||||
**not** collectible/unloadable, so each run retains one small assembly (the same behavior as
|
||||
`eval`); prefer a single builder entry over many tiny runs. `dry_run` loads nothing.
|
||||
|
||||
#### The builder pattern (bulk construction)
|
||||
|
||||
For bulk construction — creating many objects, wiring private fields, generating content — put the
|
||||
logic in a **versioned project script** and invoke it with `run_script`. Write the file with
|
||||
`write_text_file` (ideally under a non-`Assets/` folder such as `AgentScripts/` so the write triggers
|
||||
no asset import), then call `run_script --file AgentScripts/Build.cs --entry Build.All`. Iterating the
|
||||
script and re-running it costs an in-memory compile (< ~2s), **not** a 15–20s domain reload.
|
||||
|
||||
The anti-patterns this replaces: carrying construction code as escaped C# strings through `eval`
|
||||
(verbose, unreviewable, shell-escaping hazards), and editing a builder under `Assets/` in a loop
|
||||
(each edit pays a full domain reload). Hold the line: code goes in files on disk via `run_script`;
|
||||
`eval` stays for genuinely ad-hoc one-liners.
|
||||
|
||||
See [Creating commands](../creating-commands.md) and [Connectivity](../connectivity.md).
|
||||
@@ -0,0 +1,169 @@
|
||||
# Wait commands
|
||||
|
||||
Server-side condition waiting (AUTHAPI-25). `wait_for` waits until a runtime/editor member condition holds — evaluated on the Editor's per-frame loop — and can act in the **same frame** the condition first becomes true (e.g. screenshot the moment a match-end screen appears). It replaces the dominant agent anti-pattern of client-side polling loops: read a value via `eval`, sleep in the client, repeat. That loop is token-expensive and slow, and its client round-trips can miss short-lived states entirely (a 2-second screen is easy to miss at ~1–2 s per poll). A server-side condition is a cheap per-frame delegate that can act atomically the frame it fires.
|
||||
|
||||
## Sync vs async: pick the right mode first
|
||||
|
||||
`/api/exec` executes **one command at a time** — a synchronous `wait_for` holds that queue for its entire duration. The Editor itself never freezes (each poll is marshaled onto the main thread between sleeps), but **every other command queues behind the wait until it resolves**. That includes any command whose effect your condition is waiting for: if the condition needs a mutation you haven't issued yet, the mutation can never run, and the wait self-deadlocks until its timeout.
|
||||
|
||||
Use a **synchronous** wait only when BOTH hold:
|
||||
|
||||
- the wait is short — comfortably under your client's HTTP timeout (typically **< 30 s**), and
|
||||
- the condition does **not** depend on any command you still need to issue (it will be satisfied by something already in motion: play-mode gameplay, a running bake, a timer, ...).
|
||||
|
||||
For **everything else**, pass `async=true`: you get a `waitId` back immediately, other commands keep executing, and you poll `wait_status` (and cancel with `wait_cancel`). This is the default choice for long waits and for any "issue command, then wait for its effect" flow.
|
||||
|
||||
A synchronous wait submitted as a detached job (`POST /api/exec` with `"job": true`) remains cancelable: `POST /api/job/cancel` interrupts the sleep loop and resolves the wait as `canceled`.
|
||||
|
||||
> Member resolution, coercion, and the read-only security policy are intended to be shared with the invoke/query surface (AUTHAPI-16). Until that shared resolver lands, `wait_for` ships a self-contained resolver with the same rules that the two will converge on. Type and member-chain resolution is cached per wait — only the live target instance (an `ObjectRef` target or a `findType` instance) is re-resolved each poll.
|
||||
|
||||
### `wait_for`
|
||||
Wait until a member condition holds, then optionally act in the same frame.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `condition` | yes | `–` | The condition object (see below). |
|
||||
| `timeout_s` | no | `30` | Maximum seconds to wait (clamped to 0–600; NaN/Infinity are rejected). |
|
||||
| `poll_interval_ms` | no | `100` | How often to re-check (clamped to 16–5000 ms; NaN/Infinity are rejected). The sleep never exceeds the remaining budget, so a large interval cannot stretch a short timeout. |
|
||||
| `on_met` | no | `–` | Follow-up run in the same frame the condition first holds: `{ capture: { view, source, save_path, width, height, include_image }, pause }`. `capture.source` (game view only): `camera` (default) renders a camera and **misses Screen Space - Overlay UI**; `screen` captures the composited game view including overlay canvases (HUDs), Play Mode only. |
|
||||
| `return_history` | no | `false` | Include the observed `{ value, elapsedMs }` samples. A ring buffer keeps the **last 200** samples, so the tail near the transition is retained (the interesting part), not the first 200 identical observations. |
|
||||
| `tolerate_missing` | no | `false` | Treat resolution failures (type/member/instance not found) as condition-not-met and retry until timeout, instead of failing fast. Enables "wait until X exists" — e.g. waiting for a spawned object via `findType`. On timeout the last resolution error is included. |
|
||||
| `async` | no | `false` | Return a `waitId` immediately and evaluate in the background; poll `wait_status`, cancel with `wait_cancel`. **Use for long waits and whenever the condition depends on a command you still need to issue.** |
|
||||
|
||||
**The `condition` object**
|
||||
|
||||
| Field | Required | Description |
|
||||
|-------|----------|-------------|
|
||||
| `member` | yes | Dotted member path. With no `target`/`findType` it is a fully-qualified **static** path (e.g. `UnityEditor.EditorApplication.isPlaying`); with `target`/`findType` it walks **instance** members (e.g. `State`, `Health.CurrentHealth`). Read-only fields/properties only — write-only members and methods are rejected. |
|
||||
| `target` | no | Object handle (`instanceId` / `hierarchyPath` / `guid` / `path` / `globalId`) to read instance members from. Re-resolved every poll, so the referenced object may appear mid-wait (combine with `tolerate_missing`). |
|
||||
| `findType` | no | Fully-qualified `UnityEngine.Object` type name; the member walks from a **live** instance. Persistent objects (prefab/asset files on disk) and hidden editor internals (`hideFlags != None`) are excluded, so the wait never silently binds to a prefab asset instead of the scene instance. Scene instances are preferred; if several candidates remain, the lowest instance id is chosen deterministically and a `note` on the result reports the ambiguity — pass `target` to select one explicitly. |
|
||||
| `op` | no | `equals` (default) \| `notEquals` \| `greaterThan` \| `lessThan` \| `contains` (strings) \| `changed` (met on the first change from the initially observed value; needs no `value`). |
|
||||
| `value` | conditional | Comparison operand, coerced to the member's type (primitive, enum name, or string). Required for every op except `changed`. When either side is a `float`, `equals`/`notEquals` compare at **float precision** (widening a float to double manufactures noise digits — `0.9f` would otherwise never equal `0.9`). |
|
||||
|
||||
**`op: changed` is for value-like members** — primitives, enums, and strings. It compares the member's current value against the first observed value, so content mutations *inside* a reference-typed member (a list gaining an element, a field on a referenced object changing) are **not** detected: the reference itself must change. Likewise, `value`/`initialValue`/`history` snapshots of mutable reference types reflect the object's state at serialization time, not at observation time.
|
||||
|
||||
**Returns:** `WaitResult`
|
||||
|
||||
```
|
||||
{
|
||||
"state": "met", // pending | met | timedOut | interrupted | canceled | failed | not_found
|
||||
"met": true,
|
||||
"timedOut": false,
|
||||
"member": "MatchManager.Instance.State",
|
||||
"op": "equals",
|
||||
"value": "Ended", // most recently observed value
|
||||
"initialValue": "Playing", // first observed value (baseline for op=changed)
|
||||
"elapsedMs": 512, // frozen at the terminal transition
|
||||
"framesObserved": 6,
|
||||
"note": "…", // present only for non-fatal resolution notes (e.g. ambiguous findType)
|
||||
"capture": { "savedPath": "Screenshots/victory.png" }, // present only with on_met.capture
|
||||
"waitId": "…" // present only for async submissions / status snapshots
|
||||
}
|
||||
```
|
||||
|
||||
- A **timeout** returns `met:false, timedOut:true` with the last observed `value` — distinct from a resolution/evaluation error, which returns `state:"failed"` with an `error` message (unless `tolerate_missing` turns resolution errors into retries; the wait then times out with the last resolution error attached).
|
||||
- If the main thread stalls past the per-poll marshal budget (~60 s: a huge import, a modal bake), the poll is counted as **missed** and the wait keeps going until its own budget expires — a stall does not turn the wait into a generic error.
|
||||
- The sync result is well under 2 KB without `history`.
|
||||
|
||||
### `wait_status`
|
||||
Get the status/result of an async wait (`wait_for` with `async=true`).
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `wait_id` | yes | `–` | The id returned by `wait_for` when `async=true`. |
|
||||
|
||||
**Returns:** `WaitResult` (`state:"not_found"` if the id is unknown — async waits do not survive a domain reload; completed waits are retained up to a bounded count and pruned oldest-first the next time a `wait_for` is submitted, so a session that never submits another wait keeps its last results queryable).
|
||||
|
||||
### `wait_cancel`
|
||||
Cancel an async wait.
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `wait_id` | yes | `–` | The id to cancel. |
|
||||
|
||||
**Returns:** `WaitResult`. If the wait was still active, it resolves to `state:"canceled"`. If it had already reached a terminal state (`met`/`timedOut`/`failed`/`interrupted`) before the cancel request arrived, that actual state is returned unchanged — the cancel had no effect, and reporting a fake `canceled` here would misreport whether an `on_met` follow-up (capture/pause) actually ran.
|
||||
|
||||
## Migration: replace a client polling loop with one call
|
||||
|
||||
Before — an agent screenshots a match-end screen by polling with `eval` (dozens of round-trips, and the 2-second screen is easy to miss):
|
||||
|
||||
```
|
||||
# ~60 client round-trips, each ~1–2s:
|
||||
loop:
|
||||
state = eval "MatchManager.Instance.State" # read
|
||||
if state == "Ended": break
|
||||
sleep 1 # client-side sleep
|
||||
capture_game_view --source screen --save_path Screenshots/victory.png
|
||||
```
|
||||
|
||||
After — one **async** wait that captures atomically in the frame the condition first holds, while other commands stay available (the match may take minutes, and you may still need to issue commands that drive it):
|
||||
|
||||
```
|
||||
# 1. Arm the wait; returns { waitId } immediately:
|
||||
wait_for --condition '{"member":"MatchManager.Instance.State","op":"equals","value":"Ended"}' \
|
||||
--timeout_s 300 \
|
||||
--on_met '{"capture":{"view":"game","source":"screen","save_path":"Screenshots/victory.png"}}' \
|
||||
--async true
|
||||
|
||||
# 2. Keep working: start the match, drive gameplay, run other commands...
|
||||
|
||||
# 3. Collect the result (poll until state != "pending"):
|
||||
wait_status --wait_id <waitId>
|
||||
```
|
||||
|
||||
Only use the synchronous form when the wait is short and self-contained — e.g. play mode is already running and the state flips within a few seconds:
|
||||
|
||||
```
|
||||
wait_for --condition '{"member":"MatchManager.Instance.State","op":"equals","value":"Ended"}' --timeout_s 10
|
||||
```
|
||||
|
||||
## Waiting for something to exist (spawn waits)
|
||||
|
||||
By default the first failed resolution fails the wait (`state:"failed"`) — that catches typos fast. To wait for an object or member that does not exist *yet*, pass `tolerate_missing:true`:
|
||||
|
||||
```
|
||||
wait_for --condition '{"findType":"MyGame.BossController","member":"IsActivated","op":"equals","value":true}' \
|
||||
--tolerate_missing true --timeout_s 120 --async true
|
||||
```
|
||||
|
||||
Resolution failures are then treated as condition-not-met and retried every poll until the object appears (or the wait times out, with the last resolution error attached).
|
||||
|
||||
## Interruption and lifecycle
|
||||
|
||||
- Concurrent waits are capped (8); a further submission is rejected with a structured error telling you to `wait_cancel` one or let one finish.
|
||||
- A **domain reload** (`beforeAssemblyReload`) or **exiting play mode** resolves every active wait to `state:"interrupted"` rather than hanging or leaking. **Entering play mode does not interrupt**: with domain reload disabled ("Enter Play Mode Options"), a wait armed in edit mode survives into play mode — the natural way to arm a capture before pressing play. (With domain reload enabled, the reload interrupts it anyway.)
|
||||
- `on_met.pause` pauses play mode on the frame the condition holds (no-op outside play mode).
|
||||
- If the server is stopping while a synchronous wait is pending, the wait resolves as `interrupted` ("server stopping") instead of evaluating against a shutting-down editor.
|
||||
|
||||
## Result contract details
|
||||
|
||||
- `error` is set only when the wait itself ends abnormally (`failed`, `interrupted`, `canceled`,
|
||||
or the surfaced last resolution error on a tolerated timeout). A failed `on_met` follow-up
|
||||
(capture/pause) is reported separately as **`onMetError`** while `state` stays `met` — the
|
||||
condition did hold; check `onMetError` before trusting `capture`.
|
||||
- `condition.target` and `condition.findType` are **mutually exclusive** (rejected up front).
|
||||
- Member paths resolve **public** readable fields/properties only.
|
||||
- A **destroyed** Unity object mid-path fails structurally; as the **leaf** value it is observed
|
||||
as `null` (Unity's own `==` semantics), so "wait until destroyed" is
|
||||
`{ "op": "equals", "value": null }` — the `value` key must be present and explicitly `null`;
|
||||
omitting it is rejected for every op except `changed`.
|
||||
- `tolerate_missing` retries the evaluator's STRUCTURED failures: resolution failures
|
||||
(type/member/instance not found, unless permanently malformed) and evaluation errors
|
||||
("cannot compare yet" — e.g. an ordering op while the member is still null); the last error is
|
||||
surfaced if the wait times out. A member getter that THROWS fails the wait immediately,
|
||||
tolerated or not.
|
||||
- A bare type name matching several loaded types binds deterministically (lowest full name) and
|
||||
surfaces a `note` — use the fully-qualified name to disambiguate.
|
||||
|
||||
## Interaction with `batch`
|
||||
|
||||
`wait_for` is rejected inside a `batch` (`not_batchable`): the batch holds the main thread for its
|
||||
whole turn, so the editor state a condition watches cannot change while the wait polls — it would
|
||||
just burn its timeout. Run an async `wait_for` before or after the batch and poll `wait_status`
|
||||
(both `wait_status` and `wait_cancel` are quick reads and stay batchable).
|
||||
|
||||
## Interaction with the startup settle gate
|
||||
|
||||
`wait_for` is a background command (`MainThreadRequired=false`), so it is **exempt from the settling busy-gate** (AUTHAPI-35): it is accepted even while the Editor is still importing/compiling after a cold start. A wait polling during that window observes a half-ready editor — types may not be loaded and objects may not exist yet. Combine with `tolerate_missing:true` for a deliberate wait-until-ready pattern, or gate on `/api/status` reporting `ready` first if you want fully settled reads.
|
||||
|
||||
See [Creating commands](../creating-commands.md) and [Connectivity](../connectivity.md).
|
||||
@@ -0,0 +1,541 @@
|
||||
# Connectivity
|
||||
|
||||
How a client reaches a running Unity instance: the loopback-only HTTP servers, their port ranges, the port descriptor file used for discovery, and the bearer-token authentication every request must carry.
|
||||
|
||||
The rest of this page covers the implementation. If you just want to connect, start with the two sections below.
|
||||
|
||||
## Connecting to a running Editor
|
||||
|
||||
Connections go through the `unity` CLI. Run `unity command` with no command name to connect to a Unity instance and list its available commands:
|
||||
|
||||
```bash
|
||||
# Auto-discover a Unity instance from the current directory
|
||||
unity command
|
||||
|
||||
# Connect to a specific project
|
||||
unity command --project-path /path/to/your/unity/project
|
||||
```
|
||||
|
||||
Once connected, run any command by name, e.g. `unity command editor_status`.
|
||||
|
||||
## Connecting to a running Player (game)
|
||||
|
||||
To target a running development Player instead of the Editor, use `--runtime` (by process name) or `--runtime-path` (by the location of the runtime port file). These options go **after** `command` and **before** the command name:
|
||||
|
||||
```bash
|
||||
# By Player process/executable name
|
||||
unity command --runtime MyGame.exe runtime_status
|
||||
|
||||
# By the folder/bundle where the runtime port file lives
|
||||
unity command --runtime-path "C:\Builds\MyGame" runtime_status # Windows: next to the .exe
|
||||
unity command --runtime-path "/Users/me/Builds/MyGame.app" runtime_status # macOS: the .app bundle
|
||||
```
|
||||
|
||||
> The Runtime server only runs in a **development Player build** with the runtime manager enabled — see [Runtime connection & setup](runtime-setup.md).
|
||||
|
||||
## Finding the port in the Unity logs
|
||||
|
||||
The [port descriptor file](#port-descriptor-file) is the primary discovery channel, but the server also reports itself to the Unity log when it starts and stops — useful when you can't read the descriptor file directly. Look for `Pipeline`-prefixed lines.
|
||||
|
||||
- **Runtime (Player)** — logs its port **and** the descriptor file location on start, and a line on stop:
|
||||
|
||||
```
|
||||
Pipeline: Runtime server started successfully on port 7901
|
||||
Pipeline: Runtime descriptor written to /path/to/MyGame/.unity-pipeline-runtime-port
|
||||
Pipeline: Runtime server stopped
|
||||
```
|
||||
|
||||
- **Editor** — logs its port when the server is (re)started from the **Pipeline ▸ Start Server** menu or re-opened by its watchdog (`Pipeline Server started on port 7801`). Its descriptor always lives at the fixed `Library/Pipeline/.unity-pipeline-port` path.
|
||||
|
||||
### Where the logs live
|
||||
|
||||
**Editor log** (`Editor.log`):
|
||||
|
||||
| OS | Path |
|
||||
|----|------|
|
||||
| Windows | `C:\Users\<user>\AppData\Local\Unity\Editor\Editor.log` |
|
||||
| macOS | `~/Library/Logs/Unity/Editor.log` |
|
||||
|
||||
**Player log** (`Player.log`):
|
||||
|
||||
| OS | Path |
|
||||
|----|------|
|
||||
| Windows | `C:\Users\<user>\AppData\LocalLow\<company>\<product>\Player.log` |
|
||||
| macOS | `~/Library/Logs/<company>/<product>/Player.log` |
|
||||
|
||||
(`<company>` and `<product>` are the project's **Company Name** and **Product Name** from Player Settings.)
|
||||
|
||||
## Loopback-only binding
|
||||
|
||||
Both the Editor and Runtime servers bind to the **IPv4 loopback** (`127.0.0.1`) — plus the `localhost` hostname for compatibility:
|
||||
|
||||
```csharp
|
||||
if (Socket.OSSupportsIPv4)
|
||||
m_HttpListener.Prefixes.Add($"http://127.0.0.1:{m_Port}/");
|
||||
m_HttpListener.Prefixes.Add($"http://localhost:{m_Port}/");
|
||||
```
|
||||
|
||||
The server is never exposed on a routable interface — it is reachable only from the same machine.
|
||||
|
||||
Clients should connect to **`127.0.0.1`** explicitly rather than `localhost`. Unity's Mono `HttpListener` only reliably serves the IPv4 loopback: a request arriving over the IPv6 loopback (`::1`) is answered with `400` because Mono mis-parses the bracketed `[::1]` host. Since `localhost` resolves to `::1` or `127.0.0.1` non-deterministically (notably on Windows with Node's default DNS order), dialing `localhost` caused intermittent connection failures — dialing `127.0.0.1` avoids the IPv6 path entirely.
|
||||
|
||||
In addition, the server refuses any request that carries an `Origin` header (legitimate CLI/CI clients never send one), which blocks a browser page from reaching the local server and short-circuits CORS preflights. The Editor server can opt into the single exception: enabling **Allow Browser Clients** in the pipeline settings asset (`Window/Pipeline/Settings...`, applies on next start) admits `Origin: null`, the opaque origin a sandboxed browser frame reports, and answers that client's CORS preflight. An ordinary page sends its real origin and is still refused, and a sandboxed one still needs the bearer token.
|
||||
|
||||
## Port ranges
|
||||
|
||||
Each server type picks the first free port in its range (or you can pin one explicitly):
|
||||
|
||||
| Server | Production range | Test range |
|
||||
|--------|------------------|------------|
|
||||
| **Editor** | `7800`–`7849` | `7850`–`7899` |
|
||||
| **Runtime** (Player) | `7900`–`7949` | `7950`–`7999` |
|
||||
|
||||
If no port in the range is free, startup throws (`No available ports in range …`).
|
||||
|
||||
## Port Descriptor File
|
||||
|
||||
When a server starts, it writes a small JSON **instance descriptor** so clients can discover it. This is the only discovery channel — there is no broadcast or registry.
|
||||
|
||||
| Server | Descriptor path |
|
||||
|--------|-----------------|
|
||||
| **Editor** | `<projectPath>/Library/Pipeline/.unity-pipeline-port` |
|
||||
| **Runtime** | `<workingDirectory>/.unity-pipeline-runtime-port` |
|
||||
|
||||
The Editor descriptor lives under the git-ignored `Library/` folder. The file is created with permissions restricted to the current user (it carries the auth token). The server rewrites it on every heartbeat to refresh `lastHeartbeat`, and deletes it on shutdown.
|
||||
|
||||
The Runtime descriptor is written next to the Player, at `{Application.dataPath}/..`:
|
||||
|
||||
- **Windows / Linux** — beside the executable, e.g. `C:\Builds\MyGame\.unity-pipeline-runtime-port`.
|
||||
- **macOS** — `Application.dataPath` is `<Game>.app/Contents`, so the descriptor lands at the **`.app` bundle root**: `MyGame.app/.unity-pipeline-runtime-port`. (The file is inside the `.app` bundle.) This is why `unity command --runtime-path` takes the `.app` bundle path on macOS — see [Runtime connection & setup](runtime-setup.md).
|
||||
- **Android** — `Application.dataPath` points inside the read-only APK install directory, so the descriptor is written to `Application.persistentDataPath` instead: `<persistentDataPath>/.unity-pipeline-runtime-port`.
|
||||
|
||||
> Test servers do **not** write a descriptor (they override `WritesDescriptor => false`) — the test already knows its port, so it never clobbers the live server's file. See [Tests architecture](testing.md).
|
||||
|
||||
### Editor descriptor fields
|
||||
|
||||
```json
|
||||
{
|
||||
"pid": 12345,
|
||||
"port": 7800,
|
||||
"projectPath": "/path/to/Project",
|
||||
"projectName": "Project",
|
||||
"unityVersion": "6000.x.y",
|
||||
"mode": "editor",
|
||||
"startedAt": "2026-06-25T10:00:00Z",
|
||||
"lastHeartbeat": "2026-06-25T10:05:00Z",
|
||||
"evalToken": "<base64 token>",
|
||||
"capabilities": ["exec.argv", "exec.commandLine"],
|
||||
"info": "This editor has not been opened in automated mode and therefore could get stuck on modal dialogs. Human intervention may be required."
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Meaning |
|
||||
|-------|---------|
|
||||
| `pid` | Process id of the Unity Editor. |
|
||||
| `port` | Port the server is listening on. |
|
||||
| `projectPath` | Absolute path to the Unity project. |
|
||||
| `projectName` | Project folder name. |
|
||||
| `unityVersion` | Editor version string. |
|
||||
| `mode` | `"editor"` or `"batchmode"`. |
|
||||
| `startedAt` | When the instance started (UTC). |
|
||||
| `lastHeartbeat` | Last heartbeat (UTC), refreshed on status calls. |
|
||||
| `evalToken` | Bearer token for authenticating requests. |
|
||||
| `capabilities` | Optional wire features this server understands (see [Capability negotiation](#capability-negotiation)). **An absent key means the server predates the field** — treat it as supporting none of them. |
|
||||
| `info` | Corrective guidance for the client, omitted when there is nothing to say. Currently only set when the Editor is interactive and wasn't launched with `-automated`: a blocking modal dialog can stall the Editor's main-thread update loop (and therefore all pipeline command processing) until a human dismisses it. Read this before issuing commands — it's the one field every client sees, since reading the descriptor is a mandatory first step just to connect. |
|
||||
|
||||
### Runtime descriptor fields
|
||||
|
||||
The runtime descriptor shares `pid`, `port`, `unityVersion`, `startedAt`, `lastHeartbeat`, and `evalToken`, and adds:
|
||||
|
||||
| Field | Meaning |
|
||||
|-------|---------|
|
||||
| `platform` | Unity runtime platform (e.g. `WindowsPlayer`). |
|
||||
| `buildGuid` | Unique build identifier (`Application.buildGUID`). |
|
||||
| `workingDirectory` | Directory the Player is running from. |
|
||||
|
||||
The runtime descriptor also carries `capabilities`, with the same meaning and the same absent-key rule.
|
||||
|
||||
(The runtime descriptor carries `platform`/`buildGuid`/`workingDirectory` in place of the editor's `projectPath`/`projectName`/`mode`.)
|
||||
|
||||
## Authentication
|
||||
|
||||
Every request must authenticate with a bearer token:
|
||||
|
||||
```
|
||||
Authorization: Bearer <evalToken>
|
||||
```
|
||||
|
||||
- The server **generates the token at startup** (`SecurityTokenManager.GetOrCreateToken()` — 256 bits of CSPRNG output, base64-encoded). In the Editor the token is persisted in `SessionState`, so it **survives domain reloads** within an editor session (recompiles, entering play mode) — long-lived clients (MCP sessions, IDE integrations) keep working instead of getting `401` after every reload. It is regenerated only when the Editor **restarts**, or on an explicit rotation (`SecurityTokenManager.ClearCache()` / `RotateToken()`). Player builds use a per-process token.
|
||||
- The token is published in the descriptor's `evalToken` field. The descriptor is **re-advertised with the live token on every heartbeat**, so the port file never advertises a token the server would reject (e.g. after a reload or rotation).
|
||||
- The server validates the bearer token on **every** request (before routing) using a constant-time comparison. A missing or wrong token returns `401 Unauthorized`.
|
||||
|
||||
## Discovering and calling an instance
|
||||
|
||||
A client connects by:
|
||||
|
||||
1. Reading the descriptor file (editor: `Library/Pipeline/.unity-pipeline-port`).
|
||||
2. Taking the `port` and `evalToken` from it.
|
||||
3. Sending requests to `http://127.0.0.1:<port>/...` with `Authorization: Bearer <evalToken>`.
|
||||
|
||||
Endpoints exposed by the server include `/api/status`, `/api/editor_status`, `/api/dialog`, `/api/commands` (lists available commands), `/api/exec` (POST — runs a command), `/api/test-status`, and `/api/progress`.
|
||||
|
||||
### Command progress (`GET /api/progress`)
|
||||
|
||||
While a command is executing over `/api/exec`, clients can poll `GET /api/progress` for the
|
||||
task's live progress — the `unity` CLI uses this to render terminal progress bars, mirroring
|
||||
`EditorUtility.DisplayProgressBar`. The endpoint is served off the Editor main thread, so it
|
||||
answers even while a long synchronous command has the Editor blocked. Response
|
||||
(`pct` is 0–1; all `progress` fields optional; `progress` is omitted when nothing is reported):
|
||||
|
||||
```json
|
||||
{
|
||||
"active": true,
|
||||
"progress": {
|
||||
"title": "Generating World",
|
||||
"info": "Processing 42/100",
|
||||
"current": 42,
|
||||
"total": 100,
|
||||
"pct": 0.42
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Progress sources, in order of precedence:
|
||||
|
||||
1. `CliProgress.Report(title, info, current, total, progress)` — explicit reporting from
|
||||
command code; thread-safe and works from a blocked main thread.
|
||||
2. Running `UnityEditor.Progress` items, mirrored automatically.
|
||||
|
||||
### Detached jobs (`/api/exec` with `"job": true`, `/api/job`, `/api/job/cancel`)
|
||||
|
||||
A long command can outlive the client's HTTP timeout. Submitting it as a **detached job**
|
||||
returns a job id immediately; the command runs in the background (still one at a time, in
|
||||
arrival order) and the client polls for the result — reattaching at any point:
|
||||
|
||||
1. `POST /api/exec` with body `{"command": "…", "parameters": {…}, "job": true}` → the
|
||||
standard exec envelope, with the job handle as its `result`:
|
||||
`{"success": true, "result": {"jobId": "…", "state": "queued"}}` (lean by default —
|
||||
add `"verbose": true` to get the `command`/`executedAt` metadata back, see AUTHAPI-21).
|
||||
2. `GET /api/job?id=<jobId>` → `{"jobId", "command", "state":
|
||||
"queued|running|completed|failed|canceled", "progress": {…}, "result", "error", …}`.
|
||||
`progress` mirrors `/api/progress` while the job runs; `result` is retained after
|
||||
completion (last 100 terminal jobs, 1 hour) so it can be fetched repeatedly.
|
||||
3. `POST /api/job/cancel` with body `{"id": "<jobId>"}` — a queued job is canceled before it
|
||||
starts; a running job gets a cooperative cancellation flag that command or `eval` code can
|
||||
observe via `Unity.Pipeline.PipelineCancellation.ThrowIfCancellationRequested()` (arbitrary
|
||||
synchronous code cannot be aborted from outside).
|
||||
|
||||
At most 100 jobs may be queued or running at once (jobs execute strictly serially, so a deeper
|
||||
backlog is pure queued work with no benefit) — submitting one beyond that returns `429`.
|
||||
|
||||
Jobs live in Editor memory: they do not survive domain reloads (script recompilation).
|
||||
|
||||
Editor code that already calls `EditorUtility.DisplayProgressBar` can switch to the drop-in
|
||||
`CliEditorProgress.DisplayProgressBar` / `ClearProgressBar` wrappers to keep the Editor dialog
|
||||
and gain CLI visibility. See [Creating commands](creating-commands.md).
|
||||
|
||||
### Readiness and the settle window
|
||||
|
||||
On a **cold project import** the editor server comes up (and its descriptor is written) while the Editor is still importing assets and compiling scripts, so the Editor is not yet able to service commands. Until the Editor is first seen idle after server start:
|
||||
|
||||
- `/api/status` reports `"status": "settling"` instead of `"ready"`. Wait for `ready` before issuing commands.
|
||||
- `/api/exec` rejects **main-thread** commands with **HTTP 503** and a structured, retryable envelope — distinguishable from a genuine command failure. The gate applies before execution *and* before a detached job (`"job": true`) is created, so a job can't run into the half-ready Editor in the background either. The busy reply is a standard exec envelope and follows the request's reply-shape flags: lean (the default, shown here) drops the envelope metadata; `"verbose": true` restores the `command` echo and `executedAt`:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Server Busy",
|
||||
"errorDetails": "The Editor is still settling after startup (importing assets / compiling scripts), so main-thread commands are not serviceable yet. Retry shortly, or poll /api/status until it reports 'ready'.",
|
||||
"status": "busy",
|
||||
"busyReason": "settling",
|
||||
"retryable": true
|
||||
}
|
||||
```
|
||||
|
||||
`busyReason` is the specific cause, matching `/api/status`'s own `status` values for the same conditions (`"settling"`, `"blocked_by_dialog"`) — branch on it directly rather than inferring the cause from whether `dialogs` happens to be populated.
|
||||
|
||||
- Background commands (`recompile_status`, `package_status`, `console`, ...) and `editor_status` stay servable throughout, so progress remains observable.
|
||||
|
||||
The settle gate is one-way and scoped to the **editor session**: once the Editor has been idle once after startup, the server reports `ready` and the gate never arms again for that session — including for server instances recreated by domain reloads and for servers started while a mid-session compile/import happens to be in flight. Warm starts settle immediately; only the cold-import window gates.
|
||||
|
||||
### Modal dialogs and the dialog-busy gate
|
||||
|
||||
A native modal dialog (e.g. "Save changes before quitting?") or an `EditorWindow` modal blocks the Editor's main thread inside a nested OS message loop, so no main-thread command can run until it's dismissed. The server mirrors these dialogs from `UnityEditor.EditorDialogEvents`:
|
||||
|
||||
- `GET /api/dialog` reports the currently open dialog(s): `{"active": bool, "dialogs": [...]}`, each entry shaped `{id, source, title, message, level, buttons, openedAt, dismissedAt}`.
|
||||
- `/api/status` reports `"status": "blocked_by_dialog"` (instead of `"ready"`) with a `"dialog"` key carrying that same shape for the first open dialog — so the cheapest, most commonly polled probe doesn't claim readiness while every main-thread command is actually gated.
|
||||
- `/api/exec` rejects any **main-thread** command with **HTTP 503**, the same busy envelope shape as the settle gate's but with `"busyReason": "blocked_by_dialog"` and a `"dialogs"` array attached.
|
||||
|
||||
**Exception: `editor_status`.** Unlike every other main-thread command, `editor_status` still returns a normal **200** while a dialog is open, served from a snapshot taken the instant before the dialog blocked the thread — nothing it reports (compiling, domain reload, play mode) can change while the thread that would update it is stuck. The response carries `"status": "blocked_by_dialog"` and the same `"dialog"` payload as `/api/status`, so a caller can still tell it apart from a genuinely idle Editor.
|
||||
|
||||
- A successful `/api/exec` response may carry `"dialogsDuringExecution"`: dialog(s) that opened and/or closed while the command was actually *executing* (not while it sat queued behind another command), so a caller learns about them without polling `/api/dialog` concurrently.
|
||||
|
||||
Coverage is mechanism-based: native message boxes and `EditorWindow` modals are covered; OS file/folder pickers and other dialog mechanisms are not — a caller still needs a fallback signal (e.g. a command timeout) for uncovered cases.
|
||||
|
||||
`/api/commands` accepts optional query parameters for discovery:
|
||||
|
||||
- `detail` — `full` (the **default**) returns the complete command metadata including `parameters` and the generated `schema`; `compact` returns a lightweight index per command (`name`, `description`, `tags`, `package`). The recommended discovery flow is to browse the compact index first, then request full detail only for the few commands you intend to invoke.
|
||||
- `query` — case-insensitive substring match on a command's name, description, or any tag.
|
||||
- `tag` — scope results to a tag subtree via segment-aware prefix match (`tag=assets` matches `assets` and `assets/import`, not `assetsx`).
|
||||
- `group_by` — `flat` (the default) returns a `commands` array; `package` or `tag` return a `groups` array instead (`tag` is a nested tree mirroring the tag/subtag hierarchy; untagged commands land in a node with an empty tag).
|
||||
- `sort` — `name` (the default) or `package` (originating package, with name as tiebreak).
|
||||
- `order` — `asc` (the default) or `desc`. Applies to the chosen `sort`; sorting happens on the flat list before pagination and grouping.
|
||||
- `offset` / `limit` — paginate the filtered, sorted list. Both apply to the flat list *before* grouping, so pages stay deterministic. `offset` skips that many matches (default `0`); `limit` caps how many are returned (default: no cap). A client has seen everything once `offset + count` reaches `total`.
|
||||
|
||||
Filters combine with AND; a filter that matches nothing returns an empty result, not an error. An invalid `detail`, `group_by`, `sort`, `order`, `offset`, or `limit` value is rejected with `400` naming the accepted values.
|
||||
|
||||
### `/api/commands` response shape
|
||||
|
||||
Every response carries the pagination counters plus a `server` block. The command payload is either a flat `commands` array or — under `group_by` — a `groups` array in its place:
|
||||
|
||||
| Field | Meaning |
|
||||
|---|---|
|
||||
| `commands` | The page of commands. Present only when `group_by=flat` (the default). |
|
||||
| `groups` | Present *instead of* `commands` when `group_by=package` or `group_by=tag`. |
|
||||
| `count` | How many commands this page actually returned. |
|
||||
| `total` | How many commands matched the filters, **before** `offset`/`limit` were applied. |
|
||||
| `offset` | Echo of the requested `offset` (`0` when not supplied). |
|
||||
| `limit` | Echo of the requested `limit` (`null` when not supplied — no cap). |
|
||||
| `server` | The responding server's `version`, `port`, and `startTime`. |
|
||||
|
||||
`GET /api/commands?detail=compact&tag=baking/lighting&limit=2` — `count` is the 2 returned here, while `total` is all 6 commands under `baking/lighting`, so the next page is `offset=2`:
|
||||
|
||||
```json
|
||||
{
|
||||
"commands": [
|
||||
{
|
||||
"name": "bake_lighting",
|
||||
"description": "Trigger an async lightmap bake of the open scene(s) via Lightmapping.BakeAsync(). Returns immediately; poll lighting_bake_status until completed.",
|
||||
"tags": ["baking/lighting"],
|
||||
"package": "Unity.Pipeline.Editor"
|
||||
},
|
||||
{
|
||||
"name": "cancel_lighting_bake",
|
||||
"description": "Cancel an in-progress lighting bake (Lightmapping.Cancel()).",
|
||||
"tags": ["baking/lighting"],
|
||||
"package": "Unity.Pipeline.Editor"
|
||||
}
|
||||
],
|
||||
"count": 2,
|
||||
"total": 6,
|
||||
"offset": 0,
|
||||
"limit": 2,
|
||||
"server": {
|
||||
"version": "0.0.1",
|
||||
"port": 54321,
|
||||
"startTime": "2026-07-29T09:14:22.113Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Under `group_by=tag` the same envelope carries a nested `groups` tree instead. Two things to note. A node's `commands` holds only the commands tagged *exactly* at that node — so `baking`, whose commands all live in subtags, reports an empty array — while its `count` covers the node's whole **subtree**. And because pagination happens before grouping, group counts describe the returned page rather than every match: `GET /api/commands?detail=compact&tag=baking&group_by=tag&limit=2` groups only the 2 commands on this page, while `total` still reports all 17 matches under `baking`:
|
||||
|
||||
```json
|
||||
{
|
||||
"groups": [
|
||||
{
|
||||
"tag": "baking",
|
||||
"count": 2,
|
||||
"commands": [],
|
||||
"children": [
|
||||
{
|
||||
"tag": "baking/lighting",
|
||||
"count": 1,
|
||||
"commands": [
|
||||
{
|
||||
"name": "bake_lighting",
|
||||
"description": "Trigger an async lightmap bake of the open scene(s) via Lightmapping.BakeAsync(). Returns immediately; poll lighting_bake_status until completed.",
|
||||
"tags": ["baking/lighting"],
|
||||
"package": "Unity.Pipeline.Editor"
|
||||
}
|
||||
],
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"tag": "baking/navmesh",
|
||||
"count": 1,
|
||||
"commands": [
|
||||
{
|
||||
"name": "bake_navmesh",
|
||||
"description": "Trigger an async legacy NavMesh bake of the open scene(s) via UnityEditor.AI.NavMeshBuilder. Returns immediately; poll navmesh_bake_status until completed.",
|
||||
"tags": ["baking/navmesh"],
|
||||
"package": "Unity.Pipeline.Editor"
|
||||
}
|
||||
],
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"count": 2,
|
||||
"total": 17,
|
||||
"offset": 0,
|
||||
"limit": 2,
|
||||
"server": {
|
||||
"version": "0.0.1",
|
||||
"port": 54321,
|
||||
"startTime": "2026-07-29T09:14:22.113Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`group_by=package` uses the same envelope with flatter nodes — `{ "package": "Unity.Pipeline.Editor", "count": 2, "commands": [ ... ] }`, with no `children`.
|
||||
|
||||
## Executing a command (`POST /api/exec`)
|
||||
|
||||
A request carries the command in **exactly one** of three forms:
|
||||
|
||||
```jsonc
|
||||
{"command": "reload_file", "parameters": {"path": "Assets/A.cs"}} // structured
|
||||
{"commandLine": "reload_file Assets/A.cs"} // server tokenizes, then binds
|
||||
{"argv": ["eval", "return 2+2;"]} // server binds directly
|
||||
```
|
||||
|
||||
For the two raw forms the **first token is the command name**, and the server binds the rest
|
||||
against the command's declared parameters. This is what lets a client execute a command in a
|
||||
single request without knowing its schema — the structured form needs `GET /api/commands` first,
|
||||
the raw forms need nothing.
|
||||
|
||||
**Prefer `argv` whenever you already have split tokens.** On POSIX a CLI never holds the original
|
||||
command *string* — the shell already split and unquoted argv — so re-quoting those tokens for the
|
||||
server to re-split is a lossy round-trip across two independently-versioned quoting dialects. Use
|
||||
`commandLine` only when you genuinely hold raw text (a chat message, an MCP argument, a pasted
|
||||
line).
|
||||
|
||||
Supplying more than one form is an error, and so is sending `parameters` alongside `commandLine`
|
||||
or `argv`. The latter is **rejected rather than ignored**: silently dropping a payload makes "you
|
||||
sent it and we discarded it" indistinguishable from "you never sent it".
|
||||
|
||||
`argv` and `commandLine` are exec-only. `batch` operations keep `command` + `params`.
|
||||
|
||||
### Tokenizer dialect (`commandLine` only)
|
||||
|
||||
This is a **versioned contract**, not an implementation detail. It is the pipeline's own dialect —
|
||||
identical on every OS, and deliberately *not* the caller's shell — so the same string tokenizes
|
||||
the same way from cmd.exe, bash, an MCP client, or a chat box.
|
||||
|
||||
| Rule | Example | Tokens |
|
||||
|---|---|---|
|
||||
| Whitespace separates tokens; runs collapse | `a b` | `a`, `b` |
|
||||
| `"…"` groups, `\` escapes the next character | `"a b"`, `"a\"b"` | `a b`, `a"b` |
|
||||
| `'…'` groups, with **no** escapes at all | `'a\b'` | `a\b` |
|
||||
| `\` escapes outside quotes | `a\ b` | `a b` |
|
||||
| Adjacent spans concatenate | `a"b c"d` | `ab cd` |
|
||||
| `""` yields one **empty** token | `--message ""` | `--message`, *(empty)* |
|
||||
| Unbalanced quote or dangling `\` | `"oops` | **error** |
|
||||
|
||||
There is **no expansion of any kind** — no `$VAR`, globbing, `~`, comments, or operators. `&&`,
|
||||
`|` and `>` are ordinary characters.
|
||||
|
||||
Two consequences worth stating explicitly:
|
||||
|
||||
- **Single quotes are load-bearing.** They pass double quotes through untouched, which is the only
|
||||
practical way to send `eval 'obj.name = "x";'` or `batch --operations '[{…}]'`.
|
||||
- **Windows `CommandLineToArgvW` rules do not apply.** `"a""b"` is `ab` here, not `a"b`. The usual
|
||||
argument for Windows rules — that `\` is the path separator — does not hold, because every path
|
||||
this API accepts is a forward-slashed Unity asset path.
|
||||
|
||||
### Argument grammar (both raw forms)
|
||||
|
||||
- `--key value`, `--key=value`, or a bare `--key` (which means `true`, and is an error on a
|
||||
non-boolean parameter).
|
||||
- A follower is consumed as the value **unless it starts with `--`**, so `--path -foo` works.
|
||||
- `--` ends flag parsing; everything after it is a positional value, exempt from the `-foo` and
|
||||
`key=value` checks.
|
||||
- Positionals fill **required parameters in declaration order, then optional parameters in
|
||||
declaration order**. A slot already filled by an explicit flag is skipped, not overwritten.
|
||||
- Not supported, by design: short flags, `-abc` bundling, `--no-x`, repeated-flag-as-array,
|
||||
flag-name abbreviation, and `@argfile`.
|
||||
|
||||
> **Declaration order is wire API for raw clients.** Reordering a command's parameters — or
|
||||
> flipping one between required and optional — silently changes what a positional binds to. Append
|
||||
> new optional parameters at the end. See [Creating commands](creating-commands.md).
|
||||
|
||||
### Successful replies echo the bound parameters
|
||||
|
||||
```json
|
||||
{"success": true, "result": "pong", "parameters": {"message": "hi"}}
|
||||
```
|
||||
|
||||
`parameters` is present **only** for `argv`/`commandLine` requests, so structured replies are
|
||||
unchanged. It reports exactly what the binder stored, so a client that no longer binds locally can
|
||||
still show the user what the server understood.
|
||||
|
||||
### Argument errors
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Invalid Command Arguments",
|
||||
"errorCode": "INVALID_COMMAND_ARGS",
|
||||
"errorDetails": "log_editor has no parameter --mesage. Did you mean --message?",
|
||||
"argProblems": [{"kind": "unknownName", "name": "mesage", "suggestion": "message"}],
|
||||
"commandSchema": {"name": "log_editor", "parameters": [{"name": "message", "type": "String", "required": true}]}
|
||||
}
|
||||
```
|
||||
|
||||
**`errorCode` is the discriminator, not the HTTP status.** Every `/api/exec` failure is already
|
||||
`400`, so status alone cannot separate "your arguments are wrong" from "the command threw".
|
||||
|
||||
| `errorCode` | Meaning |
|
||||
|---|---|
|
||||
| `INVALID_COMMAND_ARGS` | The command resolved, but its arguments could not be bound. Nothing executed. |
|
||||
| *(absent)* | Any other failure — an unknown command, a validation error, or the command itself throwing. |
|
||||
|
||||
A malformed *request shape* — two command forms at once, `parameters` beside `argv`, an empty
|
||||
`argv`, a `commandLine` that does not tokenize (unbalanced quote, dangling backslash), or a body
|
||||
using none of the three forms — is a `400` with `error: "Invalid Request"` and **no** `errorCode`,
|
||||
because no command was ever resolved. Such a reply carries neither `argProblems` nor
|
||||
`commandSchema`: there is no command to describe.
|
||||
|
||||
`argProblems` is machine-readable and **accumulated**: every defect is reported, not just the
|
||||
first, so a user fixing a command line sees all of its problems at once. It is deliberately not
|
||||
pre-rendered English — the `unity` CLI renders *and localizes* these itself. Use `errorDetails` as
|
||||
the English fallback, and for any `kind` your client does not recognize.
|
||||
|
||||
| `kind` | Meaning |
|
||||
|---|---|
|
||||
| `emptyName` | A flag with no name: `--=` or `--=value`. A bare `--` is the end-of-flags separator, never this. |
|
||||
| `emptyValue` | `--key=` with nothing after it, or a bare `--key` on a non-boolean parameter. |
|
||||
| `singleDash` | A `-x` token in positional position. |
|
||||
| `duplicate` | The same `--key` supplied more than once. |
|
||||
| `unknownName` | No such parameter. Carries `suggestion` when a close match exists. |
|
||||
| `bareAssignment` | A `key=value` positional whose left side names *the parameter that positional fills* — a misremembered `--key value`. A left side naming some other parameter is a value: `eval "timeout=5;"` binds as `code`. |
|
||||
| `excessPositional` | More positionals than there are slots left for them. `capacity` is the slots still free after the flags took theirs and `given` is the positionals supplied; `token` is the first one with no slot. |
|
||||
| `positionalConflict` | A positional whose own slot a flag already filled. Carries `name` (that parameter) and `token`. |
|
||||
| `typeMismatch` | The declared type cannot accept the token; carries `expectedType`. |
|
||||
|
||||
`commandSchema` is the command's catalog entry, shaped like a `/api/commands` `commands[]`
|
||||
element (without the generated `schema`). It lets a client print usage without a separate schema
|
||||
fetch.
|
||||
|
||||
### Capability negotiation
|
||||
|
||||
Servers advertise optional wire features in `capabilities`, both on `GET /api/status` and in the
|
||||
**port descriptor**:
|
||||
|
||||
```json
|
||||
"capabilities": ["exec.argv", "exec.commandLine"]
|
||||
```
|
||||
|
||||
The descriptor is the carrier that matters: clients already read it locally to obtain `evalToken`,
|
||||
so negotiation costs **zero extra requests**.
|
||||
|
||||
The rule for clients:
|
||||
|
||||
- Token present → send that form.
|
||||
- Descriptor present but **no `capabilities` key at all** → the server predates this feature. Do
|
||||
not send raw forms; tell the user to update the package.
|
||||
- Never sniff first. An older server receiving `{"argv":[…]}` fails deserialization (its `command`
|
||||
field is required) and answers `400 {"error":"Invalid JSON"}` — which a client would otherwise
|
||||
surface as a generic internal failure, making version skew look like a client bug.
|
||||
|
||||
Because the descriptor file is writable by anything running as the user, treat a capability claim
|
||||
as a hint rather than a guarantee: a `400` carrying `error: "Invalid JSON"` or `"Invalid Request"`
|
||||
with **no** `errorCode`, in response to a request that used `argv`, means the same thing as an
|
||||
absent key. Degrade, don't crash.
|
||||
|
||||
## See also
|
||||
|
||||
- [Runtime connection & setup](runtime-setup.md) — enabling the server in a Player build.
|
||||
- [Creating commands](creating-commands.md) — authoring the commands clients call.
|
||||
@@ -0,0 +1,347 @@
|
||||
# Creating commands
|
||||
|
||||
A *command* is a `static` method that the Pipeline server can invoke over HTTP. The method's accessibility does not matter — `public`, `internal`, and `private` static methods can all be registered. This page covers the command-authoring API: how to declare a command, describe its parameters, return a result, and have it discovered automatically.
|
||||
|
||||
## The handler + response pattern
|
||||
|
||||
Authoring a command has two halves:
|
||||
|
||||
1. **The handler** — your `static` method, tagged `[CliCommand]`. It does the work and returns a value.
|
||||
2. **The response** — the server wraps whatever your handler returns in a [`CommandExecutionResponse`](#the-commandexecutionresponse-envelope) and serializes it to JSON for the client.
|
||||
|
||||
You never build the HTTP response yourself. Return a `string`, a number, an anonymous object, a typed model, or `null`; the server takes care of the envelope, timing, and error reporting.
|
||||
|
||||
## Declaring a command
|
||||
|
||||
Tag a `static` method with `[CliCommand]` (the examples below use `public`, but any accessibility works):
|
||||
|
||||
```csharp
|
||||
[CliCommand("name", "description", MainThreadRequired = true, RuntimeOnly = false)]
|
||||
public static <ReturnType> Handler(...);
|
||||
```
|
||||
|
||||
| Argument | Meaning |
|
||||
|----------|---------|
|
||||
| `name` | Unique command name used by the client (`unity command <name>`). |
|
||||
| `description` | Human-readable text shown in help and the `/api/commands` listing. |
|
||||
| `MainThreadRequired` | Whether the handler must run on Unity's main thread. **Default `true`.** |
|
||||
| `RuntimeOnly` | Whether the command is hidden from an Editor server's command listing. **Default `false`.** |
|
||||
| `Tags` | Optional hierarchical tags for grouping/browsing commands. Path-style: a `/` separates tag from subtag (e.g. `"assets"`, `"assets/import"`). **Default: empty.** |
|
||||
|
||||
Every discovered command also carries the **package** it originates from (derived from the declaring assembly, e.g. `Unity.Pipeline.Editor`). Tags and package appear in both the compact and full `/api/commands` listings — `detail=full` (the default) includes the parameters and generated JSON schema, while `detail=compact` returns just the lightweight index (`name`, `description`, `tags`, `package`). Tags also drive the endpoint's `tag` filter and its `group_by=tag` tree (see [Connectivity](connectivity.md)).
|
||||
|
||||
The method **must be `static`**, but its accessibility does not matter: `public`, `internal`, and `private` static methods can all be registered. `CommandRegistry` invokes handlers through reflection, so a `private` handler runs exactly like a `public` one. Only a non-static (instance) method fails to register — `CommandRegistry` skips it and logs a warning. To expose an instance method as a command, register it dynamically instead (see [Registering a command dynamically](#registering-a-command-dynamically)).
|
||||
|
||||
### Tag taxonomy
|
||||
|
||||
Every shipped command carries at least one tag (enforced by `CommandRegistrationTests.CommandRegistry_DiscoverCommands_AllShippedCommandsCarryTags`). Tags are lowercase `/`-separated paths. Pick from the established top-level tags before inventing a new one, and add a subtag when a family is large enough to browse on its own:
|
||||
|
||||
| Top-level tag | Covers | Subtags in use |
|
||||
|---------------|--------|----------------|
|
||||
| `animation` | Animation authoring | `animator`, `clip`, `timeline` |
|
||||
| `assets` | Asset CRUD and search | `import`, `text` |
|
||||
| `authoring` | Authoring-root configuration | — |
|
||||
| `batch` | Transactional multi-command execution (`batch`) | — |
|
||||
| `baking` | Bake pipelines | `lighting`, `navmesh`, `occlusion` |
|
||||
| `build` | Player builds | `settings`, `targets` |
|
||||
| `capture` | Screenshots and view/element capture | — |
|
||||
| `editor` | Editor application control | `playmode` |
|
||||
| `gameobjects` | Scene GameObject manipulation | `components` |
|
||||
| `materials` | Materials | `shaders` |
|
||||
| `navigation` | Selection and search | — |
|
||||
| `observability` | Logs and diagnostics | `console`, `performance` |
|
||||
| `packages` | UPM package management | — |
|
||||
| `prefabs` | Prefab workflows | — |
|
||||
| `runtime` | Player-only application control | `application`, `input` |
|
||||
| `scenes` | Scene lifecycle and hierarchy | — |
|
||||
| `scripts` | C# source workflows | `compile`, `eval`, `codereload` |
|
||||
| `settings` | Project settings | `audio`, `graphics`, `input`, `physics`, `player`, `quality`, `tags_layers`, `time` |
|
||||
| `tests` | Test runner | — |
|
||||
| `ui` | UI element workflows | — |
|
||||
| `wait` | Server-side condition waiting | — |
|
||||
|
||||
A command may carry multiple tags when it genuinely belongs to two families (e.g. `add_scene_to_build` is tagged `scenes` and `build/settings`).
|
||||
|
||||
### Describing parameters
|
||||
|
||||
Tag each parameter with `[CliArg]`:
|
||||
|
||||
```csharp
|
||||
[CliArg("name", "description", Required = false, DefaultValue = null)]
|
||||
```
|
||||
|
||||
| Property | Meaning |
|
||||
|----------|---------|
|
||||
| `name` | Parameter name as it appears in client arguments (`--name value`). |
|
||||
| `description` | Human-readable description for help text. |
|
||||
| `Required` | Whether the parameter must be supplied. Defaults to `false` — but if the parameter has no C# default value it is treated as required. |
|
||||
| `DefaultValue` | Value used when the client omits the parameter (a C# default value takes precedence). |
|
||||
|
||||
`[CliArg]` is optional metadata. A parameter without it still works: its name defaults to the C# parameter name and `Required` defaults to "does this parameter lack a C# default value?".
|
||||
|
||||
#### Declaration order and `Required` are wire API
|
||||
|
||||
Clients may send an unparsed command line (`argv`/`commandLine` on `/api/exec`), and the server
|
||||
binds positionals into **required parameters in declaration order, then optional parameters in
|
||||
declaration order**. Three consequences for command authors:
|
||||
|
||||
- **Reordering parameters, or flipping one between required and optional, is a breaking change**
|
||||
for those clients even though the C# signature still compiles. Append new optional parameters at
|
||||
the end.
|
||||
- **Enum parameters are validated by name** (case-insensitively), and an invalid value is rejected
|
||||
with the legal set echoed back — you get that for free, no attribute needed. A `[Flags]` enum
|
||||
also takes a comma-separated combination (`--channels Info,Warning`), and a numeric value is
|
||||
accepted, because validation defers to the same converter the executor uses.
|
||||
- **Structured (`IStructuredCommandInput` / `JObject`) parameters are reachable from a command
|
||||
line only as a JSON-valued flag**, e.g. `--payload '{"name":"x"}'`. Single quotes are the
|
||||
practical form; see the tokenizer dialect in [Connectivity](connectivity.md).
|
||||
|
||||
A token the declared type cannot accept is now a hard `400` on the raw path, rather than the
|
||||
parameter silently dropping out and the command running with a default.
|
||||
|
||||
## Worked example 1 — returning a string
|
||||
|
||||
A command can return a plain `string`. The server places it in the response `result` field.
|
||||
|
||||
```csharp
|
||||
using Unity.Pipeline.Commands;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
public static class PlayModeCommands
|
||||
{
|
||||
[CliCommand("editor_play", "Enter Unity Editor play mode")]
|
||||
public static string EnterPlayMode()
|
||||
{
|
||||
if (EditorApplication.isPlaying)
|
||||
return "Already in play mode";
|
||||
|
||||
EditorApplication.isPlaying = true;
|
||||
return "Entered play mode";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Calling `editor_play` yields a `CommandExecutionResponse` whose `result` is `"Entered play mode"`.
|
||||
|
||||
## Worked example 2 — returning a response model
|
||||
|
||||
For richer results, return a model. Extending `CommandExecutionResponse` lets you populate response fields directly; you can also return any plain serializable model (like `AuthoringResult`) and let the server wrap it.
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using Unity.Pipeline.Commands;
|
||||
using Unity.Pipeline.Models;
|
||||
|
||||
public class WordCountResponse : CommandExecutionResponse
|
||||
{
|
||||
public int WordCount { get; set; }
|
||||
|
||||
public static WordCountResponse Counted(int wordCount) => new WordCountResponse
|
||||
{
|
||||
Success = true,
|
||||
WordCount = wordCount
|
||||
};
|
||||
|
||||
public static WordCountResponse Failed(string error) => new WordCountResponse
|
||||
{
|
||||
Success = false,
|
||||
Error = error
|
||||
};
|
||||
}
|
||||
|
||||
public static class TextCommands
|
||||
{
|
||||
[CliCommand("word_count", "Count the words in a string")]
|
||||
public static WordCountResponse CountWords(
|
||||
[CliArg("text", "Text to count words in", Required = true)] string text)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
return WordCountResponse.Failed("text parameter is required and cannot be empty");
|
||||
|
||||
return WordCountResponse.Counted(text.Split((char[])null, StringSplitOptions.RemoveEmptyEntries).Length);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`WordCountResponse` adds `wordCount` on top of the standard envelope. Another common shape is a domain model such as `AuthoringResult` — the canonical identity (asset path, GUID, instance id, hierarchy path) of an object a command created, returned so the client can reference it in a follow-up call.
|
||||
|
||||
## Structured (multi-field) parameters
|
||||
|
||||
When a command needs a structured argument with several fields, don't spread them across many `[CliArg]` parameters — declare a small DTO that implements `IStructuredCommandInput` and take it as a single parameter. The type is advertised to clients as a nested JSON **object** schema in `GET /api/commands` (instead of collapsing to `string`), and the value is deserialized automatically via Newtonsoft — no extra wiring.
|
||||
|
||||
```csharp
|
||||
[CliCommand("set_time_settings", "Change Time settings. Requires confirm=true; use dry_run to preview.")]
|
||||
public static CommandExecutionResponse Set(
|
||||
[CliArg("settings", "Fields to change; omitted fields are left unchanged.")] TimeSettingsArgs settings = null,
|
||||
[CliArg("confirm", "Apply the change. Without it the call is refused.")] bool confirm = false,
|
||||
[CliArg("dry_run", "Preview the change without applying it.")] bool dryRun = false)
|
||||
{ /* ... */ }
|
||||
|
||||
public class TimeSettingsArgs : IStructuredCommandInput
|
||||
{
|
||||
[CliArg("fixedDeltaTime", "Fixed timestep in seconds (e.g. 0.02).")]
|
||||
public float? FixedDeltaTime { get; set; }
|
||||
|
||||
[CliArg("timeScale", "Time scale (1 = real-time).")]
|
||||
public float? TimeScale { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
`JsonSchemaGenerator` reflects over the type's public, writable fields and properties to emit `{ "type": "object", "properties": { ... } }`, recursing into nested `IStructuredCommandInput` members and arrays/lists of them. Member metadata mirrors command parameters:
|
||||
|
||||
- `[CliArg(name, description, Required = ...)]` controls the property name, description, and whether it appears in the schema's `required` array.
|
||||
- Without a `[CliArg]`, the member (or its Newtonsoft `[JsonProperty]`) name is used and it is optional. Use nullable types (`float?`) for "omitted = leave unchanged" semantics.
|
||||
- `[JsonIgnore]` members are omitted from the schema.
|
||||
|
||||
`[CliArg]` is valid on parameters, fields, and properties, so the same attribute annotates DTO members. Most commands that take an `IStructuredCommandInput` are mutations — pair the DTO with `confirm`/`dry_run` and follow the [safety conventions](safety-and-mutations.md) (inline gate + `AuthoringUndoScope`).
|
||||
|
||||
## The `CommandExecutionResponse` envelope
|
||||
|
||||
Whatever your handler returns, the client receives a `CommandExecutionResponse`:
|
||||
|
||||
| Field | Type | Meaning | In lean reply? |
|
||||
|-------|------|---------|----------------|
|
||||
| `success` | `bool` | Whether the command ran without throwing. | always |
|
||||
| `result` | `object` | Your handler's return value (a string, model, anonymous object, or `null`). | always on success, explicitly `null` when null; omitted on failure |
|
||||
| `error` | `string` | Error summary when `success` is `false`. | when non-null |
|
||||
| `errorDetails` | `string` | Extra error context. | when non-null |
|
||||
| `warnings` | `string[]` | Corrective guidance the server wants the caller to see (e.g. an unrecognized option value). | when non-null (omitted when there is nothing to say, in every mode) |
|
||||
| `command` | `string` | The command name. | verbose only |
|
||||
| `executedAt` | `DateTime` | When the reply was produced. | verbose only |
|
||||
| `executionTimeMs` | `long?` | How long the command took. | verbose only |
|
||||
|
||||
The envelope is **lean by default** (AUTHAPI-21): serialized compact (no indentation), with the
|
||||
envelope's own null keys omitted and the always-on metadata (`command`, `executedAt`,
|
||||
`executionTimeMs`) dropped. A minimal success is just `{"success":true,"result":...}`. Agents
|
||||
consume the JSON directly; a client can pretty-print for a human if needed.
|
||||
|
||||
The boundaries of the lean contract, worth knowing:
|
||||
|
||||
- **Only the envelope's nulls are omitted; your `result` payload keeps explicit nulls.** The
|
||||
envelope's null keys are redundant (`success` already disambiguates: `error` is null on success),
|
||||
so they are dropped. Inside `result` the opposite holds — a scene object's `AuthoringResult`
|
||||
still carries `"assetPath": null` — because an absent payload key would be indistinguishable
|
||||
from a nonexistent or misspelled one.
|
||||
- **A successful reply always carries `result`, even when its value is null — in every mode.** A
|
||||
null result on success is the command's actual value (e.g. a `format="value"` read of an
|
||||
unassigned object-reference field), never a droppable envelope null. Only a *failure* omits the
|
||||
`result` key.
|
||||
- **The metadata gating applies to the whole graph.** A command whose result is itself a response
|
||||
model (e.g. `eval` returns an `EvalResponse` nested under `result`) has its nested
|
||||
`executedAt`/`executionTimeMs` stripped in lean mode too — and restored by verbose along with
|
||||
everything else.
|
||||
|
||||
Two independent request flags adjust the shape:
|
||||
|
||||
- `"verbose": true` — full fidelity back: every envelope field, explicit nulls, and nested response
|
||||
metadata, for debugging or correlation. Honored on request-validation failures too.
|
||||
- `"omitNulls": true` — drop null keys from the whole reply **inside `result`** (the success
|
||||
`result` key itself always survives, see above). Opt in only when the result schema is already
|
||||
known and the nulls are pure bytes — the payoff case is bulk list reads (e.g. `list_shaders`
|
||||
over hundreds of built-ins repeating `"assetPath": null` per item).
|
||||
|
||||
The `GET`-style endpoints (`/api/job`, `/api/progress`) have no envelope/payload split — every field
|
||||
is payload — so they include null keys explicitly by default and accept `omit_nulls=true` as a query
|
||||
parameter instead. An unrecognized value (e.g. `omit_nulls=1`) is not silently coerced: the reply
|
||||
keeps its nulls and carries a `warnings` array naming the accepted values, so an agent can correct
|
||||
itself instead of guessing.
|
||||
|
||||
If your handler throws, the server catches it and returns a failure envelope with `success = false` and the exception message in `error` — you do not need to catch-and-wrap yourself unless you want a tailored message.
|
||||
|
||||
## `MainThreadRequired`
|
||||
|
||||
- **Default: `true`.** Most Unity APIs (scene, GameObject, asset, play-mode access) must run on the main thread. The server marshals these handlers onto the main thread via its dispatcher.
|
||||
- **Set `false` only** for handlers that are thread-safe and read-only / pollable (e.g. a status or buffer-read command). These run on a background thread so they never block the main thread or deadlock against a busy editor.
|
||||
|
||||
When in doubt, leave it `true`.
|
||||
|
||||
## `RuntimeOnly`
|
||||
|
||||
- **Default: `false`** — the command is advertised in an Editor server's `/api/commands` listing.
|
||||
- **Set `true`** to hide a command from the Editor command listing. It remains **executable**; it is simply not advertised when a client is connected to an Editor (Runtime/Player servers still list it). Use this for commands that only make sense against a running Player.
|
||||
|
||||
## Discovery
|
||||
|
||||
Commands are discovered by `CommandRegistry`, which scans for `[CliCommand]`-tagged methods through a pluggable `ICommandDiscovery`:
|
||||
|
||||
- In the **Editor**, `TypeCacheCommandDiscovery` provides fast `TypeCache`-based discovery.
|
||||
- In a **Player**, the registry falls back to reflection over loaded assemblies.
|
||||
|
||||
Results are cached until the next domain reload. **A newly added command becomes available after the next recompile** — no registration call is needed; just declare it and recompile.
|
||||
|
||||
## Registering a command dynamically
|
||||
|
||||
`[CliCommand]` covers commands you know about at compile time. When the set of commands depends on runtime state — a tool that exposes one command per loaded document, a plugin host, a test that needs a throwaway command — register them directly instead:
|
||||
|
||||
```csharp
|
||||
CommandRegistry.RegisterCommand(
|
||||
"my_dynamic_command",
|
||||
"What this command does",
|
||||
new Func<string, int, object>(MyHandler),
|
||||
mainThreadRequired: true,
|
||||
runtimeOnly: false,
|
||||
tags: new[] { "mytool" });
|
||||
```
|
||||
|
||||
The result is an ordinary command: it appears in `/api/commands`, carries the same metadata, and executes through `/api/exec` exactly like an attribute-declared one.
|
||||
|
||||
**Parameters still come from `[CliArg]`.** The registry reads the handler's own parameters, so a handler method annotates them exactly as an attribute-declared command does:
|
||||
|
||||
```csharp
|
||||
private static object MyHandler(
|
||||
[CliArg("text", "Some input", Required = true)] string text,
|
||||
[CliArg("count", "How many times")] int count = 1)
|
||||
{
|
||||
return new { echoed = text, count };
|
||||
}
|
||||
```
|
||||
|
||||
A lambda cannot carry `[CliArg]` on its parameters, so registering one yields default metadata (the C# parameter name, required only when there is no default value). Register a named method when you want full metadata.
|
||||
|
||||
**Instance methods are allowed.** Unlike the attribute path, the handler is a delegate, so it may be bound to an instance — the command then runs against that instance:
|
||||
|
||||
```csharp
|
||||
var session = new MySession(documentId);
|
||||
CommandRegistry.RegisterCommand("read_document", "Read the open document",
|
||||
new Func<string>(session.Read), tags: new[] { "mytool" });
|
||||
```
|
||||
|
||||
**Removing a command:**
|
||||
|
||||
```csharp
|
||||
bool removed = CommandRegistry.UnregisterCommand("my_dynamic_command");
|
||||
```
|
||||
|
||||
`UnregisterCommand` returns `false` when nothing was registered under that name. Attribute-declared commands are part of the code and are never removable, so passing one of their names also returns `false`.
|
||||
|
||||
**Lifetime and collisions.** Registrations live until they are unregistered or the domain reloads — statics reset on reload, so re-register from wherever your code initializes (e.g. `[InitializeOnLoadMethod]`).
|
||||
|
||||
A registration made **while in Play Mode** is also dropped when Play Mode exits, so a script registering from `Awake` or `OnEnable` starts each Play session with a clean registry. This matters when Enter Play Mode has domain reload disabled: without it, the second entry would throw (the name would still be taken by the first entry) and a surviving registration would hold a delegate bound to the previous session's destroyed object. A registration made in **Edit Mode** survives a Play session — an `[InitializeOnLoadMethod]` runs once per domain, so nothing would restore it.
|
||||
|
||||
All registrations survive a discovery-mechanism swap. Registering a name that is already taken, by an attribute-declared command or an earlier dynamic one, throws `InvalidOperationException` rather than shadowing it; unregister first if you mean to replace it.
|
||||
|
||||
## Minimal custom command template
|
||||
|
||||
```csharp
|
||||
using Unity.Pipeline.Commands;
|
||||
|
||||
public static class MyCommands
|
||||
{
|
||||
[CliCommand("my_command", "What this command does")]
|
||||
public static object MyCommand(
|
||||
[CliArg("text", "Some input", Required = true)] string text,
|
||||
[CliArg("count", "How many times")] int count = 1)
|
||||
{
|
||||
// Do work on the main thread (MainThreadRequired defaults to true).
|
||||
return new { echoed = text, count }; // anonymous object → response.result
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Recompile, then invoke it from your client (`unity command my_command --text hello --count 3`).
|
||||
|
||||
## See also
|
||||
|
||||
- [Command reference](commands/runtime.md)
|
||||
- [Connectivity](connectivity.md) — how clients reach the server and authenticate.
|
||||
@@ -0,0 +1,20 @@
|
||||
apiRules:
|
||||
# Everything under Unity.Pipeline.Tests.* (Tests/Editor, Tests/Runtime, and their
|
||||
# sub-namespaces) is test-only. Some of it is forced `public` for a compiler reason, not
|
||||
# because it's consumable package API: e.g. code-reload/IL-interpreter probe classes are named
|
||||
# literally by SourceCodeTransformer in a freshly-generated override that Roslyn compiles into
|
||||
# a separate, uniquely named in-memory assembly (no InternalsVisibleTo exists for those
|
||||
# ephemeral assemblies), so `internal` fails the compile with CS0122. Exempting the whole test
|
||||
# namespace tree means a future probe/fixture that needs the same `public` workaround doesn't
|
||||
# need a new filter.yml entry. This does not affect PVP-150/151 for the shipped Runtime/Editor
|
||||
# assemblies — only Unity.Pipeline.Tests.* is excluded.
|
||||
- exclude:
|
||||
uidRegex: ^Unity\.Pipeline\.Tests\..+$
|
||||
type: Type
|
||||
|
||||
# Decoy attribute (CodeReloadInPlaceTests.cs) that only exists to prove the IL weaver matches
|
||||
# by full name, not simple name — not consumable API, test-only despite living outside the
|
||||
# Unity.Pipeline.Tests namespace.
|
||||
- exclude:
|
||||
uidRegex: ^FakeReload\.CodeReloadAttribute$
|
||||
type: Type
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
@@ -0,0 +1,272 @@
|
||||
# Pipeline Documentation
|
||||
|
||||
`com.unity.pipeline` lets a client — CLI, CI, or an agent — drive a running Unity Editor (or a
|
||||
development Player) over a local HTTP API by executing registered commands.
|
||||
|
||||
## Guides
|
||||
|
||||
| Page | What it covers |
|
||||
|------|----------------|
|
||||
| [Creating commands](creating-commands.md) | The command-authoring API: `[CliCommand]`/`[CliArg]`, the handler + response pattern, and the `MainThreadRequired` / `RuntimeOnly` flags. |
|
||||
| [Creating authoring commands](authoring-commands.md) | Building content-authoring commands: the authoring root sandbox, `ObjectRef` in / `AuthoringResult` out, and undo grouping — with a worked example for a new content type. |
|
||||
| [Safety & mutations](safety-and-mutations.md) | Conventions shared by state-changing commands: the `confirm`/`dry_run` gate, Undo grouping via `AuthoringUndoScope`, and the path sandbox. |
|
||||
| [Connectivity](connectivity.md) | How servers bind localhost, the port ranges, the port/descriptor file, and the bearer-token auth workflow. |
|
||||
| [Runtime connection & setup](runtime-setup.md) | Running the server in a development Player via Project Settings → Pipeline → Runtime and the dev-build gating. |
|
||||
| [Reloading code](code-reload.md) | Updating `[CodeReload]` methods in a running game: the `Assembly.Load` and interpreter backends, supported changes, the watcher, and how the woven dispatch works. |
|
||||
| [Tests architecture](testing.md) | Writing command tests via `PipelineClient` (over HTTP) and via direct command calls. |
|
||||
| [Analytics](analytics.md) | The three usage events the Editor server reports, what each field means, and what is deliberately not collected. |
|
||||
|
||||
## Command reference
|
||||
|
||||
| Page | Commands |
|
||||
|------|----------|
|
||||
| [Asset & file commands](commands/assets-and-files.md) | Create / import / move / copy / rename / delete / find assets, import settings, folders, text files. |
|
||||
| [Scene commands](commands/scenes.md) | Create / open / save scenes, build-settings list, hierarchy, active scene. |
|
||||
| [GameObject & component commands](commands/gameobjects-and-components.md) | Create / find / transform / parent / tag / layer GameObjects; add / remove / read / set components. |
|
||||
| [Prefab commands](commands/prefabs.md) | Create, instantiate, variant, apply / revert overrides, unpack, edit prefab contents. |
|
||||
| [Script commands](commands/scripts.md) | Create and attach scripts; get / set serialized fields; compile and run project entry points in memory (`run_script`). |
|
||||
| [Animation commands](commands/animation.md) | Create AnimationClips (+ curves), AnimatorControllers (parameters / layers / states / transitions), and Timeline assets. |
|
||||
| [Material & shader commands](commands/materials.md) | Read / set material shader properties and keywords; list and introspect shaders. |
|
||||
| [Baking commands](commands/baking.md) | Bake / clear lighting, NavMesh, and occlusion culling (async — poll the matching `*_bake_status`). |
|
||||
| [Navigation & selection commands](commands/navigation.md) | Read / set the Editor selection; run Unity Search queries. |
|
||||
| [Capture commands](commands/capture.md) | Screenshot the game / scene view and capture UI Toolkit visual elements. |
|
||||
| [Build, compilation & test commands](commands/build-and-compilation.md) | Build the Player, switch build target, read/write build settings, list targets/profiles, recompile scripts, and run tests (async — poll the matching `*_status`). |
|
||||
| [Project settings commands](commands/project-settings.md) | Read / change Audio, Graphics, Input, Physics, Player, Quality, Tags & Layers, and Time settings (`get_*`/`set_*`, gated by `confirm`/`dry_run`). |
|
||||
| [Package Manager commands](commands/package-manager.md) | List / search / add / remove / resolve UPM packages and poll operation status. |
|
||||
| [Editor lifecycle & observability commands](commands/editor-lifecycle-and-observability.md) | Play / stop / pause, focus, menus, screenshots, console logs, performance stats, authoring root. |
|
||||
| [Runtime commands](commands/runtime.md) | Player-side commands: status, quit, frame rate, time scale, log, console, eval, code reload. |
|
||||
|
||||
## Command index
|
||||
|
||||
Every available command, grouped by area. Each name links to its full reference (parameters + return type).
|
||||
|
||||
### Asset & file commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| [`create_asset`](commands/assets-and-files.md#create_asset) | Create a ScriptableObject / Object asset at a path. |
|
||||
| [`import_asset`](commands/assets-and-files.md#import_asset) | Import an external file into the project. |
|
||||
| [`move_asset`](commands/assets-and-files.md#move_asset) | Move / rename an asset (keeps its GUID). |
|
||||
| [`copy_asset`](commands/assets-and-files.md#copy_asset) | Copy an asset (fresh GUID). |
|
||||
| [`rename_asset`](commands/assets-and-files.md#rename_asset) | Rename an asset in place. |
|
||||
| [`delete_asset`](commands/assets-and-files.md#delete_asset) | Delete an asset (needs confirm). |
|
||||
| [`find_assets`](commands/assets-and-files.md#find_assets) | Find assets by type / name / label. |
|
||||
| [`set_import_settings`](commands/assets-and-files.md#set_import_settings) | Set an asset's importer settings and re-import. |
|
||||
| [`get_import_settings`](commands/assets-and-files.md#get_import_settings) | Read an asset's importer settings. |
|
||||
| [`create_folder`](commands/assets-and-files.md#create_folder) | Create a folder under the authoring root. |
|
||||
| [`read_text_file`](commands/assets-and-files.md#read_text_file) | Read a UTF-8 text file. |
|
||||
| [`write_text_file`](commands/assets-and-files.md#write_text_file) | Write a UTF-8 text file, then import it. |
|
||||
|
||||
### Scene commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| [`create_scene`](commands/scenes.md#create_scene) | Create and save a new scene. |
|
||||
| [`open_scene`](commands/scenes.md#open_scene) | Open an existing scene. |
|
||||
| [`save_scene`](commands/scenes.md#save_scene) | Save an open scene. |
|
||||
| [`save_all`](commands/scenes.md#save_all) | Save all dirty open scenes. |
|
||||
| [`list_open_scenes`](commands/scenes.md#list_open_scenes) | List open scenes and their state. |
|
||||
| [`set_active_scene`](commands/scenes.md#set_active_scene) | Set which open scene is active. |
|
||||
| [`get_scene_hierarchy`](commands/scenes.md#get_scene_hierarchy) | Return a scene's GameObject tree. |
|
||||
| [`add_scene_to_build`](commands/scenes.md#add_scene_to_build) | Add a scene to Build Settings. |
|
||||
| [`remove_scene_from_build`](commands/scenes.md#remove_scene_from_build) | Remove a scene from Build Settings. |
|
||||
|
||||
### GameObject & component commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| [`create_gameobject`](commands/gameobjects-and-components.md#create_gameobject) | Create a GameObject or primitive. |
|
||||
| [`create_gameobjects`](commands/gameobjects-and-components.md#create_gameobjects) | Batch-create GameObjects / primitives. |
|
||||
| [`find_gameobjects`](commands/gameobjects-and-components.md#find_gameobjects) | Find GameObjects by name / tag / component / path. |
|
||||
| [`set_transform`](commands/gameobjects-and-components.md#set_transform) | Set local position / rotation / scale. |
|
||||
| [`set_parent`](commands/gameobjects-and-components.md#set_parent) | Reparent a GameObject (or detach to root). |
|
||||
| [`set_active`](commands/gameobjects-and-components.md#set_active) | Set a GameObject's active state. |
|
||||
| [`set_tag`](commands/gameobjects-and-components.md#set_tag) | Set a GameObject's tag. |
|
||||
| [`set_layer`](commands/gameobjects-and-components.md#set_layer) | Set a GameObject's layer. |
|
||||
| [`rename_gameobject`](commands/gameobjects-and-components.md#rename_gameobject) | Rename a GameObject. |
|
||||
| [`delete_gameobject`](commands/gameobjects-and-components.md#delete_gameobject) | Delete a GameObject (undoable). |
|
||||
| [`add_component`](commands/gameobjects-and-components.md#add_component) | Add a component by type name. |
|
||||
| [`remove_component`](commands/gameobjects-and-components.md#remove_component) | Remove a component. |
|
||||
| [`get_component_properties`](commands/gameobjects-and-components.md#get_component_properties) | Read a component's serialized properties. |
|
||||
| [`set_component_properties`](commands/gameobjects-and-components.md#set_component_properties) | Set a component's serialized properties. |
|
||||
|
||||
### Prefab commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| [`create_prefab`](commands/prefabs.md#create_prefab) | Save a GameObject as a prefab. |
|
||||
| [`instantiate_prefab`](commands/prefabs.md#instantiate_prefab) | Instantiate a prefab into a scene. |
|
||||
| [`create_prefab_variant`](commands/prefabs.md#create_prefab_variant) | Create a prefab variant. |
|
||||
| [`apply_prefab_overrides`](commands/prefabs.md#apply_prefab_overrides) | Apply instance overrides to the source. |
|
||||
| [`revert_prefab_overrides`](commands/prefabs.md#revert_prefab_overrides) | Revert instance overrides. |
|
||||
| [`unpack_prefab`](commands/prefabs.md#unpack_prefab) | Unpack a prefab instance. |
|
||||
| [`save_prefab_contents`](commands/prefabs.md#save_prefab_contents) | Edit a prefab in an isolated stage and save. |
|
||||
|
||||
### Script commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| [`create_script`](commands/scripts.md#create_script) | Create a C# script from a template. |
|
||||
| [`attach_script`](commands/scripts.md#attach_script) | Attach a MonoBehaviour by type / asset. |
|
||||
| [`set_serialized_field`](commands/scripts.md#set_serialized_field) | Set a serialized field on a component / asset. |
|
||||
| [`get_serialized_fields`](commands/scripts.md#get_serialized_fields) | Read serialized fields. |
|
||||
| [`run_script`](commands/scripts.md#run_script) | Compile a project `.cs` file in memory (no domain reload) and run a named static entry point — the builder-pattern path for bulk construction. |
|
||||
|
||||
### Animation commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| [`create_animation_clip`](commands/animation.md#create_animation_clip) | Create an empty .anim AnimationClip. |
|
||||
| [`set_animation_curve`](commands/animation.md#set_animation_curve) | Add / replace a float curve on a clip. |
|
||||
| [`get_animation_clip`](commands/animation.md#get_animation_clip) | Read a clip's curves and metadata. |
|
||||
| [`remove_animation_curve`](commands/animation.md#remove_animation_curve) | Remove a curve binding (needs confirm). |
|
||||
| [`create_animator_controller`](commands/animation.md#create_animator_controller) | Create an .controller AnimatorController. |
|
||||
| [`add_animator_parameter`](commands/animation.md#add_animator_parameter) | Add a parameter to a controller. |
|
||||
| [`add_animator_layer`](commands/animation.md#add_animator_layer) | Add a layer to a controller. |
|
||||
| [`add_animator_state`](commands/animation.md#add_animator_state) | Add a state to a layer. |
|
||||
| [`add_animator_transition`](commands/animation.md#add_animator_transition) | Add a transition between states. |
|
||||
| [`get_animator_controller`](commands/animation.md#get_animator_controller) | Read a controller's full structure. |
|
||||
| [`create_timeline`](commands/animation.md#create_timeline) | Create a .playable Timeline asset. |
|
||||
| [`add_timeline_track`](commands/animation.md#add_timeline_track) | Add a track to a Timeline. |
|
||||
| [`add_timeline_clip`](commands/animation.md#add_timeline_clip) | Add a clip to a Timeline track. |
|
||||
| [`get_timeline`](commands/animation.md#get_timeline) | Read a Timeline's structure. |
|
||||
|
||||
### Material & shader commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| [`get_material_properties`](commands/materials.md#get_material_properties) | Read a material's shader and properties. |
|
||||
| [`set_material_properties`](commands/materials.md#set_material_properties) | Set material shader properties / keywords. |
|
||||
| [`list_shaders`](commands/materials.md#list_shaders) | Discover available shaders. |
|
||||
| [`get_shader_properties`](commands/materials.md#get_shader_properties) | Introspect a shader's property list. |
|
||||
|
||||
### Baking commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| [`bake_lighting`](commands/baking.md#bake_lighting) | Async lightmap bake (poll `lighting_bake_status`). |
|
||||
| [`lighting_bake_status`](commands/baking.md#lighting_bake_status) | Status of the last lighting bake. |
|
||||
| [`cancel_lighting_bake`](commands/baking.md#cancel_lighting_bake) | Cancel an in-progress lighting bake. |
|
||||
| [`clear_baked_lighting`](commands/baking.md#clear_baked_lighting) | Clear baked lightmaps (needs confirm). |
|
||||
| [`get_lighting_settings`](commands/baking.md#get_lighting_settings) | Read the active LightingSettings. |
|
||||
| [`set_lighting_settings`](commands/baking.md#set_lighting_settings) | Apply a subset of lighting settings. |
|
||||
| [`bake_navmesh`](commands/baking.md#bake_navmesh) | Async legacy NavMesh bake (poll `navmesh_bake_status`). |
|
||||
| [`navmesh_bake_status`](commands/baking.md#navmesh_bake_status) | Status of the last NavMesh bake. |
|
||||
| [`cancel_navmesh_bake`](commands/baking.md#cancel_navmesh_bake) | Cancel an in-progress NavMesh bake. |
|
||||
| [`clear_navmesh`](commands/baking.md#clear_navmesh) | Clear the baked NavMesh (needs confirm). |
|
||||
| [`get_navmesh_settings`](commands/baking.md#get_navmesh_settings) | Read legacy NavMesh bake settings. |
|
||||
| [`set_navmesh_settings`](commands/baking.md#set_navmesh_settings) | Apply a subset of NavMesh settings. |
|
||||
| [`bake_navmesh_surfaces`](commands/baking.md#bake_navmesh_surfaces) | Bake NavMeshSurface components (AI Navigation). |
|
||||
| [`bake_occlusion_culling`](commands/baking.md#bake_occlusion_culling) | Async occlusion bake (poll `occlusion_bake_status`). |
|
||||
| [`occlusion_bake_status`](commands/baking.md#occlusion_bake_status) | Status of the last occlusion bake. |
|
||||
| [`cancel_occlusion_bake`](commands/baking.md#cancel_occlusion_bake) | Cancel an in-progress occlusion bake. |
|
||||
| [`clear_occlusion_culling`](commands/baking.md#clear_occlusion_culling) | Clear baked occlusion data (needs confirm). |
|
||||
|
||||
### Navigation & selection commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| [`get_selection`](commands/navigation.md#get_selection) | Read the current Editor selection. |
|
||||
| [`set_selection`](commands/navigation.md#set_selection) | Set the Editor selection. |
|
||||
| [`search`](commands/navigation.md#search) | Run a Unity Search query. |
|
||||
|
||||
### Capture commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| [`capture_game_view`](commands/capture.md#capture_game_view) | Render a camera to a PNG (base64). |
|
||||
| [`capture_scene_view`](commands/capture.md#capture_scene_view) | Render the Scene View to a PNG (base64). |
|
||||
| [`capture_editor_element`](commands/capture.md#capture_editor_element) | Capture a UI Toolkit element from an EditorWindow (6000.7+). |
|
||||
| [`capture_runtime_element`](commands/capture.md#capture_runtime_element) | Capture a UI Toolkit element from a runtime panel (6000.7+). |
|
||||
|
||||
### Build, compilation & test commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| [`build`](commands/build-and-compilation.md#build) | Async Player build (poll `build_status`). |
|
||||
| [`build_status`](commands/build-and-compilation.md#build_status) | Status / report of the current build. |
|
||||
| [`switch_build_target`](commands/build-and-compilation.md#switch_build_target) | Switch the active build target (needs confirm). |
|
||||
| [`switch_build_target_status`](commands/build-and-compilation.md#switch_build_target_status) | Status of the last target switch. |
|
||||
| [`list_build_targets`](commands/build-and-compilation.md#list_build_targets) | List known build targets. |
|
||||
| [`get_build_settings`](commands/build-and-compilation.md#get_build_settings) | Read build configuration. |
|
||||
| [`set_build_settings`](commands/build-and-compilation.md#set_build_settings) | Set build configuration fields. |
|
||||
| [`list_build_profiles`](commands/build-and-compilation.md#list_build_profiles) | List Build Profile assets (Unity 6). |
|
||||
| [`recompile`](commands/build-and-compilation.md#recompile) | Force a script recompile (poll `recompile_status`). |
|
||||
| [`recompile_status`](commands/build-and-compilation.md#recompile_status) | Status of the last recompile. |
|
||||
| [`list_tests`](commands/build-and-compilation.md#list_tests) | List available tests without running. |
|
||||
| [`run_tests`](commands/build-and-compilation.md#run_tests) | Run Unity tests with filters. |
|
||||
| [`test_status`](commands/build-and-compilation.md#test_status) | Status of async test execution. |
|
||||
| [`cancel_tests`](commands/build-and-compilation.md#cancel_tests) | Cancel running tests. |
|
||||
|
||||
### Project settings commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| [`get_audio_settings`](commands/project-settings.md#get_audio_settings) | Read Audio settings. |
|
||||
| [`set_audio_settings`](commands/project-settings.md#set_audio_settings) | Change Audio settings (needs confirm). |
|
||||
| [`get_graphics_settings`](commands/project-settings.md#get_graphics_settings) | Read Graphics settings. |
|
||||
| [`set_graphics_settings`](commands/project-settings.md#set_graphics_settings) | Set the default render pipeline (needs confirm). |
|
||||
| [`get_input_settings`](commands/project-settings.md#get_input_settings) | Read legacy Input Manager axes. |
|
||||
| [`set_input_settings`](commands/project-settings.md#set_input_settings) | Tune a legacy input axis (needs confirm). |
|
||||
| [`get_physics_settings`](commands/project-settings.md#get_physics_settings) | Read Physics settings. |
|
||||
| [`set_physics_settings`](commands/project-settings.md#set_physics_settings) | Change Physics settings (needs confirm). |
|
||||
| [`get_player_settings`](commands/project-settings.md#get_player_settings) | Read PlayerSettings. |
|
||||
| [`set_player_settings`](commands/project-settings.md#set_player_settings) | Change PlayerSettings (needs confirm). |
|
||||
| [`get_quality_settings`](commands/project-settings.md#get_quality_settings) | Read QualitySettings. |
|
||||
| [`set_quality_settings`](commands/project-settings.md#set_quality_settings) | Change QualitySettings (needs confirm). |
|
||||
| [`get_tags_layers`](commands/project-settings.md#get_tags_layers) | Read tags and layers. |
|
||||
| [`set_tags_layers`](commands/project-settings.md#set_tags_layers) | Add / remove tags, name layers (needs confirm). |
|
||||
| [`get_time_settings`](commands/project-settings.md#get_time_settings) | Read Time settings. |
|
||||
| [`set_time_settings`](commands/project-settings.md#set_time_settings) | Change Time settings (needs confirm). |
|
||||
|
||||
### Package Manager commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| [`package_list`](commands/package-manager.md#package_list) | List packages by scope. |
|
||||
| [`package_search`](commands/package-manager.md#package_search) | Search the registry. |
|
||||
| [`package_add`](commands/package-manager.md#package_add) | Add a UPM package (needs confirm). |
|
||||
| [`package_remove`](commands/package-manager.md#package_remove) | Remove a UPM package (needs confirm). |
|
||||
| [`package_resolve`](commands/package-manager.md#package_resolve) | Resolve / refresh packages. |
|
||||
| [`package_status`](commands/package-manager.md#package_status) | Status of the last package operation. |
|
||||
|
||||
### Editor lifecycle & observability commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| [`editor_play`](commands/editor-lifecycle-and-observability.md#editor_play) | Enter play mode. |
|
||||
| [`editor_stop`](commands/editor-lifecycle-and-observability.md#editor_stop) | Exit play mode. |
|
||||
| [`editor_pause`](commands/editor-lifecycle-and-observability.md#editor_pause) | Toggle play mode pause. |
|
||||
| [`editor_status`](commands/editor-lifecycle-and-observability.md#editor_status) | Detailed Editor status. |
|
||||
| [`editor_focus`](commands/editor-lifecycle-and-observability.md#editor_focus) | Bring the Editor to the foreground. |
|
||||
| [`menu`](commands/editor-lifecycle-and-observability.md#menu) | Execute (or list) Editor menu items. |
|
||||
| [`screenshot`](commands/editor-lifecycle-and-observability.md#screenshot) | Capture Scene / Game view to a PNG file. |
|
||||
| [`set_autotick`](commands/editor-lifecycle-and-observability.md#set_autotick) | Keep the editor ticking while unfocused. |
|
||||
| [`get_performance_stats`](commands/editor-lifecycle-and-observability.md#get_performance_stats) | Read render / memory / frame stats. |
|
||||
| [`audit`](commands/editor-lifecycle-and-observability.md#audit) | Run a Project Auditor scan, producing a CSV of issues. |
|
||||
| [`audit_status`](commands/editor-lifecycle-and-observability.md#audit_status) | Poll the audit scan; returns the CSV path + issue count. |
|
||||
| [`report_evals`](commands/editor-lifecycle-and-observability.md#report_evals) | Ranked report of local eval-usage telemetry + command-coverage suggestions. |
|
||||
| [`get_authoring_root`](commands/editor-lifecycle-and-observability.md#get_authoring_root) | Get the authoring-root folder. |
|
||||
| [`set_authoring_root`](commands/editor-lifecycle-and-observability.md#set_authoring_root) | Set the authoring-root folder. |
|
||||
|
||||
### Runtime commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| [`runtime_status`](commands/runtime.md#runtime_status) | Runtime application status. |
|
||||
| [`quit`](commands/runtime.md#quit) | Quit the application. |
|
||||
| [`set_target_framerate`](commands/runtime.md#set_target_framerate) | Set the target frame rate. |
|
||||
| [`set_timescale`](commands/runtime.md#set_timescale) | Set the time scale. |
|
||||
| [`simulate_key`](commands/runtime.md#simulate_key) | Simulate a keyboard event (Input System). |
|
||||
| [`simulate_pointer`](commands/runtime.md#simulate_pointer) | Simulate a pointer event (Input System). |
|
||||
| [`log`](commands/runtime.md#log) | Write a message to the Unity console. |
|
||||
| [`console`](commands/runtime.md#console) | Read captured console output. |
|
||||
| [`console_status`](commands/runtime.md#console_status) | Console counters and compile-failure flag, without the entries. |
|
||||
| [`clear_console`](commands/runtime.md#clear_console) | Clear the console / log buffer. |
|
||||
| [`eval`](commands/runtime.md#eval) | Evaluate C# via Roslyn. |
|
||||
| [`eval_file`](commands/runtime.md#eval_file) | Evaluate C# from a .cs file. |
|
||||
| [`reload_file`](commands/runtime.md#reload_file) | Apply [CodeReload] edits from a file. |
|
||||
| [`reload_file_editor_interpreter`](commands/runtime.md#reload_file_editor_interpreter) | Same, on the IlInterpreter backend (IL2CPP-safe). |
|
||||
| [`reload_file_player_interpreter`](commands/runtime.md#reload_file_player_interpreter) | Compile in the Editor and push [CodeReload] edits to connected players (IL2CPP-safe). |
|
||||
| [`codereload_status`](commands/runtime.md#codereload_status) | Code reload registry status. |
|
||||
| [`cleanup_codereload`](commands/runtime.md#cleanup_codereload) | Clear old code reload DLLs / registry. |
|
||||
@@ -0,0 +1,80 @@
|
||||
# Runtime connection & setup
|
||||
|
||||
How to run the Pipeline server inside a built Player so a client can drive it the same way it drives the Editor. The runtime server only exists in **development builds**, so setup is mostly about building correctly.
|
||||
|
||||
The package's runtime assemblies (`Unity.Pipeline`, `Unity.Pipeline.IlInterpreter`) and its bundled Roslyn DLLs carry the define constraint `UNITY_EDITOR || DEVELOPMENT_BUILD || ENABLE_RUNTIME_PIPELINE`, so Unity does not compile or package them for a non-development Player build. A release build contains no Pipeline code regardless of how `enableInBuilds` is set.
|
||||
|
||||
To ship the Pipeline in a build that is *not* a Development Build, add `ENABLE_RUNTIME_PIPELINE` to the scripting define symbols for that build (Project Settings → Player → Scripting Define Symbols, or `BuildPlayerOptions.extraScriptingDefines`). The build then contains the same Pipeline assemblies a development build would, and `enableInBuilds` still decides whether the server actually starts. Only do this deliberately: such a build carries a local HTTP server that executes commands.
|
||||
|
||||
## Configuring the runtime server
|
||||
|
||||
The runtime server is configured entirely through **Project Settings → Pipeline → Runtime**. No GameObject or component needs to be added to any scene — `RuntimePipelineBootstrap` creates the runtime driver automatically at boot (Player start, or entering Play Mode in the Editor) if settings exist and `enableInBuilds` is on.
|
||||
|
||||
Your settings are never written as a file under `Assets/` — they live in `ProjectSettings/Packages/com.unity.pipeline/RuntimePipelineConfig.json`, a normal, tracked part of `ProjectSettings/` (the same place Unity itself keeps most other project-wide settings). Merely viewing the Project Settings page, or reading via `get_runtime_pipeline_settings`, shows built-in defaults but never creates this file — it's written the first time you actually change a setting (through the page, or a confirmed `set_runtime_pipeline_settings`).
|
||||
|
||||
**Security warning:** whenever `enableInBuilds` is on *and* Development Build is checked, the settings page shows a persistent warning — that build really will carry the HTTP server and remote code execution. With Development Build unchecked the page instead notes that the setting has no effect, since the Pipeline assemblies are excluded from that build.
|
||||
|
||||
### Settings
|
||||
|
||||
| Field | Default | Meaning |
|
||||
|-------|---------|---------|
|
||||
| `enableInBuilds` | `false` | Master switch. The server only starts when this is `true`. **Off by default for safety.** |
|
||||
| `autoStart` | `true` | Start the server automatically when the Player boots (requires `enableInBuilds`). |
|
||||
| `port` | `0` | Listen port. `0` = auto-assign from `7900`–`7949`. |
|
||||
| `requestTimeoutMs` | `30000` | Per-request timeout. |
|
||||
| `enableAuditLogging` | `true` | Log remote requests for auditing. |
|
||||
| `maxWorkItemsPerFrame` | `10` | Dispatcher work items processed per frame. |
|
||||
|
||||
If `autoStart` is off, drive the server manually via `RuntimePipelineBootstrap.Instance` — the driver `RuntimeInitializeOnLoadMethod` already created at boot — and call `StartServer()`/`StopServer()` on it. `RuntimePipelineBootstrap.Bootstrap()` is idempotent (a second call returns the existing instance rather than creating a duplicate), so it's also safe to call directly if you're not sure whether bootstrap has run yet.
|
||||
|
||||
### Generated build-only assets
|
||||
|
||||
Each build generates two **transient** assets under `Assets/Settings/Pipeline/Resources/`, both deleted again once the build finishes:
|
||||
|
||||
- `RuntimePipelineConfig.asset` — your current settings, baked in so the Player can find them (the authored copy stays in `ProjectSettings/`, never here).
|
||||
- `RuntimePipelineBuildInfo.asset` — the code-reload allowlist: absolute paths to `Assets/` plus every loaded package's resolved location, captured at build time because a running Player can't resolve the project layout on its own. Read by `RuntimePipelineBootstrap` at boot; absent (e.g. Play Mode without ever having built) just means no code-reload roots are allowed, not an error.
|
||||
|
||||
Neither should ever be committed — add this to your project's `.gitignore`:
|
||||
|
||||
```
|
||||
/Assets/Settings/Pipeline/Resources/RuntimePipelineConfig.asset*
|
||||
/Assets/Settings/Pipeline/Resources/RuntimePipelineBuildInfo.asset*
|
||||
```
|
||||
|
||||
A build interrupted mid-way (crash, force-quit) can leave either behind; if you see them in `git status` after such an interruption, it's safe to delete them. The next build you run will also delete them itself, at the very start of preprocessing, before anything else — so a leftover from an interrupted build can never get packaged into a later Player, even one built with `enableInBuilds` off.
|
||||
|
||||
## Development-build gating
|
||||
|
||||
The runtime server, code evaluation, and code reload are all gated behind a compile-time guard:
|
||||
|
||||
```csharp
|
||||
#if UNITY_EDITOR || (UNITY_STANDALONE && DEBUG)
|
||||
```
|
||||
|
||||
`DEBUG` is defined for **standalone Development Builds** only. In a non-development (release) build the compilation/eval/code-reload code is compiled out entirely, so it cannot run — even if `enableInBuilds` is `true`. To use the runtime server in a Player you therefore need **both**:
|
||||
|
||||
1. A **Development Build** (defines `DEBUG`), built for a **standalone** target (Windows/macOS/Linux).
|
||||
2. `enableInBuilds = true` in Project Settings → Pipeline → Runtime.
|
||||
|
||||
## Step-by-step: enable in a dev build
|
||||
|
||||
1. Open **Project Settings → Pipeline → Runtime**.
|
||||
2. Tick **`enableInBuilds`**. Leave `autoStart` on and `port` at `0` (auto).
|
||||
3. Open **File → Build Settings** and enable **Development Build** for a **Standalone** platform.
|
||||
4. Build and run the Player.
|
||||
5. On start, the bootstrap creates a `RuntimePipelineServer`, which binds the first free port in `7900`–`7949` and writes the runtime descriptor `.unity-pipeline-runtime-port` (with `port` and `evalToken`).
|
||||
6. Connect a client using that port and token.
|
||||
|
||||
## Security
|
||||
|
||||
The runtime server applies the same protections as the editor server (see [Connectivity](connectivity.md)):
|
||||
|
||||
- It binds the **IPv4 loopback** (`http://127.0.0.1:<port>/`, plus the `localhost` hostname), so it is reachable only from the same machine and never exposed on a routable interface. Clients should connect to `127.0.0.1` explicitly — see [Loopback-only binding](connectivity.md#loopback-only-binding) for why `localhost` can resolve to the IPv6 loopback (`::1`), which Unity's Mono `HttpListener` cannot serve.
|
||||
- Every request must present `Authorization: Bearer <evalToken>`; the token is generated at startup and published in the runtime descriptor.
|
||||
|
||||
Because the server exposes code evaluation and code reload, only enable it in development/QA builds — never in a production build without additional safeguards.
|
||||
|
||||
## See also
|
||||
|
||||
- [Connectivity](connectivity.md) — ports, descriptor file, and auth in detail.
|
||||
- [Code reload](code-reload.md) — applying live code changes to a running Player.
|
||||
@@ -0,0 +1,90 @@
|
||||
# Safety & mutations
|
||||
|
||||
Mutating commands — asset writes, project-settings changes, package/build changes —
|
||||
share two conventions so command authors and clients know what to expect: a `confirm`/`dry_run` gate
|
||||
on destructive or overwriting operations, and Undo grouping via `AuthoringUndoScope`. Commands that
|
||||
resolve caller-supplied asset paths confine them with `ProjectPaths` (see
|
||||
[Creating authoring commands](authoring-commands.md)). The helpers live in `Editor/Authoring/`.
|
||||
|
||||
### The confirm / dry_run convention
|
||||
|
||||
Destructive or overwriting commands expose two boolean `[CliArg]`s — `confirm` and `dry_run` — and
|
||||
gate their own mutation on them. There is no central dispatcher; each command applies the same simple
|
||||
rules inline:
|
||||
|
||||
- **`dry_run == true`** → validate and describe the intended change, mutate nothing, return a preview.
|
||||
This wins even if `confirm` is also true.
|
||||
- **`confirm == false`** (and not a dry run) → refuse without mutating. Return the failure the way the
|
||||
command reports its other errors — `throw new ArgumentException(...)` for the throwing style (e.g.
|
||||
`delete_asset`, `clear_baked_lighting`), or a structured error object for commands that return
|
||||
structured results (e.g. `build`, which returns `{ success = false, status = "error", message = "…" }`).
|
||||
- **otherwise** (`confirm == true`, not a dry run) → perform the mutation. Group *scene/object*
|
||||
mutations in an `AuthoringUndoScope` (see below). Asset/settings/package writes are **not** part of
|
||||
Unity's Undo, so they run directly — say so in the command description ("Not undoable via Ctrl+Z.").
|
||||
|
||||
```csharp
|
||||
[CliCommand("clear_baked_lighting", "Clear baked lightmap data for the open scene(s). Destructive: requires confirm=true (use dry_run to preview). Not undoable via Ctrl+Z.")]
|
||||
public static object ClearBakedLighting(
|
||||
[CliArg("confirm", "Apply the clear. Without it the call is refused.")] bool confirm = false,
|
||||
[CliArg("dry_run", "Preview what would be cleared without clearing it.")] bool dryRun = false)
|
||||
{
|
||||
// …resolve the open scene(s)…
|
||||
if (dryRun)
|
||||
return new { status = "dry_run", wouldClear = true };
|
||||
if (!confirm)
|
||||
throw new ArgumentException("Refusing to clear baked lighting. Pass confirm=true to apply, or dry_run=true to preview.");
|
||||
|
||||
// Clearing baked GI is not part of Unity's Undo — no AuthoringUndoScope.
|
||||
Lightmapping.Clear();
|
||||
|
||||
return new { cleared = true };
|
||||
}
|
||||
```
|
||||
|
||||
Purely additive, non-destructive commands (e.g. `create_folder`, `add_animator_parameter`)
|
||||
do not take `confirm`/`dry_run`; they just perform the mutation directly.
|
||||
|
||||
### Undo grouping
|
||||
|
||||
Scene/object mutations are grouped with `AuthoringUndoScope` so a multi-step command reverts as a
|
||||
single Ctrl+Z step. It increments Unity's current Undo group on construction and calls
|
||||
`Undo.CollapseUndoOperations` on dispose, collapsing everything the body registers — via
|
||||
`Undo.RecordObject`, `Undo.RegisterCreatedObjectUndo`, and similar — into one named step. The command
|
||||
opts into Undo by calling those APIs inside the scope. See
|
||||
[Creating authoring commands](authoring-commands.md) for the full pattern and the per-mutation `Undo`
|
||||
API table.
|
||||
|
||||
> **Caveat.** Some operations are not part of Unity's Undo system. `AssetDatabase` operations, UPM
|
||||
> package changes, and settings backed by native assets may not be undoable; for those the collapse is
|
||||
> simply a no-op and Ctrl+Z will not revert the change. Don't rely on Undo to clean up such writes —
|
||||
> validate up front and fail before writing.
|
||||
|
||||
### Path confinement
|
||||
|
||||
Commands that accept a caller-supplied asset path resolve it through `ProjectPaths.Resolve`, which
|
||||
normalizes the path, rejects `..` traversal, and confines it to the configurable authoring root
|
||||
(default `Assets`) so agent-supplied paths cannot escape the project. It returns a project-relative
|
||||
path, or `null` with an `error` string when the path escapes the root.
|
||||
|
||||
```csharp
|
||||
var normalized = ProjectPaths.Resolve(path, out var error);
|
||||
if (normalized == null)
|
||||
throw new ArgumentException(error);
|
||||
// normalized is project-relative and guaranteed to live under the authoring root.
|
||||
```
|
||||
|
||||
See [Creating authoring commands](authoring-commands.md) for the full path-handling rules and the
|
||||
`set_authoring_root` / `get_authoring_root` commands that adjust the root.
|
||||
|
||||
### For command authors
|
||||
|
||||
To make a mutating command safe:
|
||||
|
||||
1. If the operation is destructive or overwrites existing content, add `confirm` and `dry_run` boolean
|
||||
`[CliArg]`s and gate on them inline (preview on `dry_run`, refuse on `!confirm`).
|
||||
2. Group scene/object mutations in `using (new AuthoringUndoScope("<operation>"))` and register Undo
|
||||
operations inside it where the mutation is undoable.
|
||||
3. Resolve any caller-supplied asset path through `ProjectPaths.Resolve` before touching the filesystem
|
||||
— ideally during validation, so a bad path fails before anything is applied.
|
||||
|
||||
See [Creating commands](creating-commands.md) and [Creating authoring commands](authoring-commands.md).
|
||||
@@ -0,0 +1,95 @@
|
||||
# Tests architecture
|
||||
|
||||
How the package's tests exercise commands. There are two complementary styles — **ViaClient** (over real HTTP, end-to-end) and **CommandDirect** (calling the static command method straight) — backed by a small set of shared test helpers.
|
||||
|
||||
## Two test styles
|
||||
|
||||
### ViaClient — exercise the full HTTP path
|
||||
|
||||
Spin up an isolated `PipelineTestServer` and call `server.Execute(command, params)`. This goes over HTTP through the same routing, parameter coercion, and threading the real server uses — it tests the command *as a client sees it*.
|
||||
|
||||
```csharp
|
||||
[Test]
|
||||
public void SetTransform_ViaClient_ArrayParams_Apply()
|
||||
{
|
||||
var go = Track(new GameObject("Xform219_Arrays"));
|
||||
|
||||
using (var server = new PipelineTestServer())
|
||||
{
|
||||
var response = server.Execute("set_transform", new
|
||||
{
|
||||
target = new { instanceId = PipelineUtils.GetObjectId(go) },
|
||||
position = new[] { 1f, 2f, 3f },
|
||||
rotation = new[] { 0f, 90f, 0f },
|
||||
scale = new[] { 2f, 2f, 2f }
|
||||
});
|
||||
|
||||
Assert.IsTrue(response.IsSuccess, $"set_transform should succeed: {response.Error}");
|
||||
Assert.AreEqual(new Vector3(1, 2, 3), go.transform.localPosition);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The test server is isolated: it uses the **test editor port range `7850`–`7899`** and **writes no descriptor**, so it never disturbs the live editor server (which uses `7800`–`7849`). `Execute` pumps the server's own dispatcher while the HTTP call is in flight, so `MainThreadRequired` commands complete even from a plain `[Test]` that blocks the main thread — no deadlock.
|
||||
|
||||
### CommandDirect — call the static method directly
|
||||
|
||||
Call the command's static method with typed arguments. This is synchronous, with **no server, HTTP, or dispatcher** involved — fast and ideal for unit-testing command logic and parameter handling.
|
||||
|
||||
```csharp
|
||||
[Test]
|
||||
public void SetTransform_Direct_TypedArrays_Apply()
|
||||
{
|
||||
var go = Track(new GameObject("Xform219_Direct"));
|
||||
|
||||
GameObjectCommands.SetTransform(RefTo(go),
|
||||
position: new[] { 1f, 2f, 3f },
|
||||
rotation: new[] { 0f, 90f, 0f },
|
||||
scale: new[] { 2f, 2f, 2f });
|
||||
|
||||
Assert.AreEqual(new Vector3(1, 2, 3), go.transform.localPosition);
|
||||
Assert.AreEqual(new Vector3(2, 2, 2), go.transform.localScale);
|
||||
}
|
||||
```
|
||||
|
||||
Use **CommandDirect** to verify a command's behavior with strongly-typed inputs; use **ViaClient** to additionally verify wire-level concerns (JSON coercion, required-parameter validation, threading).
|
||||
|
||||
## Shared helpers
|
||||
|
||||
| Helper | Role |
|
||||
|--------|------|
|
||||
| `PipelineTestServer` | Disposable wrapper that starts an isolated `TestEditorPipelineServer`, wires up a `PipelineClient`, and exposes `Execute(command, parameters, timeoutMs = 30000)`. Pumps the server dispatcher during each call to avoid main-thread deadlock. |
|
||||
| `TestEditorPipelineServer` | `EditorPipelineServer` subclass for tests. Overrides `WritesDescriptor => false`, `GetPortRange() => (7850, 7899)`, and `GetToken()` (from `SecurityTokenManager`) so it is fully isolated from the live server. |
|
||||
| `PipelineClient` | Test-side HTTP client. Constructed from a URL+token or a server/manager instance. Key methods: `ExecuteCommandAsync` (`/api/exec`), `GetStatusAsync` (`/api/status`), `PostJsonAsync`. Returns a `PipelineResponse` (`IsSuccess`, `StatusCode`, `Error`, `JsonResponse`, `IsCommandSuccess`). |
|
||||
| `EditorTestUtilities` | Async helpers for editor state, e.g. `WaitFor(Func<bool> condition, timeoutMs)` to poll a condition without busy-waiting. |
|
||||
| `LiveServerGuard` | Assembly-level `ITestAction` that asserts the **live editor server survives the test run**. |
|
||||
|
||||
### `PipelineTestServer` setup
|
||||
|
||||
```csharp
|
||||
public PipelineTestServer()
|
||||
{
|
||||
m_Server = new TestEditorPipelineServer();
|
||||
m_Server.Start(); // auto-assigns a port in 7850-7899; writes no descriptor
|
||||
m_Client = new PipelineClient($"http://localhost:{m_Server.Port}", SecurityTokenManager.GetOrCreateToken());
|
||||
}
|
||||
```
|
||||
|
||||
Wrap it in a `using` (it is disposable) so the isolated server is stopped at the end of the test.
|
||||
|
||||
### `LiveServerGuard`
|
||||
|
||||
Applied once at assembly scope (`[assembly: LiveServerGuard]`), it runs before and after **every** test in the editor suite. If a live editor server was advertising its descriptor before a test, the guard asserts afterwards that the test did not disturb it — the descriptor still exists, the port is unchanged, and the server is still listening:
|
||||
|
||||
```csharp
|
||||
Assert.IsNotNull(after, $"'{test.Name}' deleted the live pipeline server descriptor");
|
||||
Assert.AreEqual(m_Before.Port, after.Port, $"'{test.Name}' changed the port");
|
||||
Assert.IsTrue(IsListening(after.Port, after.EvalToken), $"'{test.Name}' left the server not responding");
|
||||
```
|
||||
|
||||
This is what makes it safe to "dogfood" — run the test suite against the very editor you are driving — without a test clobbering the live server. Tests that intentionally start/stop the live server are marked `[Explicit]` and must hand it back intact.
|
||||
|
||||
## See also
|
||||
|
||||
- [Creating commands](creating-commands.md) — the command API these tests exercise.
|
||||
- [Connectivity](connectivity.md) — ports, descriptor, and auth (the test server deliberately skips the descriptor).
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5144d7ecd5530424ab66f205802d8c9e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 113bc2bcfe487eb469aa654e6718f78b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Unity.Pipeline.Editor.Authoring
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Groups all Undo operations registered during its lifetime into a single, collapsible Editor
|
||||
/// Undo step, so a multi-step agent action reverts as one. Register mutations inside the scope
|
||||
/// with Undo.RegisterCreatedObjectUndo / RegisterCompleteObjectUndo / etc.
|
||||
/// </para>
|
||||
///
|
||||
/// <code>
|
||||
/// using (new AuthoringUndoScope("Create Enemy"))
|
||||
/// {
|
||||
/// var go = new GameObject("Enemy");
|
||||
/// Undo.RegisterCreatedObjectUndo(go, "Create Enemy");
|
||||
/// // ... further registered mutations collapse into the same step
|
||||
/// }
|
||||
/// </code>
|
||||
///
|
||||
/// <para>
|
||||
/// NOTE: minimal seed for the shared safety policy (CAT-2509). AssetDatabase operations
|
||||
/// (folder/asset creation, import) are NOT part of Unity's Undo system, so this scope only
|
||||
/// affects scene/object mutations.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public sealed class AuthoringUndoScope : IDisposable
|
||||
{
|
||||
private readonly int m_Group;
|
||||
private bool m_Canceled;
|
||||
|
||||
/// <summary>Start a new collapsible Undo group.</summary>
|
||||
/// <param name="name">Name shown for the collapsed Undo step.</param>
|
||||
public AuthoringUndoScope(string name)
|
||||
{
|
||||
Undo.IncrementCurrentGroup();
|
||||
m_Group = Undo.GetCurrentGroup();
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
Undo.SetCurrentGroupName(name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Skip the collapse on dispose. Call after the scope's registered operations have been
|
||||
/// reverted wholesale (e.g. <c>Undo.RevertAllDownToGroup(group)</c> on a transactional batch
|
||||
/// abort): the group id no longer refers to this scope's operations on the Undo stack, and
|
||||
/// collapsing a discarded id could fold whatever now sits at that index into a stray group.
|
||||
/// </summary>
|
||||
public void Cancel() => m_Canceled = true;
|
||||
|
||||
/// <summary>Collapse all Undo operations registered during this scope into one step, unless <see cref="Cancel"/> was called.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (!m_Canceled)
|
||||
Undo.CollapseUndoOperations(m_Group);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6ef72626627670a4eadfccdab20cd9ba
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,256 @@
|
||||
using Unity.Pipeline.Models;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Unity.Pipeline.Editor.Authoring
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves an agent-supplied <see cref="ObjectRef"/> handle to a live UnityEngine.Object, and
|
||||
/// describes any object back into a canonical <see cref="AuthoringResult"/> identity.
|
||||
/// Backed by GlobalObjectId, which addresses both assets (GUID + fileId) and scene objects.
|
||||
/// </summary>
|
||||
public static class ObjectResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Try to resolve a handle to a loaded object. Returns false with an <paramref name="error"/>
|
||||
/// when the handle is empty or does not resolve.
|
||||
/// </summary>
|
||||
/// <param name="handle">The agent-supplied object reference.</param>
|
||||
/// <param name="obj">The resolved object, or null on failure.</param>
|
||||
/// <param name="error">A human-readable reason on failure, or null on success.</param>
|
||||
/// <returns>True if the handle resolved.</returns>
|
||||
public static bool TryResolve(ObjectRef handle, out Object obj, out string error)
|
||||
{
|
||||
obj = null;
|
||||
error = null;
|
||||
|
||||
if (handle == null || handle.IsEmpty)
|
||||
{
|
||||
error = "Empty object reference.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1. Canonical GlobalObjectId.
|
||||
if (!string.IsNullOrEmpty(handle.GlobalId))
|
||||
{
|
||||
if (!GlobalObjectId.TryParse(handle.GlobalId, out var gid))
|
||||
{
|
||||
error = $"Invalid globalId '{handle.GlobalId}'.";
|
||||
return false;
|
||||
}
|
||||
|
||||
obj = GlobalObjectId.GlobalObjectIdentifierToObjectSlow(gid);
|
||||
if (obj != null)
|
||||
return true;
|
||||
|
||||
error = $"globalId '{handle.GlobalId}' did not resolve to a loaded object.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Asset path. Explicit "Assets/"/"Packages/"-rooted paths load as-is; a bare relative
|
||||
// path (e.g. "Materials/Floor.mat") is normalized under the authoring root first. If it
|
||||
// does not resolve to an asset, fall back to a hierarchy lookup so dotted GameObject names
|
||||
// (e.g. "Cube.001") still resolve, and report every strategy that was tried.
|
||||
if (!string.IsNullOrEmpty(handle.Path))
|
||||
{
|
||||
var raw = handle.Path;
|
||||
var explicitlyRooted = IsExplicitlyRooted(raw);
|
||||
var assetPath = raw;
|
||||
if (!explicitlyRooted)
|
||||
{
|
||||
var resolved = ProjectPaths.Resolve(raw, out _);
|
||||
if (!string.IsNullOrEmpty(resolved))
|
||||
assetPath = resolved;
|
||||
}
|
||||
|
||||
obj = AssetDatabase.LoadMainAssetAtPath(assetPath);
|
||||
if (obj != null)
|
||||
return true;
|
||||
|
||||
if (explicitlyRooted)
|
||||
{
|
||||
error = $"No asset at path '{raw}'.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// A bare relative string may actually be a scene hierarchy path with a dotted name.
|
||||
var go = FindByHierarchyPath(raw);
|
||||
if (go != null)
|
||||
{
|
||||
obj = go;
|
||||
return true;
|
||||
}
|
||||
|
||||
error = $"Could not resolve '{raw}': no asset at '{assetPath}', no GameObject at hierarchy path '{raw}'.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. GUID (+ optional fileId for sub-assets).
|
||||
if (!string.IsNullOrEmpty(handle.Guid))
|
||||
{
|
||||
var path = AssetDatabase.GUIDToAssetPath(handle.Guid);
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
error = $"Unknown GUID '{handle.Guid}'.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (handle.FileId.HasValue)
|
||||
{
|
||||
foreach (var candidate in AssetDatabase.LoadAllAssetsAtPath(path))
|
||||
{
|
||||
if (candidate != null &&
|
||||
AssetDatabase.TryGetGUIDAndLocalFileIdentifier(candidate, out _, out long localId) &&
|
||||
localId == handle.FileId.Value)
|
||||
{
|
||||
obj = candidate;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
error = $"No sub-asset with fileId {handle.FileId} in '{path}'.";
|
||||
return false;
|
||||
}
|
||||
|
||||
obj = AssetDatabase.LoadMainAssetAtPath(path);
|
||||
if (obj != null)
|
||||
return true;
|
||||
|
||||
error = $"Could not load asset '{path}'.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. Instance id (loaded/scene object).
|
||||
if (handle.InstanceId.HasValue)
|
||||
{
|
||||
obj = PipelineUtils.IdToObject(handle.InstanceId.Value);
|
||||
if (obj != null)
|
||||
return true;
|
||||
|
||||
error = $"No loaded object with instanceId {handle.InstanceId}.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// 5. Scene hierarchy path.
|
||||
if (!string.IsNullOrEmpty(handle.HierarchyPath))
|
||||
{
|
||||
var go = FindByHierarchyPath(handle.HierarchyPath);
|
||||
if (go != null)
|
||||
{
|
||||
obj = go;
|
||||
return true;
|
||||
}
|
||||
|
||||
error = $"No GameObject at hierarchy path '{handle.HierarchyPath}'.";
|
||||
return false;
|
||||
}
|
||||
|
||||
error = "Empty object reference.";
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Produce the canonical identity for an object (assets get path/guid/fileId; loaded objects
|
||||
/// get instanceId/hierarchyPath). Returns null for a null object.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to describe.</param>
|
||||
/// <returns>The object's canonical identity, or null if <paramref name="obj"/> is null.</returns>
|
||||
public static AuthoringResult Describe(Object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return null;
|
||||
|
||||
var result = new AuthoringResult { Type = obj.GetType().Name };
|
||||
|
||||
var assetPath = AssetDatabase.GetAssetPath(obj);
|
||||
if (!string.IsNullOrEmpty(assetPath))
|
||||
{
|
||||
result.AssetPath = assetPath;
|
||||
if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(obj, out var guid, out long localId))
|
||||
{
|
||||
result.Guid = guid;
|
||||
result.FileId = localId;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result.InstanceId = PipelineUtils.GetObjectId(obj);
|
||||
var go = obj as GameObject ?? (obj as Component)?.gameObject;
|
||||
if (go != null)
|
||||
result.HierarchyPath = GetHierarchyPath(go);
|
||||
}
|
||||
|
||||
// Not every object has a global id (e.g. transient objects); ignore failures.
|
||||
try
|
||||
{
|
||||
result.GlobalId = GlobalObjectId.GetGlobalObjectIdSlow(obj).ToString();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// leave GlobalId null
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>True when a path is explicitly rooted at "Assets/" or "Packages/" (or is exactly one of those).</summary>
|
||||
private static bool IsExplicitlyRooted(string path) =>
|
||||
path == "Assets" || path == "Packages"
|
||||
|| path.StartsWith("Assets/", System.StringComparison.Ordinal)
|
||||
|| path.StartsWith("Packages/", System.StringComparison.Ordinal);
|
||||
|
||||
private static GameObject FindByHierarchyPath(string path)
|
||||
{
|
||||
var parts = path.Trim('/').Split('/');
|
||||
if (parts.Length == 0 || string.IsNullOrEmpty(parts[0]))
|
||||
return null;
|
||||
|
||||
for (int s = 0; s < SceneManager.sceneCount; s++)
|
||||
{
|
||||
var scene = SceneManager.GetSceneAt(s);
|
||||
if (!scene.isLoaded)
|
||||
continue;
|
||||
|
||||
foreach (var root in scene.GetRootGameObjects())
|
||||
{
|
||||
if (root.name != parts[0])
|
||||
continue;
|
||||
|
||||
var current = root.transform;
|
||||
var matched = true;
|
||||
for (int i = 1; i < parts.Length; i++)
|
||||
{
|
||||
var child = current.Find(parts[i]);
|
||||
if (child == null)
|
||||
{
|
||||
matched = false;
|
||||
break;
|
||||
}
|
||||
|
||||
current = child;
|
||||
}
|
||||
|
||||
if (matched)
|
||||
return current.gameObject;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string GetHierarchyPath(GameObject go)
|
||||
{
|
||||
var sb = new System.Text.StringBuilder(go.name);
|
||||
var parent = go.transform.parent;
|
||||
while (parent != null)
|
||||
{
|
||||
sb.Insert(0, parent.name + "/");
|
||||
parent = parent.parent;
|
||||
}
|
||||
|
||||
return "/" + sb;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 26becd8c305c973488fa15b9ea058a00
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,148 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Pipeline.Editor.Authoring
|
||||
{
|
||||
/// <summary>
|
||||
/// Project-path handling for authoring commands.
|
||||
///
|
||||
/// - Paths resolve relative to the configurable <see cref="AuthoringRoot"/> (default "Assets"),
|
||||
/// so callers can pass bare paths ("Gameplay/Enemies") without repeating the "Assets/" prefix.
|
||||
/// - Explicit "Assets/..." (or "Packages/...") paths are treated as project-relative as-is.
|
||||
/// - Every resolved path is confined to <see cref="AuthoringRoot"/> (the sandbox). With the
|
||||
/// default root ("Assets") that means full Assets access; set a sub-folder to confine an agent.
|
||||
/// - "../" traversal and writes outside the project root are always rejected.
|
||||
///
|
||||
/// NOTE: minimal seed for the shared safety policy (CAT-2509).
|
||||
/// </summary>
|
||||
public static class ProjectPaths
|
||||
{
|
||||
private const string DefaultRoot = "Assets";
|
||||
|
||||
// Per-project EditorPrefs key so the root doesn't leak across projects on the same machine.
|
||||
private static string PrefKey => $"Unity.Pipeline.AuthoringRoot:{Application.dataPath}";
|
||||
|
||||
/// <summary>Absolute path to the project root (the folder that contains Assets/).</summary>
|
||||
public static string ProjectRoot => Path.GetDirectoryName(Application.dataPath);
|
||||
|
||||
/// <summary>
|
||||
/// Base folder (project-relative, under Assets/) that bare paths resolve against and that all
|
||||
/// authoring writes are confined to. Defaults to "Assets". Persisted per project via EditorPrefs.
|
||||
/// Setting an invalid value (outside Assets/ or containing "..") throws.
|
||||
/// </summary>
|
||||
public static string AuthoringRoot
|
||||
{
|
||||
get
|
||||
{
|
||||
var root = EditorPrefs.GetString(PrefKey, DefaultRoot);
|
||||
return string.IsNullOrEmpty(root) ? DefaultRoot : root;
|
||||
}
|
||||
set
|
||||
{
|
||||
var normalized = NormalizeRoot(value, out var error);
|
||||
if (normalized == null)
|
||||
throw new ArgumentException(error);
|
||||
EditorPrefs.SetString(PrefKey, normalized);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Reset the authoring root to the default ("Assets").</summary>
|
||||
public static void ResetAuthoringRoot() => EditorPrefs.DeleteKey(PrefKey);
|
||||
|
||||
/// <summary>
|
||||
/// Resolve an agent-supplied path to a normalized, project-relative path confined to
|
||||
/// <see cref="AuthoringRoot"/>. Bare paths are taken relative to the root; explicit
|
||||
/// "Assets/..." paths and absolute paths under the project are honored. Returns null with an
|
||||
/// <paramref name="error"/> when the path escapes the root or the project.
|
||||
/// </summary>
|
||||
/// <param name="path">The agent-supplied path to resolve.</param>
|
||||
/// <param name="error">A human-readable reason on failure, or null on success.</param>
|
||||
/// <returns>The normalized, project-relative path, or null on failure.</returns>
|
||||
public static string Resolve(string path, out string error)
|
||||
{
|
||||
error = null;
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
error = "Path is required.";
|
||||
return null;
|
||||
}
|
||||
|
||||
var root = AuthoringRoot;
|
||||
var normalized = path.Replace('\\', '/').Trim();
|
||||
|
||||
// Absolute path: must live under the project root; convert to project-relative.
|
||||
if (Path.IsPathRooted(normalized))
|
||||
{
|
||||
var full = Path.GetFullPath(normalized).Replace('\\', '/');
|
||||
var projectRoot = ProjectRoot.Replace('\\', '/').TrimEnd('/');
|
||||
if (!full.Equals(projectRoot, StringComparison.OrdinalIgnoreCase) &&
|
||||
!full.StartsWith(projectRoot + "/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
error = $"Path '{path}' is outside the project root '{projectRoot}'.";
|
||||
return null;
|
||||
}
|
||||
|
||||
normalized = full.Length > projectRoot.Length ? full.Substring(projectRoot.Length + 1) : string.Empty;
|
||||
}
|
||||
|
||||
normalized = normalized.TrimStart('/');
|
||||
|
||||
if (normalized.Split('/').Contains(".."))
|
||||
{
|
||||
error = $"Path '{path}' must not contain '..'.";
|
||||
return null;
|
||||
}
|
||||
|
||||
// Explicit project-relative paths ("Assets/..." / "Packages/...") are used as-is; anything
|
||||
// else is a bare path taken relative to the authoring root.
|
||||
var isProjectRelative =
|
||||
normalized == "Assets" || normalized.StartsWith("Assets/", StringComparison.Ordinal) ||
|
||||
normalized == "Packages" || normalized.StartsWith("Packages/", StringComparison.Ordinal);
|
||||
|
||||
var resolved = (isProjectRelative ? normalized : CombineUnder(root, normalized)).TrimEnd('/');
|
||||
|
||||
// Confine to the authoring root (default "Assets" => full Assets access).
|
||||
if (!(resolved == root || resolved.StartsWith(root + "/", StringComparison.Ordinal)))
|
||||
{
|
||||
error = $"Path '{path}' resolves to '{resolved}', which is outside the authoring root '{root}'.";
|
||||
return null;
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
private static string CombineUnder(string root, string relative)
|
||||
{
|
||||
relative = relative.TrimStart('/');
|
||||
return string.IsNullOrEmpty(relative) ? root : $"{root}/{relative}";
|
||||
}
|
||||
|
||||
private static string NormalizeRoot(string value, out string error)
|
||||
{
|
||||
error = null;
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
error = "Authoring root is required.";
|
||||
return null;
|
||||
}
|
||||
|
||||
var normalized = value.Replace('\\', '/').Trim().TrimStart('/').TrimEnd('/');
|
||||
if (normalized.Split('/').Contains(".."))
|
||||
{
|
||||
error = $"Authoring root '{value}' must not contain '..'.";
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(normalized == "Assets" || normalized.StartsWith("Assets/", StringComparison.Ordinal)))
|
||||
{
|
||||
error = $"Authoring root '{value}' must be under the project's Assets/ folder.";
|
||||
return null;
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 462c39b6cec1060408ca75a32bf31d89
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6a4d02bf29da41d499ea151c638ee920
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+355
@@ -0,0 +1,355 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Build;
|
||||
using UnityEditor.Build.Reporting;
|
||||
using UnityEditor.Compilation;
|
||||
using UnityEditor.UnityLinker;
|
||||
using UnityEngine;
|
||||
using Unity.Pipeline.Compilation;
|
||||
using Unity.Pipeline.CodeReload;
|
||||
using IlInterpreter.Interpreter;
|
||||
using Assembly = System.Reflection.Assembly;
|
||||
|
||||
namespace Unity.Pipeline.Editor.BuildProcessors
|
||||
{
|
||||
/// <summary>
|
||||
/// Contributes an auto-generated <c>link.xml</c> that preserves the host-binding surface the
|
||||
/// interpreter exposes to reloaded / eval'd code, so IL2CPP managed stripping cannot remove
|
||||
/// members the built game never referenced (symptom: "Host method '…' is not registered in the
|
||||
/// binding", on device only).
|
||||
///
|
||||
/// Unity's own generated <c>link.xml</c> can't cover this: it preserves what the build *references*,
|
||||
/// and the whole point of code reload is to call members the build never referenced. So preserve
|
||||
/// (a) every type in <see cref="HostBinding.RegisteredTypes"/> with <c>preserve="all"</c>, and
|
||||
/// (b) — because demand-time auto-bind (<see cref="HostBinding.AutoBindResolver"/>) lets reloaded
|
||||
/// code reach ANY user or UnityEngine type — the build's user assemblies and UnityEngine modules
|
||||
/// wholesale.
|
||||
///
|
||||
/// Development builds only: the interpreter reload/eval sinks compile out of release players.
|
||||
/// Also preserves the interpreter engine itself (IlInterpreter + its reflection/metadata
|
||||
/// dependencies) — kept here, rather than in a static <c>Runtime/link.xml</c>, precisely so
|
||||
/// release players don't pay the size cost for machinery they compile out.
|
||||
/// </summary>
|
||||
class CodeReloadLinkXmlGenerator : IUnityLinkerProcessor
|
||||
{
|
||||
public int callbackOrder => 0;
|
||||
|
||||
public string GenerateAdditionalLinkXmlFile(BuildReport report, UnityLinkerBuildPipelineData data)
|
||||
{
|
||||
// Only development builds carry the interpreter; release builds need no extra preservation.
|
||||
if (report == null || !report.summary.options.HasFlag(BuildOptions.Development))
|
||||
return string.Empty;
|
||||
|
||||
// The managed assemblies staged for this player build — the universe for the wholesale
|
||||
// preservation pass. See CollectPlayerAssemblyNames: it unions the linker input directory
|
||||
// with the editor's own player-assembly view, because on some build backends the input
|
||||
// directory is empty at IUnityLinkerProcessor time and relying on it alone silently
|
||||
// collapsed preservation to the explicit per-type surface — stripping everything
|
||||
// demand-time auto-bind reaches (e.g. a live-added `new Slider()`, whose UITK ctors the
|
||||
// built game never referenced) on device only.
|
||||
// inputDirectory is obsolete on the incremental build pipeline (it comes back empty there),
|
||||
// which is exactly the gap the union above closes; on the classic pipeline it is still the
|
||||
// exact staged set, so keep reading it.
|
||||
#pragma warning disable CS0618
|
||||
var playerAssemblies = CollectPlayerAssemblyNames(data.inputDirectory);
|
||||
#pragma warning restore CS0618
|
||||
|
||||
var path = Path.GetFullPath(FileUtil.GetUniqueTempPathInProject() + "-hostbindings.link.xml");
|
||||
File.WriteAllText(path, GenerateLinkXml(playerAssemblies, out var stats));
|
||||
// The stats line is the diagnostic: if a game assembly with [CodeReload] types is missing
|
||||
// from the reloadable-target list (e.g. its name matches the engine-assembly skip
|
||||
// prefixes), its private state can be stripped and binding fails on device only.
|
||||
Debug.Log($"Pipeline: generated code-reload host-binding link.xml at {path} — {stats}");
|
||||
return path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The simple names of the managed assemblies that end up in this player build — the universe
|
||||
/// the wholesale-preservation pass draws from. Unions two sources so a gap in either can't
|
||||
/// silently collapse preservation:
|
||||
/// <list type="bullet">
|
||||
/// <item>the linker input directory (the exact staged set, incl. test assemblies for a test
|
||||
/// player) — recursively, since some backends nest the managed DLLs in a subfolder;</item>
|
||||
/// <item>the editor's own view — <see cref="CompilationPipeline"/> player assemblies (user
|
||||
/// code) plus every loaded <c>UnityEngine*</c> module (the precompiled engine modules
|
||||
/// demand-time auto-bind can construct from) — which stays populated even when the input
|
||||
/// directory is empty at <see cref="IUnityLinkerProcessor"/> time.</item>
|
||||
/// </list>
|
||||
/// A union only adds; it never drops an assembly the input scan found, so a test player keeps
|
||||
/// its test assemblies. Returns null only if every source came up empty (preserve per-type only).
|
||||
/// </summary>
|
||||
internal static string[] CollectPlayerAssemblyNames(string inputDirectory)
|
||||
{
|
||||
var names = new HashSet<string>(StringComparer.Ordinal);
|
||||
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(inputDirectory) && Directory.Exists(inputDirectory))
|
||||
foreach (var dll in Directory.GetFiles(inputDirectory, "*.dll", SearchOption.AllDirectories))
|
||||
names.Add(Path.GetFileNameWithoutExtension(dll));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"Pipeline: could not enumerate linker input assemblies " +
|
||||
$"('{inputDirectory}'): {ex.Message} — falling back to the editor's " +
|
||||
"player-assembly view for wholesale preservation.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var a in CompilationPipeline.GetAssemblies(AssembliesType.Player))
|
||||
if (!string.IsNullOrEmpty(a.name))
|
||||
names.Add(a.name);
|
||||
}
|
||||
catch { /* editor API unavailable in this context — the UnityEngine sweep still runs */ }
|
||||
|
||||
foreach (var a in PipelineUtils.GetLoadedAssemblies())
|
||||
{
|
||||
var n = a.GetName().Name;
|
||||
if (!string.IsNullOrEmpty(n) && n.StartsWith("UnityEngine", StringComparison.Ordinal))
|
||||
names.Add(n);
|
||||
}
|
||||
|
||||
return names.Count > 0 ? new List<string>(names).ToArray() : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build the interpreter's default host binding and emit a <c>link.xml</c> preserving every
|
||||
/// registered type, grouped by owning assembly. Pure and deterministic — directly unit-testable.
|
||||
/// Derives from <see cref="IlInterpreterHostBindings.CreateStandard"/> — the builder the code-reload
|
||||
/// executor runs — so the preservation set cannot drift from the runtime surface. The per-reload
|
||||
/// <c>AllowType(targetType)</c> overlay is excluded: the target is the user's own component,
|
||||
/// which the built player references and IL2CPP won't strip.
|
||||
/// </summary>
|
||||
public static string GenerateLinkXml() => GenerateLinkXml(null, out _);
|
||||
|
||||
public static string GenerateLinkXml(out string stats) => GenerateLinkXml(null, out stats);
|
||||
|
||||
/// <summary><paramref name="stats"/>: one-line summary of what was preserved, logged at build
|
||||
/// time so a missing game assembly is visible in the build log.
|
||||
/// <paramref name="playerAssemblyNames"/>: simple names of the managed assemblies staged for
|
||||
/// the player build; when provided, user assemblies and UnityEngine modules from that set are
|
||||
/// preserved wholesale (including engine-named assemblies carrying [CodeReload] members — the
|
||||
/// attribute marks them as user code), and reload-target entries are limited to assemblies in
|
||||
/// the set (editor-only and test assemblies drop out of game builds); when null, only per-type
|
||||
/// preservation is emitted, unfiltered.</summary>
|
||||
public static string GenerateLinkXml(IEnumerable<string> playerAssemblyNames, out string stats)
|
||||
{
|
||||
var binding = IlInterpreterHostBindings.CreateStandard();
|
||||
|
||||
// SortedSet/SortedDictionary give a stable, diffable ordering and dedupe (closed generics
|
||||
// collapsing onto their definition can produce repeats).
|
||||
var byAssembly = new SortedDictionary<string, SortedSet<string>>(StringComparer.Ordinal);
|
||||
foreach (var t in binding.RegisteredTypes)
|
||||
{
|
||||
// Preserve the open definition for constructed generics (a closed-generic fullname is
|
||||
// assembly-qualified and won't match in link.xml).
|
||||
var def = t.IsGenericType && !t.IsGenericTypeDefinition ? t.GetGenericTypeDefinition() : t;
|
||||
|
||||
var asm = def.Assembly.GetName().Name;
|
||||
var full = def.FullName;
|
||||
if (string.IsNullOrEmpty(asm) || string.IsNullOrEmpty(full))
|
||||
continue;
|
||||
|
||||
full = full.Replace('+', '/'); // link.xml uses '/' as the nested-type separator
|
||||
|
||||
if (!byAssembly.TryGetValue(asm, out var types))
|
||||
byAssembly[asm] = types = new SortedSet<string>(StringComparer.Ordinal);
|
||||
types.Add(full);
|
||||
}
|
||||
|
||||
// Also preserve the reloadable target components. The target type IS referenced by the
|
||||
// built player, but a PRIVATE field only the interpreter touches can still be stripped,
|
||||
// breaking the reflection walk; preserve="all" keeps every member so
|
||||
// AllowNonPublicInstanceMembers(targetType) can resolve private state on device.
|
||||
int hostTypeCount = 0;
|
||||
foreach (var kv in byAssembly)
|
||||
hostTypeCount += kv.Value.Count;
|
||||
|
||||
// The AppDomain scan below also sees editor-only and test assemblies that are not part
|
||||
// of the player build; when the staged set is known, reload targets outside it are
|
||||
// dropped as noise. A test-player build stages its test assemblies, so fixtures keep
|
||||
// their entries exactly where they matter.
|
||||
HashSet<string> staged = null;
|
||||
if (playerAssemblyNames != null)
|
||||
{
|
||||
staged = new HashSet<string>(StringComparer.Ordinal);
|
||||
foreach (var name in playerAssemblyNames)
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
staged.Add(name);
|
||||
}
|
||||
|
||||
int targetTypeCount = 0;
|
||||
var targetAssemblies = new SortedSet<string>(StringComparer.Ordinal);
|
||||
var engineNamedTargetAssemblies = new SortedSet<string>(StringComparer.Ordinal);
|
||||
foreach (var t in CollectReloadableTargetTypes())
|
||||
{
|
||||
var asm = t.Assembly.GetName().Name;
|
||||
var full = t.FullName;
|
||||
if (string.IsNullOrEmpty(asm) || string.IsNullOrEmpty(full))
|
||||
continue;
|
||||
if (staged != null && !staged.Contains(asm))
|
||||
continue;
|
||||
full = full.Replace('+', '/');
|
||||
if (!byAssembly.TryGetValue(asm, out var types))
|
||||
byAssembly[asm] = types = new SortedSet<string>(StringComparer.Ordinal);
|
||||
types.Add(full);
|
||||
targetTypeCount++;
|
||||
targetAssemblies.Add(asm);
|
||||
if (IsNonUserAssembly(asm))
|
||||
engineNamedTargetAssemblies.Add(asm);
|
||||
}
|
||||
|
||||
// Dev-build widening: demand-time auto-bind (HostBinding.AutoBindResolver) lets a reloaded
|
||||
// or eval'd body call into ANY user or UnityEngine type, so RegisteredTypes is not the full
|
||||
// callable set — preserve the build's user assemblies and UnityEngine modules wholesale.
|
||||
// Unity.* packages (Burst, Collections, …) stay per-type: AOT-heavy and rarely touched from
|
||||
// reload bodies. (The project-extension seam that could promote a specific type was
|
||||
// deferred to a future release, so on-device reload bodies can only reach package types
|
||||
// the stripper kept.)
|
||||
var wholesale = new SortedSet<string>(StringComparer.Ordinal);
|
||||
// The interpreter engine itself: it parses pushed override IL via
|
||||
// System.Reflection.Metadata and resolves host members by name, so those paths look
|
||||
// unused to the stripper. Emitted here (dev builds only) rather than in a static
|
||||
// Runtime/link.xml, which would bloat release players that compile the interpreter out.
|
||||
foreach (var name in InterpreterEngineAssemblies)
|
||||
wholesale.Add(name);
|
||||
if (staged != null)
|
||||
{
|
||||
foreach (var name in staged)
|
||||
{
|
||||
if (name.StartsWith("UnityEngine", StringComparison.Ordinal)) wholesale.Add(name);
|
||||
else if (!IsNonUserAssembly(name)) wholesale.Add(name);
|
||||
}
|
||||
|
||||
// A staged, engine-named assembly carrying [CodeReload] members has identified itself
|
||||
// as user code participating in code reload — a package-like name (e.g. "Unity.MyGame")
|
||||
// must not demote it. Preserving only its [CodeReload] types would leave the rest of
|
||||
// it strippable (demand-time auto-bind would hit stripped members on device only),
|
||||
// so promote it into the wholesale set like any other user assembly.
|
||||
foreach (var asm in engineNamedTargetAssemblies)
|
||||
wholesale.Add(asm);
|
||||
}
|
||||
|
||||
stats = $"{hostTypeCount} host-binding type(s) and {targetTypeCount} reloadable target type(s) " +
|
||||
$"across {byAssembly.Count} assembly(ies); target assemblies: " +
|
||||
(targetAssemblies.Count > 0 ? string.Join(", ", targetAssemblies) : "none") +
|
||||
$"; {wholesale.Count} assembly(ies) preserved wholesale for demand-time auto-bind";
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("<linker>");
|
||||
sb.AppendLine(" <!--");
|
||||
sb.AppendLine(" AUTO-GENERATED by CodeReloadLinkXmlGenerator (development builds only). Do not edit.");
|
||||
sb.AppendLine(" Preserves the IlInterpreter interpreter engine and its host-binding surface");
|
||||
sb.AppendLine(" (HostBinding.RegisteredTypes), plus — wholesale — the build's user assemblies and");
|
||||
sb.AppendLine(" UnityEngine modules, so reloaded/eval'd code can call members the built game");
|
||||
sb.AppendLine(" never referenced (including types the interpreter binds on demand), which IL2CPP");
|
||||
sb.AppendLine(" managed stripping would otherwise remove.");
|
||||
sb.AppendLine(" -->");
|
||||
// Wholesale assemblies first (their per-type entries are subsumed), then per-type blocks.
|
||||
foreach (var name in wholesale)
|
||||
sb.AppendLine($" <assembly fullname=\"{name}\" preserve=\"all\"/>");
|
||||
foreach (var kv in byAssembly)
|
||||
{
|
||||
if (wholesale.Contains(kv.Key)) continue;
|
||||
sb.AppendLine($" <assembly fullname=\"{kv.Key}\">");
|
||||
foreach (var type in kv.Value)
|
||||
sb.AppendLine($" <type fullname=\"{type}\" preserve=\"all\"/>");
|
||||
sb.AppendLine(" </assembly>");
|
||||
}
|
||||
sb.AppendLine("</linker>");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
// The IlInterpreter engine and its reflection/metadata dependencies — always preserved
|
||||
// wholesale so pushed code reload keeps working on device.
|
||||
static readonly string[] InterpreterEngineAssemblies =
|
||||
{
|
||||
"Unity.Pipeline.IlInterpreter",
|
||||
"UnityPipeline.System.Reflection.Metadata",
|
||||
"UnityPipeline.System.Collections.Immutable",
|
||||
};
|
||||
|
||||
// Engine/BCL/tooling assembly names — NOT user code. Decides which staged assemblies are
|
||||
// preserved wholesale, and which reload-target assemblies get the engine-named warning.
|
||||
// Matching is deliberately precise (exact "Unity"/"System" or a dotted prefix, never the
|
||||
// bare word) so a game assembly that merely starts with the word — "UnityChanDemo",
|
||||
// "SystemsRuntime" — is still preserved wholesale.
|
||||
static bool IsNonUserAssembly(string name) =>
|
||||
name == "Unity" ||
|
||||
name.StartsWith("Unity.", StringComparison.Ordinal) ||
|
||||
name.StartsWith("UnityEngine", StringComparison.Ordinal) ||
|
||||
name.StartsWith("UnityEditor", StringComparison.Ordinal) ||
|
||||
name == "System" ||
|
||||
name.StartsWith("System.", StringComparison.Ordinal) ||
|
||||
name.StartsWith("Microsoft.", StringComparison.Ordinal) ||
|
||||
name.StartsWith("Mono.", StringComparison.Ordinal) ||
|
||||
name == "mscorlib" ||
|
||||
name == "netstandard" ||
|
||||
name.StartsWith("nunit.", StringComparison.Ordinal);
|
||||
|
||||
/// <summary>
|
||||
/// User-code types that carry a class-level <c>[CodeReload]</c> or any <c>[CodeReload]</c>
|
||||
/// method — i.e. every type whose members can be reloaded through
|
||||
/// the interpreter and therefore needs its (possibly private) fields kept under stripping.
|
||||
/// The attributes live in <c>Unity.Pipeline</c>, so only assemblies referencing it can carry
|
||||
/// them — a cheap, metadata-only check that gates the expensive type scan. Assembly *names*
|
||||
/// play no part here, so game assemblies named like packages ("Unity.MyGame") are covered.
|
||||
/// </summary>
|
||||
static IEnumerable<Type> CollectReloadableTargetTypes()
|
||||
{
|
||||
var results = new List<Type>();
|
||||
foreach (var asm in PipelineUtils.GetLoadedAssemblies())
|
||||
{
|
||||
if (!ReferencesPipelineRuntime(asm))
|
||||
continue;
|
||||
|
||||
Type[] types;
|
||||
try { types = asm.GetTypes(); }
|
||||
catch (ReflectionTypeLoadException ex) { types = ex.Types; }
|
||||
catch { continue; }
|
||||
|
||||
foreach (var t in types)
|
||||
{
|
||||
if (t == null) continue;
|
||||
try
|
||||
{
|
||||
if (t.IsDefined(typeof(CodeReloadAttribute), inherit: false) || HasReloadableMethod(t))
|
||||
results.Add(t);
|
||||
}
|
||||
catch { /* type that can't be inspected — skip */ }
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
static bool ReferencesPipelineRuntime(Assembly asm)
|
||||
{
|
||||
var pipeline = typeof(CodeReloadAttribute).Assembly.GetName().Name;
|
||||
try
|
||||
{
|
||||
foreach (var reference in asm.GetReferencedAssemblies())
|
||||
if (string.Equals(reference.Name, pipeline, StringComparison.Ordinal))
|
||||
return true;
|
||||
}
|
||||
catch { /* dynamic/unloadable assembly — treat as not referencing */ }
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool HasReloadableMethod(Type t)
|
||||
{
|
||||
const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic
|
||||
| BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly;
|
||||
foreach (var m in t.GetMethods(flags))
|
||||
{
|
||||
if (m.IsDefined(typeof(CodeReloadAttribute), inherit: false))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b802ce727e209d74bbbda4467462840a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+375
@@ -0,0 +1,375 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Unity.Pipeline.Config;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Build;
|
||||
using UnityEditor.Build.Reporting;
|
||||
|
||||
namespace Unity.Pipeline.Editor.BuildProcessors
|
||||
{
|
||||
/// <summary>
|
||||
/// Build processor for runtime Pipeline support. Three responsibilities:
|
||||
/// - Validates the RuntimePipelineConfig settings (JSON-authored, read via
|
||||
/// RuntimePipelineConfig.Load()) before allowing builds with runtime Pipeline enabled.
|
||||
/// - Bakes the current settings into a transient RuntimePipelineConfig Resources asset so a
|
||||
/// Player build can find them (the authored copy stays in ProjectSettings/, never here).
|
||||
/// - Bakes the project's code reload scope (Assets + loaded package locations) into a
|
||||
/// generated RuntimePipelineBuildInfo asset. A running Player cannot resolve the project
|
||||
/// layout, so the absolute roots it is allowed to code reload from must be captured at build
|
||||
/// time.
|
||||
/// Both generated assets are purged at the very start of every build (before either is
|
||||
/// possibly rewritten) and deleted again after a successful build, so machine-specific/
|
||||
/// duplicated data never lingers in the project — including a stale, possibly-enabled asset
|
||||
/// left behind by a build that was interrupted before OnPostprocessBuild could run (Unity
|
||||
/// never invokes it for a failed or cancelled build).
|
||||
/// </summary>
|
||||
#if UNITY_6000_3_OR_NEWER
|
||||
class PipelineRuntimeBuildProcessor : IPreprocessBuildWithContext, IPostprocessBuildWithReport
|
||||
#else
|
||||
class PipelineRuntimeBuildProcessor : IPreprocessBuildWithReport, IPostprocessBuildWithReport
|
||||
#endif
|
||||
{
|
||||
/// <summary>Callback ordering relative to other build processors (0 = default).</summary>
|
||||
public int callbackOrder => 0;
|
||||
|
||||
private const string ConfigAssetPath = "Assets/Settings/Pipeline/Resources/RuntimePipelineConfig.asset";
|
||||
private const string BuildInfoAssetPath = "Assets/Settings/Pipeline/Resources/RuntimePipelineBuildInfo.asset";
|
||||
|
||||
// Opt-in symbol that puts the Pipeline into a build that is not a Development Build. Kept in
|
||||
// step with the defineConstraints on Unity.Pipeline / Unity.Pipeline.IlInterpreter and on the
|
||||
// bundled DLLs under Runtime/Plugins/CodeAnalysis: if this file and those disagree, a player
|
||||
// gets settings assets no code can read, or Pipeline code with no settings to start from.
|
||||
private const string EnableSymbol = "ENABLE_RUNTIME_PIPELINE";
|
||||
|
||||
/// <summary>Verify bundled DLL integrity and validate runtime pipeline configuration before a build.</summary>
|
||||
#if UNITY_6000_3_OR_NEWER
|
||||
/// <param name="ctx">The build callback context.</param>
|
||||
public void OnPreprocessBuild(BuildCallbackContext ctx)
|
||||
#else
|
||||
/// <param name="report">The build report.</param>
|
||||
public void OnPreprocessBuild(BuildReport report)
|
||||
#endif
|
||||
{
|
||||
// Purge any leftovers from a previous, interrupted build before anything else in this
|
||||
// method — every exit path below (no config, disabled, not a development build,
|
||||
// validation failure) must start from a clean slate. OnPostprocessBuild is not invoked
|
||||
// for a failed/cancelled build, so it can never be relied on for this;
|
||||
// this purge is the only mechanism that actually guarantees it. Without it, a stale
|
||||
// *enabled* asset left by an earlier interrupted build would still get packaged into a
|
||||
// build that is supposed to have Pipeline disabled.
|
||||
DeleteGeneratedAssetsIfPresent();
|
||||
|
||||
// Integrity gate: fail the build if a bundled Roslyn DLL was swapped or modified.
|
||||
VerifyBundledChecksums();
|
||||
|
||||
var config = RuntimePipelineConfig.Load();
|
||||
if (config == null)
|
||||
{
|
||||
Debug.LogWarning("Pipeline: No RuntimePipelineConfig asset found (Project Settings > Pipeline > Runtime). Pipeline will be disabled in Player builds.");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!config.enableInBuilds)
|
||||
{
|
||||
Debug.LogWarning("Pipeline: RuntimePipelineConfig found, but enableInBuilds = false. Pipeline will be disabled in Player builds.");
|
||||
return;
|
||||
}
|
||||
|
||||
// A build that will not contain the Pipeline assemblies cannot read the generated
|
||||
// Resources assets, so baking them would ship dead, machine-specific data.
|
||||
if (!PipelineIsInBuild())
|
||||
{
|
||||
Debug.Log("Pipeline: enableInBuilds is on, but this build is neither a Development " +
|
||||
$"Build nor defines {EnableSymbol}, so the Pipeline assemblies and settings are " +
|
||||
"excluded from it.");
|
||||
return;
|
||||
}
|
||||
|
||||
var validationResult = config.Validate();
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
throw new BuildFailedException($"Pipeline: Runtime configuration validation failed: {validationResult.Message}");
|
||||
}
|
||||
|
||||
if (validationResult.Level == "warning")
|
||||
{
|
||||
Debug.LogWarning($"Pipeline: Runtime configuration warning: {validationResult.Message}");
|
||||
}
|
||||
|
||||
// Push signing: ensure the project key exists and bake its public half into the
|
||||
// build info, so the player rejects any code-reload push not signed by the matching
|
||||
// private key. Log the fingerprint so a later key mismatch is diagnosable from the
|
||||
// build log alone.
|
||||
var privateKey = PushSigningKey.LoadOrCreate(PushSigningKey.DefaultDirectory, out var keyCreated);
|
||||
var pushPublicKey = Unity.Pipeline.CodeReload.PushEnvelope.DerivePublicKey(privateKey);
|
||||
Debug.Log($"Pipeline: push-signing key {(keyCreated ? "GENERATED" : "loaded")} " +
|
||||
$"(fingerprint {Unity.Pipeline.CodeReload.PushEnvelope.Fingerprint(pushPublicKey)}, " +
|
||||
$"Library/Pipeline/{PushSigningKey.KeyFileName}). This build only accepts pushes signed with it.");
|
||||
|
||||
WriteConfigAsset(config);
|
||||
WriteBuildInfoAsset(pushPublicKey);
|
||||
|
||||
Debug.Log("Pipeline: Runtime server ENABLED in build.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
UnityEngine.Object.DestroyImmediate(config);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether this build will contain the Pipeline runtime assemblies, i.e. whether it satisfies
|
||||
/// their <c>UNITY_EDITOR || DEVELOPMENT_BUILD || ENABLE_RUNTIME_PIPELINE</c> define
|
||||
/// constraint. A scripted build (the <c>build</c> command, which calls BuildPlayer with
|
||||
/// <see cref="BuildOptions.Development"/>) is a development build even when the Build
|
||||
/// Settings toggle is off, so the command's active options count too.
|
||||
/// </summary>
|
||||
private static bool PipelineIsInBuild()
|
||||
{
|
||||
if (EditorUserBuildSettings.development)
|
||||
return true;
|
||||
|
||||
if (Commands.Build.BuildCommand.ActiveScriptedBuildOptions is { } scriptedOptions
|
||||
&& (scriptedOptions & BuildOptions.Development) != 0)
|
||||
return true;
|
||||
|
||||
var group = BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget);
|
||||
var defines = PlayerSettings.GetScriptingDefineSymbols(NamedBuildTarget.FromBuildTargetGroup(group));
|
||||
return defines != null && defines.Split(';').Any(d => d.Trim() == EnableSymbol);
|
||||
}
|
||||
|
||||
public void OnPostprocessBuild(BuildReport report)
|
||||
{
|
||||
DeleteGeneratedAssetsIfPresent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Absolute roots considered in-scope for runtime code reload: the project's Assets folder
|
||||
/// plus the resolved location of every package loaded into the project (a local package may
|
||||
/// live anywhere on disk, not only under Packages).
|
||||
/// </summary>
|
||||
/// <returns>Absolute paths of the project's Assets folder and every loaded package.</returns>
|
||||
public static List<string> CollectProjectRoots()
|
||||
{
|
||||
var roots = new List<string> { Path.GetFullPath(Application.dataPath) };
|
||||
|
||||
foreach (var package in UnityEditor.PackageManager.PackageInfo.GetAllRegisteredPackages())
|
||||
{
|
||||
if (!string.IsNullOrEmpty(package.resolvedPath))
|
||||
{
|
||||
roots.Add(Path.GetFullPath(package.resolvedPath));
|
||||
}
|
||||
}
|
||||
|
||||
return roots;
|
||||
}
|
||||
|
||||
private static void WriteConfigAsset(RuntimePipelineConfig sourceConfig)
|
||||
{
|
||||
var folder = Path.GetDirectoryName(ConfigAssetPath).Replace('\\', '/');
|
||||
CreateFolderRecursive(folder);
|
||||
|
||||
// No DeleteGeneratedAssetIfPresent() call here: OnPreprocessBuild already purged both
|
||||
// paths before any of this method's caller ran, so this path is guaranteed clear.
|
||||
|
||||
var baked = ScriptableObject.CreateInstance<RuntimePipelineConfig>();
|
||||
baked.enableInBuilds = sourceConfig.enableInBuilds;
|
||||
baked.port = sourceConfig.port;
|
||||
baked.requestTimeoutMs = sourceConfig.requestTimeoutMs;
|
||||
baked.enableAuditLogging = sourceConfig.enableAuditLogging;
|
||||
baked.autoStart = sourceConfig.autoStart;
|
||||
baked.maxWorkItemsPerFrame = sourceConfig.maxWorkItemsPerFrame;
|
||||
AssetDatabase.CreateAsset(baked, ConfigAssetPath);
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
|
||||
private static void WriteBuildInfoAsset(string pushPublicKey)
|
||||
{
|
||||
var folder = Path.GetDirectoryName(BuildInfoAssetPath).Replace('\\', '/');
|
||||
CreateFolderRecursive(folder);
|
||||
|
||||
// No DeleteGeneratedAssetIfPresent() call here: OnPreprocessBuild already purged both
|
||||
// paths before any of this method's caller ran, so this path is guaranteed clear.
|
||||
|
||||
var buildInfo = ScriptableObject.CreateInstance<RuntimePipelineBuildInfo>();
|
||||
buildInfo.allowedReloadRoots = CollectProjectRoots();
|
||||
buildInfo.pushPublicKey = pushPublicKey ?? "";
|
||||
AssetDatabase.CreateAsset(buildInfo, BuildInfoAssetPath);
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete both transient build-only assets if either is present, however they got there —
|
||||
/// baked by a previous build (normal cleanup) or left behind by one that never reached
|
||||
/// OnPostprocessBuild (crash, force-quit, cancelled build).
|
||||
/// </summary>
|
||||
private static void DeleteGeneratedAssetsIfPresent()
|
||||
{
|
||||
DeleteGeneratedAssetIfPresent(ConfigAssetPath);
|
||||
DeleteGeneratedAssetIfPresent(BuildInfoAssetPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Untyped load + a raw file-existence fallback, deliberately not a typed
|
||||
/// AssetDatabase.LoadAssetAtPath<T>: a typed load returns null — leaving a stale file
|
||||
/// untouched on disk, inside Resources, where a Player build would still package it — for
|
||||
/// any asset whose script reference broke after a package upgrade, or that was only
|
||||
/// partially written by the very crash that left it behind. Both are exactly the kind of
|
||||
/// leftover this cleanup exists to catch.
|
||||
/// </summary>
|
||||
private static void DeleteGeneratedAssetIfPresent(string assetPath)
|
||||
{
|
||||
if (AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(assetPath) != null || File.Exists(assetPath))
|
||||
AssetDatabase.DeleteAsset(assetPath);
|
||||
}
|
||||
|
||||
private static void CreateFolderRecursive(string folder)
|
||||
{
|
||||
var parts = folder.Split('/');
|
||||
var current = parts[0];
|
||||
for (var i = 1; i < parts.Length; i++)
|
||||
{
|
||||
var next = current + "/" + parts[i];
|
||||
if (!AssetDatabase.IsValidFolder(next))
|
||||
AssetDatabase.CreateFolder(current, parts[i]);
|
||||
current = next;
|
||||
}
|
||||
}
|
||||
|
||||
// Relative location of the bundled Roslyn DLLs + their integrity manifest within the package.
|
||||
private const string CodeAnalysisRelDir = "Runtime/Plugins/CodeAnalysis";
|
||||
private const string ChecksumsFileName = "CHECKSUMS";
|
||||
|
||||
/// <summary>
|
||||
/// Verify the bundled Roslyn DLLs against the committed CHECKSUMS manifest, locating them
|
||||
/// relative to this package on disk. Throws <see cref="BuildFailedException"/> on any
|
||||
/// mismatch so a tampered/swapped DLL cannot be built into a player.
|
||||
/// </summary>
|
||||
public static void VerifyBundledChecksums()
|
||||
{
|
||||
if (!TryGetBundledCodeAnalysisPaths(out var codeAnalysisDir, out var checksumsPath))
|
||||
{
|
||||
throw new BuildFailedException(
|
||||
"Pipeline: could not locate the com.unity.pipeline package on disk to verify " +
|
||||
"bundled Roslyn DLL integrity. Aborting build.");
|
||||
}
|
||||
|
||||
var error = VerifyChecksums(codeAnalysisDir, checksumsPath);
|
||||
if (error != null)
|
||||
{
|
||||
throw new BuildFailedException($"Pipeline: bundled Roslyn DLL integrity check failed. {error}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolve the bundled Roslyn DLL folder and its CHECKSUMS manifest from this package's
|
||||
/// location on disk. Returns false when the package cannot be located, in which case both
|
||||
/// out parameters are null.
|
||||
/// </summary>
|
||||
/// <param name="codeAnalysisDir">Directory holding the bundled Roslyn DLLs.</param>
|
||||
/// <param name="checksumsPath">Path to the CHECKSUMS manifest inside that directory.</param>
|
||||
/// <returns>True when the package was located and both paths were resolved.</returns>
|
||||
public static bool TryGetBundledCodeAnalysisPaths(out string codeAnalysisDir, out string checksumsPath)
|
||||
{
|
||||
codeAnalysisDir = null;
|
||||
checksumsPath = null;
|
||||
|
||||
var packageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssembly(
|
||||
typeof(PipelineRuntimeBuildProcessor).Assembly);
|
||||
|
||||
if (packageInfo == null || string.IsNullOrEmpty(packageInfo.resolvedPath))
|
||||
return false;
|
||||
|
||||
codeAnalysisDir = Path.Combine(packageInfo.resolvedPath, CodeAnalysisRelDir);
|
||||
checksumsPath = Path.Combine(codeAnalysisDir, ChecksumsFileName);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Core, side-effect-free integrity check (so it is directly unit-testable). Returns null
|
||||
/// when every DLL listed in <paramref name="checksumsPath"/> exists under
|
||||
/// <paramref name="codeAnalysisDir"/> with a matching SHA-256 and no unlisted DLL is present;
|
||||
/// otherwise returns a human-readable error describing the first problem found.
|
||||
/// </summary>
|
||||
/// <param name="codeAnalysisDir">Directory containing the bundled Roslyn DLLs.</param>
|
||||
/// <param name="checksumsPath">Path to the CHECKSUMS manifest.</param>
|
||||
/// <returns>Null if every DLL matches; otherwise a human-readable description of the first problem found.</returns>
|
||||
public static string VerifyChecksums(string codeAnalysisDir, string checksumsPath)
|
||||
{
|
||||
if (!Directory.Exists(codeAnalysisDir))
|
||||
return $"DLL directory not found: {codeAnalysisDir}";
|
||||
if (!File.Exists(checksumsPath))
|
||||
return $"CHECKSUMS manifest not found: {checksumsPath}";
|
||||
|
||||
var expected = ParseChecksums(checksumsPath);
|
||||
if (expected.Count == 0)
|
||||
return $"CHECKSUMS manifest has no entries: {checksumsPath}";
|
||||
|
||||
// Every listed DLL must exist and match.
|
||||
foreach (var entry in expected)
|
||||
{
|
||||
var dllPath = Path.Combine(codeAnalysisDir, entry.Key);
|
||||
if (!File.Exists(dllPath))
|
||||
return $"listed DLL is missing: {entry.Key}";
|
||||
|
||||
var actual = ComputeSha256(dllPath);
|
||||
if (!string.Equals(actual, entry.Value, StringComparison.OrdinalIgnoreCase))
|
||||
return $"hash mismatch for {entry.Key} (expected {entry.Value}, got {actual})";
|
||||
}
|
||||
|
||||
// No unlisted DLL may sit alongside them (guards against an injected extra assembly).
|
||||
foreach (var dllPath in Directory.GetFiles(codeAnalysisDir, "*.dll"))
|
||||
{
|
||||
var name = Path.GetFileName(dllPath);
|
||||
if (!expected.ContainsKey(name))
|
||||
return $"unexpected DLL not listed in CHECKSUMS: {name}";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>SHA-256 of a file as a lowercase hex string.</summary>
|
||||
/// <param name="filePath">The file to hash.</param>
|
||||
/// <returns>The lowercase hex-encoded SHA-256 hash.</returns>
|
||||
public static string ComputeSha256(string filePath)
|
||||
{
|
||||
using (var sha = SHA256.Create())
|
||||
using (var stream = File.OpenRead(filePath))
|
||||
{
|
||||
var hash = sha.ComputeHash(stream);
|
||||
var sb = new StringBuilder(hash.Length * 2);
|
||||
foreach (var b in hash)
|
||||
sb.Append(b.ToString("x2"));
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
// Parse "<sha256> <filename> # comment" lines, skipping blanks and '#' comment lines.
|
||||
private static Dictionary<string, string> ParseChecksums(string checksumsPath)
|
||||
{
|
||||
var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var raw in File.ReadAllLines(checksumsPath))
|
||||
{
|
||||
var line = raw.Trim();
|
||||
if (line.Length == 0 || line.StartsWith("#"))
|
||||
continue;
|
||||
|
||||
var tokens = line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (tokens.Length < 2)
|
||||
continue;
|
||||
|
||||
result[tokens[1]] = tokens[0];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 50df0b5c45692fb4a81122ca6eec4709
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,51 @@
|
||||
using UnityEditor;
|
||||
|
||||
namespace Unity.Pipeline.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Drop-in replacements for <see cref="EditorUtility"/>'s progress-bar calls that ALSO make
|
||||
/// the progress visible to a connected CLI over <c>GET /api/progress</c> (CLI-488).</para>
|
||||
/// <para><c>EditorUtility.DisplayProgressBar</c> has no public getter and cannot be observed from
|
||||
/// the pipeline server, so existing code keeps its Editor progress dialog but stays invisible
|
||||
/// to the terminal. Swapping the call site to this class keeps the exact same Editor behavior
|
||||
/// and adds the CLI reporting — including while the main thread is blocked inside the task,
|
||||
/// which is when the dialog (and the terminal bar) matter most:</para>
|
||||
/// <code>
|
||||
/// CliEditorProgress.DisplayProgressBar("Generating World", $"Processing {i}/{n}", (float)i / n);
|
||||
/// // …
|
||||
/// CliEditorProgress.ClearProgressBar();
|
||||
/// </code>
|
||||
/// <para>Code that can take a package dependency but runs headless (batchmode) can call
|
||||
/// <see cref="CliProgress.Report"/> directly instead.</para>
|
||||
/// </summary>
|
||||
public static class CliEditorProgress
|
||||
{
|
||||
/// <summary>Same as <see cref="EditorUtility.DisplayProgressBar"/>, plus CLI reporting.</summary>
|
||||
/// <param name="title">Short title for the operation.</param>
|
||||
/// <param name="info">Detail text (e.g. current step).</param>
|
||||
/// <param name="progress">Progress in [0, 1].</param>
|
||||
public static void DisplayProgressBar(string title, string info, float progress)
|
||||
{
|
||||
CliProgress.Report(title, info, progress: progress);
|
||||
EditorUtility.DisplayProgressBar(title, info, progress);
|
||||
}
|
||||
|
||||
/// <summary>Same as <see cref="EditorUtility.DisplayCancelableProgressBar"/>, plus CLI reporting.</summary>
|
||||
/// <param name="title">Short title for the operation.</param>
|
||||
/// <param name="info">Detail text (e.g. current step).</param>
|
||||
/// <param name="progress">Progress in [0, 1].</param>
|
||||
/// <returns>True if the user clicked Cancel.</returns>
|
||||
public static bool DisplayCancelableProgressBar(string title, string info, float progress)
|
||||
{
|
||||
CliProgress.Report(title, info, progress: progress);
|
||||
return EditorUtility.DisplayCancelableProgressBar(title, info, progress);
|
||||
}
|
||||
|
||||
/// <summary>Same as <see cref="EditorUtility.ClearProgressBar"/>, plus clearing the CLI report.</summary>
|
||||
public static void ClearProgressBar()
|
||||
{
|
||||
CliProgress.Clear();
|
||||
EditorUtility.ClearProgressBar();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bc6ed9f7b4ea400f8ed4508d6e2b761b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetVersion: 1
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e52bfc361c2ed7846a3c4c69b24828c5
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 881bcacc155b44229761a2e2b2917fae
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+427
@@ -0,0 +1,427 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Unity.Pipeline.Commands;
|
||||
using Unity.Pipeline.Editor.Authoring;
|
||||
using Unity.Pipeline.Editor.Commands.GameObjects;
|
||||
using Unity.Pipeline.Models;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Pipeline.Editor.Commands.Animation
|
||||
{
|
||||
/// <summary>
|
||||
/// Group A of CLI-214: author <see cref="AnimationClip"/> assets and their float curves.
|
||||
///
|
||||
/// These sit on the CLI-190 authoring foundation, so:
|
||||
/// - every agent-supplied asset path is funnelled through <see cref="ProjectPaths.Resolve"/>
|
||||
/// (sandboxed to the authoring root),
|
||||
/// - an existing clip is addressed with an <see cref="ObjectRef"/> resolved by
|
||||
/// <see cref="ObjectResolver"/> and re-confined to the authoring root,
|
||||
/// - destructive/overwriting operations require an explicit <c>confirm</c> argument and every
|
||||
/// command supports <c>dry_run</c>.
|
||||
///
|
||||
/// NOTE: AnimationClip sub-object edits (curves, clip settings) are NOT covered by Unity's Undo,
|
||||
/// so changes are persisted with <see cref="EditorUtility.SetDirty"/> + <see cref="AssetDatabase.SaveAssets"/>
|
||||
/// rather than wrapped in an <see cref="AuthoringUndoScope"/>. Only float curves are supported in
|
||||
/// v1 (object-reference / PPtr curves are out of scope).
|
||||
/// </summary>
|
||||
static class AnimationClipCommands
|
||||
{
|
||||
[CliCommand("create_animation_clip",
|
||||
"Create an empty .anim AnimationClip asset under the authoring root, with an optional frame rate and loop flag.",
|
||||
MainThreadRequired = true,
|
||||
Tags = new[] { "animation/clip" })]
|
||||
public static AuthoringResult CreateAnimationClip(
|
||||
[CliArg("path", "Asset path ending in .anim, relative to the authoring root. The Assets/ prefix is optional.", Required = true)] string path,
|
||||
[CliArg("frameRate", "Sampling frame rate of the clip (default 60).")] float frameRate = 60f,
|
||||
[CliArg("loop", "If true, set the clip's loop-time flag in its AnimationClipSettings (default false).")] bool loop = false,
|
||||
[CliArg("confirm", "Required (true) only when overwriting an existing asset at the path.")] bool confirm = false,
|
||||
[CliArg("dry_run", "If true, validate inputs and report what would be created without writing anything.")] bool dryRun = false)
|
||||
{
|
||||
var normalized = ProjectPaths.Resolve(path, out var error);
|
||||
if (normalized == null)
|
||||
throw new ArgumentException(error);
|
||||
|
||||
if (!string.Equals(Path.GetExtension(normalized), ".anim", StringComparison.OrdinalIgnoreCase))
|
||||
throw new ArgumentException($"Animation clip path '{normalized}' must end in .anim.");
|
||||
|
||||
if (frameRate <= 0f)
|
||||
throw new ArgumentException($"frameRate must be positive (got {frameRate}).");
|
||||
|
||||
var exists = AssetDatabase.LoadMainAssetAtPath(normalized) != null;
|
||||
if (exists && !confirm)
|
||||
throw new ArgumentException($"An asset already exists at '{normalized}'. Pass confirm=true to overwrite it.");
|
||||
|
||||
if (dryRun)
|
||||
return new AuthoringResult { AssetPath = normalized, Type = nameof(AnimationClip) };
|
||||
|
||||
EnsureParentFolder(normalized);
|
||||
|
||||
var clip = new AnimationClip { frameRate = frameRate };
|
||||
|
||||
var settings = AnimationUtility.GetAnimationClipSettings(clip);
|
||||
settings.loopTime = loop;
|
||||
AnimationUtility.SetAnimationClipSettings(clip, settings);
|
||||
|
||||
// CreateAsset does not reliably overwrite, so remove an existing asset first (the confirm
|
||||
// guard above gates that one already exists).
|
||||
if (exists)
|
||||
AssetDatabase.DeleteAsset(normalized);
|
||||
|
||||
AssetDatabase.CreateAsset(clip, normalized);
|
||||
EditorUtility.SetDirty(clip);
|
||||
AssetDatabase.SaveAssets();
|
||||
AssetDatabase.ImportAsset(normalized);
|
||||
|
||||
var loaded = AssetDatabase.LoadMainAssetAtPath(normalized);
|
||||
var result = ObjectResolver.Describe(loaded) ?? new AuthoringResult { Type = nameof(AnimationClip) };
|
||||
result.AssetPath = normalized;
|
||||
return result;
|
||||
}
|
||||
|
||||
[CliCommand("set_animation_curve",
|
||||
"Add or replace a single float curve binding on an AnimationClip (via AnimationUtility.SetEditorCurve). " +
|
||||
"Replacing an existing binding overwrites it rather than duplicating.",
|
||||
MainThreadRequired = true,
|
||||
Tags = new[] { "animation/clip" })]
|
||||
public static SetAnimationCurveResult SetAnimationCurve(
|
||||
[CliArg("clip", "Reference to the AnimationClip to edit (path / guid / globalId).", Required = true)] ObjectRef clip,
|
||||
[CliArg("path", "GameObject path relative to the animated root the property lives on. Empty string (default) targets the root.")] string path = "",
|
||||
[CliArg("type", "Component type the property lives on, e.g. \"Transform\", \"UnityEngine.Light\". Resolved via the component TypeResolver.", Required = true)] string type = null,
|
||||
[CliArg("property", "Curve property name, e.g. \"m_LocalPosition.x\", \"m_LocalScale.y\", \"localEulerAnglesRaw.z\".", Required = true)] string property = null,
|
||||
[CliArg("keys", "Keyframes: [{ time, value, inTangent?, outTangent?, weightedMode?: \"None\"|\"In\"|\"Out\"|\"Both\" }]. Omitted tangents default to 0 (flat); this is NOT Unity's Auto tangent mode.", Required = true)] JArray keys = null,
|
||||
[CliArg("dry_run", "If true, validate type/property/keys without writing the curve.")] bool dryRun = false)
|
||||
{
|
||||
var (clipAsset, clipPath) = ResolveClip(clip);
|
||||
var componentType = ResolveCurveType(type);
|
||||
if (string.IsNullOrWhiteSpace(property))
|
||||
throw new ArgumentException("property is required.");
|
||||
|
||||
var bindingPath = path ?? string.Empty;
|
||||
var curve = BuildCurve(keys, out var keyCount);
|
||||
|
||||
var result = new SetAnimationCurveResult
|
||||
{
|
||||
AssetPath = clipPath,
|
||||
Type = nameof(AnimationClip),
|
||||
Binding = new CurveBinding { Path = bindingPath, Type = componentType.Name, Property = property },
|
||||
KeyCount = keyCount
|
||||
};
|
||||
|
||||
if (dryRun)
|
||||
return result;
|
||||
|
||||
var binding = EditorCurveBinding.FloatCurve(bindingPath, componentType, property);
|
||||
AnimationUtility.SetEditorCurve(clipAsset, binding, curve);
|
||||
|
||||
EditorUtility.SetDirty(clipAsset);
|
||||
AssetDatabase.SaveAssets();
|
||||
|
||||
var described = ObjectResolver.Describe(clipAsset);
|
||||
if (described != null)
|
||||
{
|
||||
result.Guid = described.Guid;
|
||||
result.FileId = described.FileId;
|
||||
result.GlobalId = described.GlobalId;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[CliCommand("get_animation_clip",
|
||||
"Read an AnimationClip's metadata and all float curve bindings (optionally with keyframes).",
|
||||
MainThreadRequired = true,
|
||||
Tags = new[] { "animation/clip" })]
|
||||
public static AnimationClipInfo GetAnimationClip(
|
||||
[CliArg("clip", "Reference to the AnimationClip to read (path / guid / globalId).", Required = true)] ObjectRef clip,
|
||||
[CliArg("includeKeys", "If true, include each binding's keyframes (default false).")] bool includeKeys = false)
|
||||
{
|
||||
var (clipAsset, clipPath) = ResolveClip(clip);
|
||||
|
||||
var settings = AnimationUtility.GetAnimationClipSettings(clipAsset);
|
||||
var info = new AnimationClipInfo
|
||||
{
|
||||
AssetPath = clipPath,
|
||||
FrameRate = clipAsset.frameRate,
|
||||
Length = clipAsset.length,
|
||||
Loop = settings.loopTime
|
||||
};
|
||||
|
||||
foreach (var binding in AnimationUtility.GetCurveBindings(clipAsset))
|
||||
{
|
||||
var curve = AnimationUtility.GetEditorCurve(clipAsset, binding);
|
||||
var keyCount = curve?.length ?? 0;
|
||||
|
||||
var entry = new CurveBindingInfo
|
||||
{
|
||||
Path = binding.path,
|
||||
Type = binding.type != null ? binding.type.Name : null,
|
||||
Property = binding.propertyName,
|
||||
KeyCount = keyCount
|
||||
};
|
||||
|
||||
if (includeKeys && curve != null)
|
||||
{
|
||||
entry.Keys = new List<KeyframeInfo>(keyCount);
|
||||
foreach (var key in curve.keys)
|
||||
{
|
||||
entry.Keys.Add(new KeyframeInfo
|
||||
{
|
||||
Time = key.time,
|
||||
Value = key.value,
|
||||
InTangent = key.inTangent,
|
||||
OutTangent = key.outTangent
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
info.Bindings.Add(entry);
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
[CliCommand("remove_animation_curve",
|
||||
"Remove a float curve binding from an AnimationClip (SetEditorCurve(clip, binding, null)). Destructive: requires confirm=true.",
|
||||
MainThreadRequired = true,
|
||||
Tags = new[] { "animation/clip" })]
|
||||
public static SetAnimationCurveResult RemoveAnimationCurve(
|
||||
[CliArg("clip", "Reference to the AnimationClip to edit (path / guid / globalId).", Required = true)] ObjectRef clip,
|
||||
[CliArg("path", "GameObject path relative to the animated root the binding lives on. Empty string (default) targets the root.")] string path = "",
|
||||
[CliArg("type", "Component type of the binding to remove, e.g. \"Transform\". Resolved via the component TypeResolver.", Required = true)] string type = null,
|
||||
[CliArg("property", "Curve property name to remove, e.g. \"m_LocalPosition.x\".", Required = true)] string property = null,
|
||||
[CliArg("confirm", "Must be true to actually remove the binding (destructive guard).")] bool confirm = false,
|
||||
[CliArg("dry_run", "If true, report the binding that would be removed without removing it.")] bool dryRun = false)
|
||||
{
|
||||
var (clipAsset, clipPath) = ResolveClip(clip);
|
||||
var componentType = ResolveCurveType(type);
|
||||
if (string.IsNullOrWhiteSpace(property))
|
||||
throw new ArgumentException("property is required.");
|
||||
|
||||
var bindingPath = path ?? string.Empty;
|
||||
var binding = EditorCurveBinding.FloatCurve(bindingPath, componentType, property);
|
||||
var existing = AnimationUtility.GetEditorCurve(clipAsset, binding);
|
||||
if (existing == null)
|
||||
throw new ArgumentException(
|
||||
$"No curve binding for path='{bindingPath}', type='{componentType.Name}', property='{property}' on '{clipPath}'.");
|
||||
|
||||
var keyCount = existing.length;
|
||||
var result = new SetAnimationCurveResult
|
||||
{
|
||||
AssetPath = clipPath,
|
||||
Type = nameof(AnimationClip),
|
||||
Binding = new CurveBinding { Path = bindingPath, Type = componentType.Name, Property = property },
|
||||
KeyCount = keyCount
|
||||
};
|
||||
|
||||
if (dryRun)
|
||||
return result;
|
||||
|
||||
if (!confirm)
|
||||
throw new ArgumentException(
|
||||
$"Refusing to remove curve '{property}' from '{clipPath}'. Pass confirm=true (destructive, not undoable via Unity's Undo).");
|
||||
|
||||
AnimationUtility.SetEditorCurve(clipAsset, binding, null);
|
||||
EditorUtility.SetDirty(clipAsset);
|
||||
AssetDatabase.SaveAssets();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolve a clip handle to a loaded <see cref="AnimationClip"/> and its sandbox-confined asset
|
||||
/// path, rejecting handles that don't point at an on-disk AnimationClip or that escape the root.
|
||||
/// </summary>
|
||||
private static (AnimationClip clip, string path) ResolveClip(ObjectRef clip)
|
||||
{
|
||||
if (clip == null || clip.IsEmpty)
|
||||
throw new ArgumentException("clip is required.");
|
||||
|
||||
if (!ObjectResolver.TryResolve(clip, out var obj, out var error))
|
||||
throw new ArgumentException(error);
|
||||
|
||||
if (!(obj is AnimationClip clipAsset))
|
||||
throw new ArgumentException($"Reference '{clip}' resolved to a {obj.GetType().Name}, not an AnimationClip.");
|
||||
|
||||
var assetPath = AssetDatabase.GetAssetPath(clipAsset);
|
||||
if (string.IsNullOrEmpty(assetPath))
|
||||
throw new ArgumentException($"Reference '{clip}' does not point at an on-disk AnimationClip.");
|
||||
|
||||
var confined = ProjectPaths.Resolve(assetPath, out var confineError);
|
||||
if (confined == null)
|
||||
throw new ArgumentException(
|
||||
$"Clip '{assetPath}' is outside the authoring root '{ProjectPaths.AuthoringRoot}': {confineError}");
|
||||
|
||||
return (clipAsset, confined);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolve a curve-binding component type. Reuses the component <see cref="TypeResolver"/>,
|
||||
/// which only accepts <see cref="Component"/> subclasses (curves bind to component properties).
|
||||
/// </summary>
|
||||
private static Type ResolveCurveType(string type)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(type))
|
||||
throw new ArgumentException("type is required.");
|
||||
|
||||
var resolved = TypeResolver.ResolveComponentType(type);
|
||||
if (resolved == null)
|
||||
throw new ArgumentException(
|
||||
$"Could not resolve component type '{type}'. Use a short name (e.g. Transform) or a fully-qualified name (e.g. UnityEngine.Light).");
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
/// <summary>Build an <see cref="AnimationCurve"/> from the agent-supplied keyframe array.</summary>
|
||||
private static AnimationCurve BuildCurve(JArray keys, out int keyCount)
|
||||
{
|
||||
if (keys == null || keys.Count == 0)
|
||||
throw new ArgumentException("keys must be a non-empty array of { time, value, ... }.");
|
||||
|
||||
var keyframes = new List<Keyframe>(keys.Count);
|
||||
foreach (var token in keys)
|
||||
{
|
||||
if (!(token is JObject keyObj))
|
||||
throw new ArgumentException("Each key must be an object: { time, value, inTangent?, outTangent?, weightedMode? }.");
|
||||
|
||||
if (keyObj["time"] == null || keyObj["value"] == null)
|
||||
throw new ArgumentException("Each key requires a 'time' and a 'value'.");
|
||||
|
||||
var time = keyObj["time"].ToObject<float>();
|
||||
var value = keyObj["value"].ToObject<float>();
|
||||
var inTangent = keyObj["inTangent"]?.ToObject<float>() ?? 0f;
|
||||
var outTangent = keyObj["outTangent"]?.ToObject<float>() ?? 0f;
|
||||
|
||||
var keyframe = new Keyframe(time, value, inTangent, outTangent);
|
||||
|
||||
var weightedToken = keyObj["weightedMode"];
|
||||
if (weightedToken != null && weightedToken.Type != JTokenType.Null)
|
||||
{
|
||||
var name = weightedToken.ToObject<string>();
|
||||
if (!Enum.TryParse<WeightedMode>(name, ignoreCase: true, out var weightedMode))
|
||||
throw new ArgumentException($"Unknown weightedMode '{name}'. Use None | In | Out | Both.");
|
||||
keyframe.weightedMode = weightedMode;
|
||||
}
|
||||
|
||||
keyframes.Add(keyframe);
|
||||
}
|
||||
|
||||
keyCount = keyframes.Count;
|
||||
return new AnimationCurve(keyframes.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>Create the asset's parent folder chain if it does not yet exist.</summary>
|
||||
private static void EnsureParentFolder(string assetPath)
|
||||
{
|
||||
var parent = Path.GetDirectoryName(assetPath)?.Replace('\\', '/');
|
||||
if (string.IsNullOrEmpty(parent) || AssetDatabase.IsValidFolder(parent))
|
||||
return;
|
||||
|
||||
CreateFolderRecursive(parent);
|
||||
}
|
||||
|
||||
private static void CreateFolderRecursive(string assetsPath)
|
||||
{
|
||||
if (AssetDatabase.IsValidFolder(assetsPath))
|
||||
return;
|
||||
|
||||
var parent = Path.GetDirectoryName(assetsPath)?.Replace('\\', '/');
|
||||
var name = Path.GetFileName(assetsPath);
|
||||
if (string.IsNullOrEmpty(parent) || string.IsNullOrEmpty(name))
|
||||
throw new ArgumentException($"Invalid folder path '{assetsPath}'.");
|
||||
|
||||
if (!AssetDatabase.IsValidFolder(parent))
|
||||
CreateFolderRecursive(parent);
|
||||
|
||||
AssetDatabase.CreateFolder(parent, name);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of <c>set_animation_curve</c> / <c>remove_animation_curve</c>: the clip identity extended
|
||||
/// with the affected binding and its key count.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
class SetAnimationCurveResult : AuthoringResult
|
||||
{
|
||||
[JsonProperty("binding")]
|
||||
public CurveBinding Binding { get; set; }
|
||||
|
||||
[JsonProperty("keyCount")]
|
||||
public int KeyCount { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>A curve binding's address: GameObject path, component type, and property name.</summary>
|
||||
[Serializable]
|
||||
class CurveBinding
|
||||
{
|
||||
[JsonProperty("path")]
|
||||
public string Path { get; set; }
|
||||
|
||||
[JsonProperty("type")]
|
||||
public string Type { get; set; }
|
||||
|
||||
[JsonProperty("property")]
|
||||
public string Property { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>Result of <c>get_animation_clip</c>: clip metadata and its curve bindings.</summary>
|
||||
[Serializable]
|
||||
class AnimationClipInfo
|
||||
{
|
||||
[JsonProperty("assetPath")]
|
||||
public string AssetPath { get; set; }
|
||||
|
||||
[JsonProperty("frameRate")]
|
||||
public float FrameRate { get; set; }
|
||||
|
||||
[JsonProperty("length")]
|
||||
public float Length { get; set; }
|
||||
|
||||
[JsonProperty("loop")]
|
||||
public bool Loop { get; set; }
|
||||
|
||||
[JsonProperty("bindings")]
|
||||
public List<CurveBindingInfo> Bindings { get; set; } = new List<CurveBindingInfo>();
|
||||
}
|
||||
|
||||
/// <summary>A single curve binding in <see cref="AnimationClipInfo"/>, optionally with its keys.</summary>
|
||||
[Serializable]
|
||||
class CurveBindingInfo
|
||||
{
|
||||
[JsonProperty("path")]
|
||||
public string Path { get; set; }
|
||||
|
||||
[JsonProperty("type")]
|
||||
public string Type { get; set; }
|
||||
|
||||
[JsonProperty("property")]
|
||||
public string Property { get; set; }
|
||||
|
||||
[JsonProperty("keyCount")]
|
||||
public int KeyCount { get; set; }
|
||||
|
||||
[JsonProperty("keys", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public List<KeyframeInfo> Keys { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>A single keyframe in a curve binding.</summary>
|
||||
[Serializable]
|
||||
class KeyframeInfo
|
||||
{
|
||||
[JsonProperty("time")]
|
||||
public float Time { get; set; }
|
||||
|
||||
[JsonProperty("value")]
|
||||
public float Value { get; set; }
|
||||
|
||||
[JsonProperty("inTangent")]
|
||||
public float InTangent { get; set; }
|
||||
|
||||
[JsonProperty("outTangent")]
|
||||
public float OutTangent { get; set; }
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user