Skip to content

Commit 3dd5ac7

Browse files
Merge pull request #969 from CoplayDev/release/v9.6.2
chore: bump version to 9.6.2
2 parents b2d95d2 + 91af9cc commit 3dd5ac7

49 files changed

Lines changed: 3069 additions & 100 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/skills/unity-mcp-skill/SKILL.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,11 +178,11 @@ uri="file:///full/path/to/file.cs"
178178

179179
| Category | Key Tools | Use For |
180180
|----------|-----------|---------|
181-
| **Scene** | `manage_scene`, `find_gameobjects` | Scene operations, finding objects |
181+
| **Scene** | `manage_scene`, `find_gameobjects` | Scene operations, finding objects. Multi-scene editing (additive load, close, set active, move GO between scenes), scene templates (`3d_basic`, `2d_basic`, `empty`, `default`), scene validation with `auto_repair`. For build settings, use `manage_build(action="scenes")`. |
182182
| **Objects** | `manage_gameobject`, `manage_components` | Creating/modifying GameObjects |
183183
| **Scripts** | `create_script`, `script_apply_edits`, `validate_script` | C# code management (auto-refreshes on create/edit) |
184184
| **Assets** | `manage_asset`, `manage_prefabs` | Asset operations. **Prefab instantiation** is done via `manage_gameobject(action="create", prefab_path="...")`, not `manage_prefabs`. |
185-
| **Editor** | `manage_editor`, `execute_menu_item`, `read_console` | Editor control, package deployment (`deploy_package`/`restore_package` actions) |
185+
| **Editor** | `manage_editor`, `execute_menu_item`, `read_console` | Editor control, package deployment (`deploy_package`/`restore_package`), undo/redo (`undo`/`redo` actions) |
186186
| **Testing** | `run_tests`, `get_test_job` | Unity Test Framework |
187187
| **Batch** | `batch_execute` | Parallel/bulk operations |
188188
| **Camera** | `manage_camera` | Camera management (Unity Camera + Cinemachine). **Tier 1** (always available): create, target, lens, priority, list, screenshot. **Tier 2** (requires `com.unity.cinemachine`): brain, body/aim/noise pipeline, extensions, blending, force/release. 7 presets: follow, third_person, freelook, dolly, static, top_down, side_scroller. Resource: `mcpforunity://scene/cameras`. Use `ping` to check Cinemachine availability. See [tools-reference.md](references/tools-reference.md#camera-tools). |

.claude/skills/unity-mcp-skill/references/tools-reference.md

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,26 @@ manage_scene(action="get_build_settings") # Build settings
175175
manage_scene(action="create", name="NewScene", path="Assets/Scenes/")
176176
manage_scene(action="load", path="Assets/Scenes/Main.unity")
177177
manage_scene(action="save")
178+
179+
# Scene templates — create with preset objects
180+
manage_scene(action="create", name="Level1", template="3d_basic") # Camera + Light + Ground
181+
manage_scene(action="create", name="Level2", template="2d_basic") # Camera (ortho) + Light
182+
manage_scene(action="create", name="Empty", template="empty") # No default objects
183+
manage_scene(action="create", name="Default", template="default") # Camera + Light (Unity default)
184+
185+
# Multi-scene editing
186+
manage_scene(action="load", path="Assets/Scenes/Level2.unity", additive=True) # Keep current scene
187+
manage_scene(action="get_loaded_scenes") # List all loaded scenes
188+
manage_scene(action="set_active_scene", scene_name="Level2") # Set active scene
189+
manage_scene(action="close_scene", scene_name="Level2") # Unload scene
190+
manage_scene(action="close_scene", scene_name="Level2", remove_scene=True) # Fully remove
191+
manage_scene(action="move_to_scene", target="Player", scene_name="Level2") # Move root GO
192+
193+
# Build settings — use manage_build(action="scenes") instead
194+
195+
# Scene validation
196+
manage_scene(action="validate") # Detect missing scripts, broken prefabs
197+
manage_scene(action="validate", auto_repair=True) # Also auto-fix missing scripts (undoable)
178198
```
179199

180200
### find_gameobjects
@@ -332,6 +352,11 @@ manage_components(
332352
# - "Assets/Prefabs/My.prefab" → String shorthand for asset paths
333353
# - "ObjectName" → String shorthand for scene name lookup
334354
# - 12345 → Integer shorthand for instanceID
355+
#
356+
# Sprite sub-asset references (for SpriteRenderer.sprite, Image.sprite, etc.):
357+
# - {"guid": "...", "spriteName": "SubSprite"} → Sprite sub-asset from atlas
358+
# - {"guid": "...", "fileID": 12345} → Sub-asset by fileID
359+
# Single-sprite textures auto-resolve from guid/path alone.
335360
```
336361

337362
---
@@ -523,6 +548,24 @@ manage_prefabs(
523548
position=[0, 1, 0],
524549
components_to_add=["AudioSource"]
525550
)
551+
552+
# Add child GameObjects to a prefab (single or batch)
553+
manage_prefabs(
554+
action="modify_contents",
555+
prefab_path="Assets/Prefabs/Player.prefab",
556+
create_child=[
557+
{"name": "Child1", "primitive_type": "Sphere", "position": [1, 0, 0]},
558+
{"name": "Child2", "primitive_type": "Cube", "parent": "Child1"}
559+
]
560+
)
561+
562+
# Add a nested prefab instance inside a prefab
563+
manage_prefabs(
564+
action="modify_contents",
565+
prefab_path="Assets/Prefabs/Player.prefab",
566+
create_child={"name": "Bullet", "source_prefab_path": "Assets/Prefabs/Bullet.prefab", "position": [0, 2, 0]}
567+
)
568+
# source_prefab_path and primitive_type are mutually exclusive
526569
```
527570

528571
---
@@ -691,7 +734,7 @@ manage_ui(
691734

692735
### manage_editor
693736

694-
Control Unity Editor state.
737+
Control Unity Editor state, undo/redo.
695738

696739
```python
697740
manage_editor(action="play") # Enter play mode
@@ -708,6 +751,10 @@ manage_editor(action="remove_layer", layer_name="OldLayer")
708751

709752
manage_editor(action="close_prefab_stage") # Exit prefab editing mode back to main scene
710753

754+
# Undo/Redo — returns the affected undo group name
755+
manage_editor(action="undo") # Undo last action
756+
manage_editor(action="redo") # Redo last undone action
757+
711758
# Package deployment (no confirmation dialog — designed for LLM-driven iteration)
712759
manage_editor(action="deploy_package") # Copy configured MCPForUnity source into installed package
713760
manage_editor(action="restore_package") # Revert to pre-deployment backup

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
__pycache__/
1313
__pycache__.meta
1414
build/
15+
!MCPForUnity/**/Build/
1516
dist/
1617
wheels/
1718
*.egg-info

MCPForUnity/Editor/Helpers/ComponentOps.cs

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -592,7 +592,7 @@ private static bool SetSerializedPropertyRecursive(SerializedProperty prop, JTok
592592
}
593593
}
594594

