--- name: unity-material description: Edit material and shader properties across Built-in/URP/HDRP --- > **Before calling any skill in this module:** if you are about to call a skill with parameters guessed from its name or description, STOP — read this file (or fetch its schema via `GET /skills/recommend?includeSchema=true`) first. If you already have the parameter definitions from recommend/schema, you may proceed straight to dryRun. ## Triggers - Changing how a surface looks - Tweaking material parameters - Swapping shaders - 调整物体外观、修改材质参数、切换 Shader # Unity Material Skills > **BATCH-FIRST**: Use `*_batch` skills when operating on 2+ objects/materials. ## Operating Mode - **Approval** (default): all mutating skills (`material_create`, `material_create_batch`, `material_assign`, `material_assign_batch`, `material_duplicate`, `material_set_color` / `_emission` / `_texture` / `_float` / `_int` / `_vector` / `_keyword` / `_render_queue` / `_shader` / `_texture_offset` / `_texture_scale` / `_gi_flags`, and the `*_batch` variants) need user grant; grant triggers a single server-side execution that returns the result. - **Auto / Bypass**: those skills execute directly. - Query skills (`material_get_properties`, `material_get_keywords`) are `SkillMode.SemiAuto` — they run in all three modes without grant. - This module contains **no** Delete / PlayMode / Reload / high-risk skills (no NeverInSemi); to delete a material asset, call the `asset` module. ## Guardrails **DO NOT** (common hallucinations): - `material_set_metallic` / `material_set_smoothness` do not exist → use `material_set_float` with `propertyName="_Metallic"` or `"_Glossiness"` (Standard) / `"_Smoothness"` (URP) - `material_set_color` r/g/b/a range is **0–1**, not 0–255 - `material_set_property` does not exist → use the specific setter: `material_set_float`, `material_set_int`, `material_set_vector`, `material_set_color` - `material_get_color` does not exist → use `material_get_properties` (returns all properties including colors) **Routing**: - For shader changes → `material_set_shader` (this module) - For texture tiling → `material_set_texture_scale` / `material_set_texture_offset` - Pipeline-specific property names differ: check Render Pipeline Compatibility table in this doc > ⚠️ **Targeting a GameObject edits the material asset, not that one object.** There is no per-object material instance here: every `material_set_*` resolves a GameObject to `renderer.sharedMaterial` and writes **the asset on disk** — an asset mutation, not a scene edit, whatever the individual skill's declared flags say. A freshly created primitive carries Unity's built-in `Default-Material`, so `material_set_color(name="Cube")` recolours *every* object in the project still using it — the classic "I coloured one cube and the whole scene changed" bug. Target by GameObject name only after confirming that object owns a material nobody else shares (`material_get_properties` → `materialPath` names the asset that would be written). **Safe route for "make this object red"** — create, colour the asset by path, then assign: ```python unity_skills.call_skill("material_create", name="Red", savePath="Assets/Materials") unity_skills.call_skill("material_set_color", path="Assets/Materials/Red.mat", r=1, g=0, b=0) unity_skills.call_skill("material_assign", name="Cube", materialPath="Assets/Materials/Red.mat") ``` > **Object Targeting**: Most single-object skills accept `name` (GameObject name) **or** `path`. Behaviour of `path`: > - In `material_set_*` / `material_get_*` (color/emission/texture/float/int/vector/keyword/shader/render_queue/gi_flags/properties), `path` may be either a **GameObject hierarchy path** *or* a **material asset path** like `Assets/Materials/X.mat` — the skill auto-detects (paths starting with `Assets/` or ending with `.mat` are treated as material assets). > - In `material_assign`, `path` is a **GameObject hierarchy path only**; the material to assign goes in the separate `materialPath` parameter. ## Skills Overview | Single Object | Batch Version | Use Batch When | |---------------|---------------|----------------| | `material_create` | `material_create_batch` | Creating 2+ materials | | `material_assign` | `material_assign_batch` | Assigning to 2+ objects | | `material_set_color` | `material_set_colors_batch` | Setting colors on 2+ objects | | `material_set_emission` | `material_set_emission_batch` | Setting emission on 2+ objects | **No batch needed**: - `material_set_texture` - Set texture - `material_set_texture_offset` / `material_set_texture_scale` - Texture tiling - `material_set_float` / `material_set_int` / `material_set_vector` - Set properties - `material_set_keyword` - Enable/disable shader keywords - `material_set_render_queue` - Set render queue - `material_set_shader` - Change shader - `material_set_gi_flags` - Set global illumination flags - `material_get_properties` / `material_get_keywords` - Query properties - `material_duplicate` - Duplicate material --- ## Skills ### material_create Create a new material (auto-detects render pipeline). | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `name` | string | Yes | - | Material name | | `shaderName` | string | No | auto-detect | Shader (auto-detects URP/HDRP/Standard); an unresolved name falls back to the pipeline default and the response adds `shaderRequested` + `warnings` | | `savePath` | string | No | null | `Assets/...`, or a folder inside an embedded/local package (`Packages//...`); read-only packages are rejected | ### material_create_batch Create multiple materials. | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `items` | json string | Yes | - | JSON array of per-item objects (see example below) | **Returns**: `{success, totalItems, successCount, failCount, results: [{success, name, path}]}` ```python unity_skills.call_skill("material_create_batch", items=[ {"name": "Red", "savePath": "Assets/Materials"}, {"name": "Blue", "savePath": "Assets/Materials"}, {"name": "Green", "savePath": "Assets/Materials"} ]) ``` ### material_assign Assign material to object's renderer. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `name` | string | No* | GameObject name | | `instanceId` | int | No* | Instance ID | | `path` | string | No* | GameObject hierarchy path | | `materialPath` | string | Yes | Material asset to assign (e.g. `Assets/Materials/X.mat`) | ### material_assign_batch Assign materials to multiple objects. | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `items` | json string | Yes | - | JSON array of per-item objects (see example below) | **Returns**: `{success, totalItems, successCount, failCount, results: [{success, gameObject, material}]}` — each item echoes `gameObject` (the object name) and `material` (the assigned asset path); failures come back as `{error, target}`. ```python unity_skills.call_skill("material_assign_batch", items=[ {"name": "Cube1", "materialPath": "Assets/Materials/Red.mat"}, {"name": "Cube2", "materialPath": "Assets/Materials/Blue.mat"} ]) ``` ### material_set_color Set material color with optional HDR intensity. | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `name` | string | No* | - | GameObject name | | `instanceId` | int | No* | 0 | GameObject instance ID | | `path` | string | No* | - | Material asset path | | `r`, `g`, `b` | float | No | 1 | Color (0-1) | | `a` | float | No | 1 | Alpha | | `propertyName` | string | No | auto-detect | Color property | | `intensity` | float | No | 1.0 | HDR intensity (>1 for bloom) | **Returns**: `{success, target, color, intensity, propertyUsed, hdrEnabled, materialPath}`. Auto-detection tries the pipeline's property first, then `_BaseColor`, `_Color`, `_TintColor`, `_EmissionColor`, and **`propertyUsed` reports which one it actually wrote** — check it before concluding a colour "didn't apply". An explicit `propertyName` that the shader doesn't have (or that names a non-colour property like `_Metallic`) is rejected with `validValues`; one that's missing but is `_BaseColor`/`_Color`/`_MainColor`/`_TintColor` falls back to whichever of that family exists (`propertyRequested` + `warnings` report it). It never falls back to `_EmissionColor` for an explicit request. ### material_set_colors_batch Set colors on multiple objects. Each item accepts: identifier (`name`/`instanceId`/`path`) + `r`, `g`, `b`, `a`, optional per-item `propertyName` (same resolution rules as `material_set_color`; overrides the batch-level `propertyName`). | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `items` | json string | Yes | - | JSON array of `{name\|instanceId\|path, r, g, b, a, propertyName?}` per-item objects (see example below) | | `propertyName` | string | No | auto-detect | Default color property applied to all items unless overridden | **Returns**: `{success, totalItems, successCount, failCount, results: [{target, success, propertyUsed, color}]}` — the per-item key is `target` (GameObject name, or the path when you addressed the asset), not `name`; a rejected item fails the whole batch and rolls back the others (`rolledBack:true`, `results[i].reverted:true`). ```python unity_skills.call_skill("material_set_colors_batch", items=[ {"name": "Cube1", "r": 1, "g": 0, "b": 0}, {"name": "Cube2", "r": 0, "g": 1, "b": 0}, {"name": "Cube3", "r": 0, "g": 0, "b": 1} ]) ``` ### material_set_emission Set emission color with auto-enable keyword. | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `name` | string | No* | - | GameObject name | | `instanceId` | int | No* | 0 | GameObject instance ID | | `path` | string | No* | - | Material asset path | | `r`, `g`, `b` | float | No | 1 | Emission color (0-1) | | `intensity` | float | No | 1.0 | HDR intensity (>1 for bloom) | | `enableEmission` | bool | No | true | Auto-enable _EMISSION keyword | ### material_set_emission_batch Set emission on multiple objects. | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `items` | json string | Yes | - | JSON array of per-item objects (see example below) | **Returns**: `{success, totalItems, successCount, failCount, results: [{success, target, emissionColor, intensity, hdrColor, emissionEnabled}]}` — a rejected item fails the whole batch and rolls back the others (`rolledBack:true`). ```python unity_skills.call_skill("material_set_emission_batch", items=[ {"name": "Neon1", "r": 1, "g": 0, "b": 1, "intensity": 5.0}, {"name": "Neon2", "r": 0, "g": 1, "b": 1, "intensity": 5.0} ]) ``` ### material_set_texture Set material texture. | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `name` | string | No* | - | GameObject name | | `instanceId` | int | No* | 0 | GameObject instance ID | | `path` | string | No* | - | Material asset path | | `texturePath` | string | Yes | - | Texture asset path | | `propertyName` | string | No | auto-detect | Texture property | ### material_set_float Set a float property on a material. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `name` | string | No* | GameObject name | | `instanceId` | int | No* | GameObject instance ID | | `path` | string | No* | Material asset path | | `propertyName` | string | Yes | Property name | | `value` | float | No | Value (default 0) | ### material_set_int Set an integer property on a material. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `name` | string | No* | GameObject name | | `instanceId` | int | No* | GameObject instance ID | | `path` | string | No* | Material asset path | | `propertyName` | string | Yes | Property name | | `value` | int | No | Value (default 0) | ### material_set_keyword Enable/disable shader keywords. | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `name` | string | No* | - | GameObject name | | `instanceId` | int | No* | 0 | GameObject instance ID | | `path` | string | No* | - | Material asset path | | `keyword` | string | Yes | - | Keyword name | | `enable` | bool | No | true | Enable or disable | **Common Keywords**: `_EMISSION`, `_NORMALMAP`, `_METALLICGLOSSMAP`, `_ALPHATEST_ON`, `_ALPHABLEND_ON` ### material_get_properties Get all material properties. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `name` | string | No* | GameObject name | | `instanceId` | int | No* | GameObject instance ID | | `path` | string | No* | Material asset path | **Returns**: `{success, target, materialPath, shader, renderQueue, keywords, giFlags, properties: {...}}` `properties` is a **dictionary of five typed groups**, not a flat list: `colors`, `floats`, `vectors`, `textures`, `integers`. Each group is an array of `{name, description, value}` (floats also carry `min`/`max`; a texture's `value` is the texture's name or null). So a property lookup is `properties.colors[i].name`, never `properties[i]`. `materialPath` is the asset path of the material actually inspected, and it is only ever a path you can feed straight back in. It is `""` for a built-in material, and also `""` for a material **embedded in another asset** such as an `.fbx`: that material does have a containing file, but the file's main asset is a GameObject rather than a Material, so handing the `.fbx` path back would fail with "material not found". An empty `materialPath` therefore means "this material cannot be addressed by path" — resolve it through its GameObject instead, and do not synthesise a path from the model's. Read it whenever you resolved the material through a GameObject: a non-empty value is the file a subsequent `material_set_*` on that object would write. ### material_get_keywords Get all enabled shader keywords on a material. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `name` | string | No* | GameObject name | | `instanceId` | int | No* | GameObject instance ID | | `path` | string | No* | Material asset path | **Returns**: `{success, target, materialPath, shader, enabledKeywords, commonKeywordStatus}` — `materialPath` is the resolved asset path of the material actually inspected (same guarantee as `material_get_properties`: `""` for a built-in material, and `""` for one embedded in another asset such as an `.fbx`, because that container's path is not feedable back in), and `commonKeywordStatus` is a `{keyword, enabled}` array covering the 14 usual suspects, so an absent keyword is distinguishable from an unchecked one. ### material_duplicate Duplicate a material asset. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `sourcePath` | string | Yes | Source material path | | `newName` | string | Yes | Name for the duplicated material | | `savePath` | string | No | Optional folder/path override for the duplicated material; `Assets/...` or an embedded/local package folder, read-only packages rejected. Omitted: saved next to the source material | ### material_set_shader Change the shader of a material. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `name` | string | No* | GameObject name | | `instanceId` | int | No* | GameObject instance ID | | `path` | string | No* | Material asset path | | `shaderName` | string | Yes | Shader name | ### material_set_vector Set a Vector4 property on a material. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `name` | string | No* | GameObject name | | `instanceId` | int | No* | GameObject instance ID | | `path` | string | No* | Material asset path | | `propertyName` | string | Yes | Property name | | `x`, `y`, `z`, `w` | float | Yes | Vector components | ### material_set_texture_offset Set texture offset (tiling position). | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `name` | string | No* | GameObject name | | `instanceId` | int | No* | GameObject instance ID | | `path` | string | No* | Material asset path | | `propertyName` | string | No | Texture property name | | `x`, `y` | float | Yes | Offset values | ### material_set_texture_scale Set texture scale (tiling). | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `name` | string | No* | GameObject name | | `instanceId` | int | No* | GameObject instance ID | | `path` | string | No* | Material asset path | | `propertyName` | string | No | Texture property name | | `x`, `y` | float | Yes | Scale values | ### material_set_render_queue Set material render queue. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `name` | string | No* | GameObject name | | `instanceId` | int | No* | GameObject instance ID | | `path` | string | No* | Material asset path | | `renderQueue` | int | No | Render queue value (default -1 = shader default) | ### material_set_gi_flags Set material global illumination flags. | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `name` | string | No* | - | GameObject name | | `instanceId` | int | No* | 0 | GameObject instance ID | | `path` | string | No* | - | GameObject hierarchy path or material asset path | | `flags` | string | No | `RealtimeEmissive` | GI flags: `None` / `RealtimeEmissive` / `BakedEmissive` / `EmissiveIsBlack` / `AnyEmissive` | --- ## Example: Efficient Material Setup ```python import unity_skills # BAD: 6 API calls unity_skills.call_skill("material_create", name="Mat1", savePath="Assets/Materials") unity_skills.call_skill("material_create", name="Mat2", savePath="Assets/Materials") unity_skills.call_skill("material_set_color", path="Assets/Materials/Mat1.mat", r=1, g=0, b=0) unity_skills.call_skill("material_set_color", path="Assets/Materials/Mat2.mat", r=0, g=0, b=1) unity_skills.call_skill("material_assign", name="Cube1", materialPath="Assets/Materials/Mat1.mat") unity_skills.call_skill("material_assign", name="Cube2", materialPath="Assets/Materials/Mat2.mat") # GOOD: 3 API calls unity_skills.call_skill("material_create_batch", items=[ {"name": "Mat1", "savePath": "Assets/Materials"}, {"name": "Mat2", "savePath": "Assets/Materials"} ]) unity_skills.call_skill("material_set_colors_batch", items=[ {"path": "Assets/Materials/Mat1.mat", "r": 1, "g": 0, "b": 0}, {"path": "Assets/Materials/Mat2.mat", "r": 0, "g": 0, "b": 1} ]) unity_skills.call_skill("material_assign_batch", items=[ {"name": "Cube1", "materialPath": "Assets/Materials/Mat1.mat"}, {"name": "Cube2", "materialPath": "Assets/Materials/Mat2.mat"} ]) ``` ## Render Pipeline Compatibility Skills auto-detect and adapt to your render pipeline: | Pipeline | Default Shader | Color Property | Texture Property | |----------|---------------|----------------|------------------| | Built-in | Standard | `_Color` | `_MainTex` | | URP | Universal Render Pipeline/Lit | `_BaseColor` | `_BaseMap` | | HDRP | HDRP/Lit | `_BaseColor` | `_BaseColorMap` | > **URP Lit exposes both `_BaseColor` and a legacy `_Color`, and only `_BaseColor` is authoritative.** `material_get_properties` lists both, but writing `_BaseColor` does **not** update the `_Color` entry — a verification pass that reads `_Color` will report the old colour and look like a failed write. Compare against `_BaseColor`, and trust the setter's `propertyUsed` field over your own guess about which property was involved. Only pass `propertyName` explicitly when auto-detection picked the wrong one. ## Best Practices 1. Save materials as assets for reuse 2. Write by asset path (`path="Assets/.../X.mat"`): it states exactly which asset changes 3. Targeting by GameObject name writes that renderer's **shared** material asset — see the warning at the top of this file; it is not a per-object override 4. Check shader property names in Unity Inspector, or read them from `material_get_properties` 5. URP/HDRP have different property names than Standard --- ## Exact Signatures Exact names, parameters, defaults, and returns are defined by `GET /skills/schema` or `unity_skills.get_skill_schema()`, not by this file. ## Common Errors Full transport-level codes (COMPILING/RATE_LIMIT etc.) → ../../references/protocol-error-codes.md | Error | Trigger | Fix | |---|---|---| | `TARGET_NOT_FOUND` | The material asset, GameObject/renderer, shader, texture, or property could not be found (e.g., `Material asset not found`, `No Renderer component found`, `Shader not found`). | Verify the asset path with `asset_find`, the object with `gameobject_find`, or inspect available properties with `material_get_properties`. | | `MISSING_PARAM` | A required parameter is missing, such as `materialPath`, `sourcePath`, `texturePath`, `propertyName`, `keyword`, or `shaderName`. | Supply the parameter named in the error and retry; use `mode=dryRun` for the full schema. | | `SEMANTIC_INVALID` | An invalid value was supplied, such as an unrecognized GI flag, an invalid asset path, a property name the shader does not use, or a `propertyName` the shader does not declare as a colour. | Correct the value using the allowed range/enum/path convention described in the error. |