From fed9dbdbadaa256b4c8c7adb989b9142cbf57980 Mon Sep 17 00:00:00 2001 From: cyanxwh Date: Sat, 17 Jan 2026 18:01:26 +0800 Subject: [PATCH 1/2] Enhance Prefab Stage support in GameObject lookup and scene management Co-Authored-By: Claude Opus 4.5 --- .../Editor/Helpers/GameObjectLookup.cs | 60 ++++++++++++++++--- .../GameObjects/ManageGameObjectCommon.cs | 46 +++++++++----- MCPForUnity/Editor/Tools/ManageScene.cs | 17 +++++- 3 files changed, 97 insertions(+), 26 deletions(-) diff --git a/MCPForUnity/Editor/Helpers/GameObjectLookup.cs b/MCPForUnity/Editor/Helpers/GameObjectLookup.cs index 0a4fbc27a..6bf94f580 100644 --- a/MCPForUnity/Editor/Helpers/GameObjectLookup.cs +++ b/MCPForUnity/Editor/Helpers/GameObjectLookup.cs @@ -3,6 +3,7 @@ using System.Linq; using Newtonsoft.Json.Linq; using UnityEditor; +using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.SceneManagement; @@ -147,19 +148,49 @@ private static IEnumerable SearchByName(string name, bool includeInactive, private static IEnumerable SearchByPath(string path, bool includeInactive) { + // Check Prefab Stage first - GameObject.Find() doesn't work in Prefab Stage + var prefabStage = PrefabStageUtility.GetCurrentPrefabStage(); + if (prefabStage != null) + { + // Use GetAllSceneObjects which already handles Prefab Stage + var allObjects = GetAllSceneObjects(includeInactive); + foreach (var go in allObjects) + { + if (go == null) continue; + var goPath = GetGameObjectPath(go); + if (goPath == path || goPath.EndsWith("/" + path)) + { + yield return go.GetInstanceID(); + } + } + yield break; + } + + // Normal scene mode // NOTE: Unity's GameObject.Find(path) only finds ACTIVE GameObjects. - // The includeInactive parameter has no effect here due to Unity API limitations. - // Consider using by_name search with includeInactive if you need to find inactive objects. + // If includeInactive=true, we need to search manually to find inactive objects. if (includeInactive) { - McpLog.Warn("[GameObjectLookup] SearchByPath with includeInactive=true: " + - "GameObject.Find() cannot find inactive objects. Use by_name search instead."); + // Search manually to support inactive objects + var allObjects = GetAllSceneObjects(true); + foreach (var go in allObjects) + { + if (go == null) continue; + var goPath = GetGameObjectPath(go); + if (goPath == path || goPath.EndsWith("/" + path)) + { + yield return go.GetInstanceID(); + } + } } - - var found = GameObject.Find(path); - if (found != null) + else { - yield return found.GetInstanceID(); + // Use GameObject.Find for active objects only (Unity API limitation) + var found = GameObject.Find(path); + if (found != null) + { + yield return found.GetInstanceID(); + } } } @@ -249,6 +280,19 @@ private static IEnumerable SearchByComponent(string componentTypeName, bool /// public static IEnumerable GetAllSceneObjects(bool includeInactive) { + // Check Prefab Stage first + var prefabStage = PrefabStageUtility.GetCurrentPrefabStage(); + if (prefabStage != null && prefabStage.prefabContentsRoot != null) + { + // Use Prefab Stage's prefabContentsRoot + foreach (var go in GetObjectAndDescendants(prefabStage.prefabContentsRoot, includeInactive)) + { + yield return go; + } + yield break; + } + + // Normal scene mode var scene = SceneManager.GetActiveScene(); if (!scene.IsValid()) yield break; diff --git a/MCPForUnity/Editor/Tools/GameObjects/ManageGameObjectCommon.cs b/MCPForUnity/Editor/Tools/GameObjects/ManageGameObjectCommon.cs index 38d743f12..3df9d5389 100644 --- a/MCPForUnity/Editor/Tools/GameObjects/ManageGameObjectCommon.cs +++ b/MCPForUnity/Editor/Tools/GameObjects/ManageGameObjectCommon.cs @@ -83,11 +83,26 @@ internal static List FindObjectsInternal( break; case "by_path": - Transform foundTransform = rootSearchObject - ? rootSearchObject.transform.Find(searchTerm) - : GameObject.Find(searchTerm)?.transform; - if (foundTransform != null) - results.Add(foundTransform.gameObject); + if (rootSearchObject != null) + { + Transform foundTransform = rootSearchObject.transform.Find(searchTerm); + if (foundTransform != null) + results.Add(foundTransform.gameObject); + } + else + { + // In Prefab Stage, GameObject.Find() doesn't work, need to search manually + var allObjects = GetAllSceneObjects(searchInactive); + foreach (var go in allObjects) + { + if (go == null) continue; + var goPath = GameObjectLookup.GetGameObjectPath(go); + if (goPath == searchTerm || goPath.EndsWith("/" + searchTerm)) + { + results.Add(go); + } + } + } break; case "by_tag": @@ -154,7 +169,14 @@ internal static List FindObjectsInternal( } } - GameObject objByPath = GameObject.Find(searchTerm); + // Try path search - in Prefab Stage, GameObject.Find() doesn't work + var allObjectsForPath = GetAllSceneObjects(true); + GameObject objByPath = allObjectsForPath.FirstOrDefault(go => + { + if (go == null) return false; + var goPath = GameObjectLookup.GetGameObjectPath(go); + return goPath == searchTerm || goPath.EndsWith("/" + searchTerm); + }); if (objByPath != null) { results.Add(objByPath); @@ -180,16 +202,8 @@ internal static List FindObjectsInternal( private static IEnumerable GetAllSceneObjects(bool includeInactive) { - var rootObjects = SceneManager.GetActiveScene().GetRootGameObjects(); - var allObjects = new List(); - foreach (var root in rootObjects) - { - allObjects.AddRange( - root.GetComponentsInChildren(includeInactive) - .Select(t => t.gameObject) - ); - } - return allObjects; + // Delegate to GameObjectLookup to avoid code duplication and ensure consistent behavior + return GameObjectLookup.GetAllSceneObjects(includeInactive); } private static Type FindType(string typeName) diff --git a/MCPForUnity/Editor/Tools/ManageScene.cs b/MCPForUnity/Editor/Tools/ManageScene.cs index 8bc5456c2..d6d2839e9 100644 --- a/MCPForUnity/Editor/Tools/ManageScene.cs +++ b/MCPForUnity/Editor/Tools/ManageScene.cs @@ -490,8 +490,21 @@ private static object GetSceneHierarchyPaged(SceneCommand cmd) { try { - try { McpLog.Info("[ManageScene] get_hierarchy: querying EditorSceneManager.GetActiveScene", always: false); } catch { } - Scene activeScene = EditorSceneManager.GetActiveScene(); + // Check Prefab Stage first + var prefabStage = PrefabStageUtility.GetCurrentPrefabStage(); + Scene activeScene; + + if (prefabStage != null) + { + activeScene = prefabStage.scene; + try { McpLog.Info("[ManageScene] get_hierarchy: using Prefab Stage scene", always: false); } catch { } + } + else + { + try { McpLog.Info("[ManageScene] get_hierarchy: querying EditorSceneManager.GetActiveScene", always: false); } catch { } + activeScene = EditorSceneManager.GetActiveScene(); + } + try { McpLog.Info($"[ManageScene] get_hierarchy: got scene valid={activeScene.IsValid()} loaded={activeScene.isLoaded} name='{activeScene.name}'", always: false); } catch { } if (!activeScene.IsValid() || !activeScene.isLoaded) { From d15fd879699be70cee1b8c3daeabfc56e74a63b4 Mon Sep 17 00:00:00 2001 From: cyanxwh Date: Mon, 19 Jan 2026 11:29:44 +0800 Subject: [PATCH 2/2] fix: unify path matching and restore fast path lookup --- .../Editor/Helpers/GameObjectLookup.cs | 20 +++++++++----- .../GameObjects/ManageGameObjectCommon.cs | 27 ++++++++++++------- MCPForUnity/Editor/Tools/ManageScene.cs | 11 +++++++- 3 files changed, 41 insertions(+), 17 deletions(-) diff --git a/MCPForUnity/Editor/Helpers/GameObjectLookup.cs b/MCPForUnity/Editor/Helpers/GameObjectLookup.cs index 6bf94f580..60d2b0967 100644 --- a/MCPForUnity/Editor/Helpers/GameObjectLookup.cs +++ b/MCPForUnity/Editor/Helpers/GameObjectLookup.cs @@ -156,9 +156,7 @@ private static IEnumerable SearchByPath(string path, bool includeInactive) var allObjects = GetAllSceneObjects(includeInactive); foreach (var go in allObjects) { - if (go == null) continue; - var goPath = GetGameObjectPath(go); - if (goPath == path || goPath.EndsWith("/" + path)) + if (MatchesPath(go, path)) { yield return go.GetInstanceID(); } @@ -175,9 +173,7 @@ private static IEnumerable SearchByPath(string path, bool includeInactive) var allObjects = GetAllSceneObjects(true); foreach (var go in allObjects) { - if (go == null) continue; - var goPath = GetGameObjectPath(go); - if (goPath == path || goPath.EndsWith("/" + path)) + if (MatchesPath(go, path)) { yield return go.GetInstanceID(); } @@ -334,6 +330,18 @@ public static Type FindComponentType(string typeName) return UnityTypeResolver.ResolveComponent(typeName); } + /// + /// Checks whether a GameObject matches a path or trailing path segment. + /// + internal static bool MatchesPath(GameObject go, string path) + { + if (go == null || string.IsNullOrEmpty(path)) + return false; + + var goPath = GetGameObjectPath(go); + return goPath == path || goPath.EndsWith("/" + path); + } + /// /// Gets the hierarchical path of a GameObject. /// diff --git a/MCPForUnity/Editor/Tools/GameObjects/ManageGameObjectCommon.cs b/MCPForUnity/Editor/Tools/GameObjects/ManageGameObjectCommon.cs index 3df9d5389..e686145ef 100644 --- a/MCPForUnity/Editor/Tools/GameObjects/ManageGameObjectCommon.cs +++ b/MCPForUnity/Editor/Tools/GameObjects/ManageGameObjectCommon.cs @@ -5,6 +5,7 @@ using MCPForUnity.Editor.Helpers; using MCPForUnity.Editor.Tools; using Newtonsoft.Json.Linq; +using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.SceneManagement; @@ -91,17 +92,25 @@ internal static List FindObjectsInternal( } else { - // In Prefab Stage, GameObject.Find() doesn't work, need to search manually - var allObjects = GetAllSceneObjects(searchInactive); - foreach (var go in allObjects) + var prefabStage = PrefabStageUtility.GetCurrentPrefabStage(); + if (prefabStage != null || searchInactive) { - if (go == null) continue; - var goPath = GameObjectLookup.GetGameObjectPath(go); - if (goPath == searchTerm || goPath.EndsWith("/" + searchTerm)) + // In Prefab Stage, GameObject.Find() doesn't work, need to search manually + var allObjects = GetAllSceneObjects(searchInactive); + foreach (var go in allObjects) { - results.Add(go); + if (GameObjectLookup.MatchesPath(go, searchTerm)) + { + results.Add(go); + } } } + else + { + var found = GameObject.Find(searchTerm); + if (found != null) + results.Add(found); + } } break; @@ -173,9 +182,7 @@ internal static List FindObjectsInternal( var allObjectsForPath = GetAllSceneObjects(true); GameObject objByPath = allObjectsForPath.FirstOrDefault(go => { - if (go == null) return false; - var goPath = GameObjectLookup.GetGameObjectPath(go); - return goPath == searchTerm || goPath.EndsWith("/" + searchTerm); + return GameObjectLookup.MatchesPath(go, searchTerm); }); if (objByPath != null) { diff --git a/MCPForUnity/Editor/Tools/ManageScene.cs b/MCPForUnity/Editor/Tools/ManageScene.cs index d6d2839e9..e58c09424 100644 --- a/MCPForUnity/Editor/Tools/ManageScene.cs +++ b/MCPForUnity/Editor/Tools/ManageScene.cs @@ -612,7 +612,16 @@ private static GameObject ResolveGameObject(JToken targetToken, Scene activeScen // Path-based find (e.g., "Root/Child/GrandChild") if (s.Contains("/")) { - try { return GameObject.Find(s); } catch { } + try + { + var ids = GameObjectLookup.SearchGameObjects("by_path", s, includeInactive: true, maxResults: 1); + if (ids.Count > 0) + { + var byPath = GameObjectLookup.FindById(ids[0]); + if (byPath != null) return byPath; + } + } + catch { } } // Name-based find (first match, includes inactive)