595-
private static bool SetObjectReference(SerializedProperty prop, JToken value, out string error)
595+
internal static bool SetObjectReference(SerializedProperty prop, JToken value, out string error)
596596
{
597597
error = null;
598598

@@ -647,12 +647,18 @@ private static bool SetObjectReference(SerializedProperty prop, JToken value, ou
647647
{
648648
string spriteName = spriteNameToken.ToString();
649649
var allAssets = AssetDatabase.LoadAllAssetsAtPath(path);
650+
var originalRef = prop.objectReferenceValue;
650651
foreach (var asset in allAssets)
651652
{
652653
if (asset is Sprite sprite && sprite.name == spriteName)
653654
{
654655
prop.objectReferenceValue = sprite;
655-
return true;
656+
if (prop.objectReferenceValue != null)
657+
return true;
658+
// Unity rejected the type — restore and report
659+
prop.objectReferenceValue = originalRef;
660+
error = $"Sprite '{spriteName}' found but is not compatible with the property type.";
661+
return false;
656662
}
657663
}
658664

@@ -667,6 +673,7 @@ private static bool SetObjectReference(SerializedProperty prop, JToken value, ou
667673
if (targetFileId != 0)
668674
{
669675
var allAssets = AssetDatabase.LoadAllAssetsAtPath(path);
676+
var originalRef = prop.objectReferenceValue;
670677
foreach (var asset in allAssets)
671678
{
672679
if (asset is Sprite sprite)
@@ -675,7 +682,11 @@ private static bool SetObjectReference(SerializedProperty prop, JToken value, ou
675682
if (spriteFileId == targetFileId)
676683
{
677684
prop.objectReferenceValue = sprite;
678-
return true;
685+
if (prop.objectReferenceValue != null)
686+
return true;
687+
prop.objectReferenceValue = originalRef;
688+
error = $"Sprite with fileID '{targetFileId}' found but is not compatible with the property type.";
689+
return false;
679690
}
680691
}
681692
}
@@ -785,6 +796,43 @@ private static bool AssignObjectReference(SerializedProperty prop, UnityEngine.O
785796
if (prop.objectReferenceValue != null)
786797
return true;
787798

799+
// Sub-asset fallback: e.g., Texture2D → Sprite
800+
string subAssetPath = AssetDatabase.GetAssetPath(resolved);
801+
if (!string.IsNullOrEmpty(subAssetPath))
802+
{
803+
var subAssets = AssetDatabase.LoadAllAssetsAtPath(subAssetPath);
804+
UnityEngine.Object match = null;
805+
int matchCount = 0;
806+
foreach (var sub in subAssets)
807+
{
808+
if (sub == null || sub == resolved) continue;
809+
prop.objectReferenceValue = sub;
810+
if (prop.objectReferenceValue != null)
811+
{
812+
match = sub;
813+
matchCount++;
814+
if (matchCount > 1) break;
815+
}
816+
}
817+
818+
if (matchCount == 1)
819+
{
820+
prop.objectReferenceValue = match;
821+
return true;
822+
}
823+
824+
// Clean up: probing may have left the property dirty
825+
prop.objectReferenceValue = null;
826+
827+
if (matchCount > 1)
828+
{
829+
error = $"Multiple compatible sub-assets found in '{subAssetPath}'. " +
830+
"Use {\"guid\": \"...\", \"spriteName\": \"<name>\"} or " +
831+
"{\"guid\": \"...\", \"fileID\": <id>} for precise selection.";
832+
return false;
833+
}
834+
}
835+
788836
// If the resolved object is a GameObject but the property expects a Component,
789837
// try each component on the GameObject until one is accepted.
790838
if (resolved is GameObject go)

MCPForUnity/Editor/Services/IToolDiscoveryService.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public class ToolMetadata
1717
public bool AutoRegister { get; set; } = true;
1818
public bool RequiresPolling { get; set; } = false;
1919
public string PollAction { get; set; } = "status";
20+
public int MaxPollSeconds { get; set; } = 0;
2021
public bool IsBuiltIn { get; set; }
2122
public string Group { get; set; } = "core";
2223
}

MCPForUnity/Editor/Services/ToolDiscoveryService.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ private ToolMetadata ExtractToolMetadata(Type type, McpForUnityToolAttribute too
133133
AutoRegister = toolAttr.AutoRegister,
134134
RequiresPolling = toolAttr.RequiresPolling,
135135
PollAction = string.IsNullOrEmpty(toolAttr.PollAction) ? "status" : toolAttr.PollAction,
136+
MaxPollSeconds = toolAttr.MaxPollSeconds,
136137
Group = toolAttr.Group ?? "core"
137138
};
138139

MCPForUnity/Editor/Services/Transport/Transports/WebSocketTransportClient.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,7 @@ private async Task SendRegisterToolsAsync(CancellationToken token)
541541
["structured_output"] = tool.StructuredOutput,
542542
["requires_polling"] = tool.RequiresPolling,
543543
["poll_action"] = tool.PollAction,
544+
["max_poll_seconds"] = tool.MaxPollSeconds,
544545
["group"] = string.IsNullOrWhiteSpace(tool.Group) ? "core" : tool.Group
545546
};
546547

MCPForUnity/Editor/Tools/Build.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)