Skip to content

Commit e617753

Browse files
cyanxwhclaude
andauthored
fix: Add Prefab Stage support for GameObject lookup (CoplayDev#573)
* Enhance Prefab Stage support in GameObject lookup and scene management Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: unify path matching and restore fast path lookup --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 2cdc386 commit e617753

3 files changed

Lines changed: 122 additions & 27 deletions

File tree

MCPForUnity/Editor/Helpers/GameObjectLookup.cs

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Linq;
44
using Newtonsoft.Json.Linq;
55
using UnityEditor;
6+
using UnityEditor.SceneManagement;
67
using UnityEngine;
78
using UnityEngine.SceneManagement;
89

@@ -147,19 +148,45 @@ private static IEnumerable<int> SearchByName(string name, bool includeInactive,
147148

148149
private static IEnumerable<int> SearchByPath(string path, bool includeInactive)
149150
{
151+
// Check Prefab Stage first - GameObject.Find() doesn't work in Prefab Stage
152+
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
153+
if (prefabStage != null)
154+
{
155+
// Use GetAllSceneObjects which already handles Prefab Stage
156+
var allObjects = GetAllSceneObjects(includeInactive);
157+
foreach (var go in allObjects)
158+
{
159+
if (MatchesPath(go, path))
160+
{
161+
yield return go.GetInstanceID();
162+
}
163+
}
164+
yield break;
165+
}
166+
167+
// Normal scene mode
150168
// NOTE: Unity's GameObject.Find(path) only finds ACTIVE GameObjects.
151-
// The includeInactive parameter has no effect here due to Unity API limitations.
152-
// Consider using by_name search with includeInactive if you need to find inactive objects.
169+
// If includeInactive=true, we need to search manually to find inactive objects.
153170
if (includeInactive)
154171
{
155-
McpLog.Warn("[GameObjectLookup] SearchByPath with includeInactive=true: " +
156-
"GameObject.Find() cannot find inactive objects. Use by_name search instead.");
172+
// Search manually to support inactive objects
173+
var allObjects = GetAllSceneObjects(true);
174+
foreach (var go in allObjects)
175+
{
176+
if (MatchesPath(go, path))
177+
{
178+
yield return go.GetInstanceID();
179+
}
180+
}
157181
}
158-
159-
var found = GameObject.Find(path);
160-
if (found != null)
182+
else
161183
{
162-
yield return found.GetInstanceID();
184+
// Use GameObject.Find for active objects only (Unity API limitation)
185+
var found = GameObject.Find(path);
186+
if (found != null)
187+
{
188+
yield return found.GetInstanceID();
189+
}
163190
}
164191
}
165192

@@ -249,6 +276,19 @@ private static IEnumerable<int> SearchByComponent(string componentTypeName, bool
249276
/// </summary>
250277
public static IEnumerable<GameObject> GetAllSceneObjects(bool includeInactive)
251278
{
279+
// Check Prefab Stage first
280+
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
281+
if (prefabStage != null && prefabStage.prefabContentsRoot != null)
282+
{
283+
// Use Prefab Stage's prefabContentsRoot
284+
foreach (var go in GetObjectAndDescendants(prefabStage.prefabContentsRoot, includeInactive))
285+
{
286+
yield return go;
287+
}
288+
yield break;
289+
}
290+
291+
// Normal scene mode
252292
var scene = SceneManager.GetActiveScene();
253293
if (!scene.IsValid())
254294
yield break;
@@ -290,6 +330,18 @@ public static Type FindComponentType(string typeName)
290330
return UnityTypeResolver.ResolveComponent(typeName);
291331
}
292332

333+
/// <summary>
334+
/// Checks whether a GameObject matches a path or trailing path segment.
335+
/// </summary>
336+
internal static bool MatchesPath(GameObject go, string path)
337+
{
338+
if (go == null || string.IsNullOrEmpty(path))
339+
return false;
340+
341+
var goPath = GetGameObjectPath(go);
342+
return goPath == path || goPath.EndsWith("/" + path);
343+
}
344+
293345
/// <summary>
294346
/// Gets the hierarchical path of a GameObject.
295347
/// </summary>

MCPForUnity/Editor/Tools/GameObjects/ManageGameObjectCommon.cs

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using MCPForUnity.Editor.Helpers;
66
using MCPForUnity.Editor.Tools;
77
using Newtonsoft.Json.Linq;
8+
using UnityEditor.SceneManagement;
89
using UnityEngine;
910
using UnityEngine.SceneManagement;
1011

@@ -83,11 +84,34 @@ internal static List<GameObject> FindObjectsInternal(
8384
break;
8485

8586
case "by_path":
86-
Transform foundTransform = rootSearchObject
87-
? rootSearchObject.transform.Find(searchTerm)
88-
: GameObject.Find(searchTerm)?.transform;
89-
if (foundTransform != null)
90-
results.Add(foundTransform.gameObject);
87+
if (rootSearchObject != null)
88+
{
89+
Transform foundTransform = rootSearchObject.transform.Find(searchTerm);
90+
if (foundTransform != null)
91+
results.Add(foundTransform.gameObject);
92+
}
93+
else
94+
{
95+
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
96+
if (prefabStage != null || searchInactive)
97+
{
98+
// In Prefab Stage, GameObject.Find() doesn't work, need to search manually
99+
var allObjects = GetAllSceneObjects(searchInactive);
100+
foreach (var go in allObjects)
101+
{
102+
if (GameObjectLookup.MatchesPath(go, searchTerm))
103+
{
104+
results.Add(go);
105+
}
106+
}
107+
}
108+
else
109+
{
110+
var found = GameObject.Find(searchTerm);
111+
if (found != null)
112+
results.Add(found);
113+
}
114+
}
91115
break;
92116

93117
case "by_tag":
@@ -154,7 +178,12 @@ internal static List<GameObject> FindObjectsInternal(
154178
}
155179
}
156180

157-
GameObject objByPath = GameObject.Find(searchTerm);
181+
// Try path search - in Prefab Stage, GameObject.Find() doesn't work
182+
var allObjectsForPath = GetAllSceneObjects(true);
183+
GameObject objByPath = allObjectsForPath.FirstOrDefault(go =>
184+
{
185+
return GameObjectLookup.MatchesPath(go, searchTerm);
186+
});
158187
if (objByPath != null)
159188
{
160189
results.Add(objByPath);
@@ -180,16 +209,8 @@ internal static List<GameObject> FindObjectsInternal(
180209

181210
private static IEnumerable<GameObject> GetAllSceneObjects(bool includeInactive)
182211
{
183-
var rootObjects = SceneManager.GetActiveScene().GetRootGameObjects();
184-
var allObjects = new List<GameObject>();
185-
foreach (var root in rootObjects)
186-
{
187-
allObjects.AddRange(
188-
root.GetComponentsInChildren<Transform>(includeInactive)
189-
.Select(t => t.gameObject)
190-
);
191-
}
192-
return allObjects;
212+
// Delegate to GameObjectLookup to avoid code duplication and ensure consistent behavior
213+
return GameObjectLookup.GetAllSceneObjects(includeInactive);
193214
}
194215

195216
private static Type FindType(string typeName)

MCPForUnity/Editor/Tools/ManageScene.cs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -490,8 +490,21 @@ private static object GetSceneHierarchyPaged(SceneCommand cmd)
490490
{
491491
try
492492
{
493-
try { McpLog.Info("[ManageScene] get_hierarchy: querying EditorSceneManager.GetActiveScene", always: false); } catch { }
494-
Scene activeScene = EditorSceneManager.GetActiveScene();
493+
// Check Prefab Stage first
494+
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
495+
Scene activeScene;
496+
497+
if (prefabStage != null)
498+
{
499+
activeScene = prefabStage.scene;
500+
try { McpLog.Info("[ManageScene] get_hierarchy: using Prefab Stage scene", always: false); } catch { }
501+
}
502+
else
503+
{
504+
try { McpLog.Info("[ManageScene] get_hierarchy: querying EditorSceneManager.GetActiveScene", always: false); } catch { }
505+
activeScene = EditorSceneManager.GetActiveScene();
506+
}
507+
495508
try { McpLog.Info($"[ManageScene] get_hierarchy: got scene valid={activeScene.IsValid()} loaded={activeScene.isLoaded} name='{activeScene.name}'", always: false); } catch { }
496509
if (!activeScene.IsValid() || !activeScene.isLoaded)
497510
{
@@ -599,7 +612,16 @@ private static GameObject ResolveGameObject(JToken targetToken, Scene activeScen
599612
// Path-based find (e.g., "Root/Child/GrandChild")
600613
if (s.Contains("/"))
601614
{
602-
try { return GameObject.Find(s); } catch { }
615+
try
616+
{
617+
var ids = GameObjectLookup.SearchGameObjects("by_path", s, includeInactive: true, maxResults: 1);
618+
if (ids.Count > 0)
619+
{
620+
var byPath = GameObjectLookup.FindById(ids[0]);
621+
if (byPath != null) return byPath;
622+
}
623+
}
624+
catch { }
603625
}
604626

605627
// Name-based find (first match, includes inactive)

0 commit comments

Comments
 (0)