112 lines
10 KiB
Markdown
112 lines
10 KiB
Markdown
# 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).
|