using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.SceneManagement; namespace MCPForUnity.Editor.Helpers { /// /// Utility class for finding and looking up GameObjects in the scene. /// Provides search functionality by name, tag, layer, component, path, and instance ID. /// public static class GameObjectLookup { /// /// Supported search methods for finding GameObjects. /// public enum SearchMethod { ByName, ByTag, ByLayer, ByComponent, ByPath, ById } /// /// Parses a search method string into the enum value. /// public static SearchMethod ParseSearchMethod(string method) { if (string.IsNullOrEmpty(method)) return SearchMethod.ByName; return method.ToLowerInvariant() switch { "by_name" => SearchMethod.ByName, "by_tag" => SearchMethod.ByTag, "by_layer" => SearchMethod.ByLayer, "by_component" => SearchMethod.ByComponent, "by_path" => SearchMethod.ByPath, "by_id" => SearchMethod.ById, _ => SearchMethod.ByName }; } /// /// Finds a single GameObject based on the target and search method. /// /// The target identifier (name, ID, path, etc.) /// The search method to use /// Whether to include inactive objects /// The found GameObject or null public static GameObject FindByTarget(JToken target, string searchMethod, bool includeInactive = false) { if (target == null) return null; var results = SearchGameObjects(searchMethod, target.ToString(), includeInactive, 1); return results.Count > 0 ? FindById(results[0]) : null; } /// /// Finds a GameObject by its instance ID. /// public static GameObject FindById(int instanceId) { return EditorUtility.InstanceIDToObject(instanceId) as GameObject; } /// /// Searches for GameObjects and returns their instance IDs. /// /// The search method string (by_name, by_tag, etc.) /// The term to search for /// Whether to include inactive objects /// Maximum number of results to return (0 = unlimited) /// List of instance IDs public static List SearchGameObjects(string searchMethod, string searchTerm, bool includeInactive = false, int maxResults = 0) { var method = ParseSearchMethod(searchMethod); return SearchGameObjects(method, searchTerm, includeInactive, maxResults); } /// /// Searches for GameObjects and returns their instance IDs. /// /// The search method /// The term to search for /// Whether to include inactive objects /// Maximum number of results to return (0 = unlimited) /// List of instance IDs public static List SearchGameObjects(SearchMethod method, string searchTerm, bool includeInactive = false, int maxResults = 0) { var results = new List(); switch (method) { case SearchMethod.ById: if (int.TryParse(searchTerm, out int instanceId)) { var obj = EditorUtility.InstanceIDToObject(instanceId) as GameObject; if (obj != null && (includeInactive || obj.activeInHierarchy)) { results.Add(instanceId); } } break; case SearchMethod.ByName: results.AddRange(SearchByName(searchTerm, includeInactive, maxResults)); break; case SearchMethod.ByPath: results.AddRange(SearchByPath(searchTerm, includeInactive)); break; case SearchMethod.ByTag: results.AddRange(SearchByTag(searchTerm, includeInactive, maxResults)); break; case SearchMethod.ByLayer: results.AddRange(SearchByLayer(searchTerm, includeInactive, maxResults)); break; case SearchMethod.ByComponent: results.AddRange(SearchByComponent(searchTerm, includeInactive, maxResults)); break; } return results; } private static IEnumerable SearchByName(string name, bool includeInactive, int maxResults) { var allObjects = GetAllSceneObjects(includeInactive); var matching = allObjects.Where(go => go.name == name); if (maxResults > 0) matching = matching.Take(maxResults); return matching.Select(go => go.GetInstanceID()); } 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 (MatchesPath(go, path)) { yield return go.GetInstanceID(); } } yield break; } // Normal scene mode // NOTE: Unity's GameObject.Find(path) only finds ACTIVE GameObjects. // If includeInactive=true, we need to search manually to find inactive objects. if (includeInactive) { // Search manually to support inactive objects var allObjects = GetAllSceneObjects(true); foreach (var go in allObjects) { if (MatchesPath(go, path)) { yield return go.GetInstanceID(); } } } else { // Use GameObject.Find for active objects only (Unity API limitation) var found = GameObject.Find(path); if (found != null) { yield return found.GetInstanceID(); } } } private static IEnumerable SearchByTag(string tag, bool includeInactive, int maxResults) { GameObject[] taggedObjects; try { if (includeInactive) { // FindGameObjectsWithTag doesn't find inactive, so we need to iterate all var allObjects = GetAllSceneObjects(true); taggedObjects = allObjects.Where(go => go.CompareTag(tag)).ToArray(); } else { taggedObjects = GameObject.FindGameObjectsWithTag(tag); } } catch (UnityException) { // Tag doesn't exist yield break; } var results = taggedObjects.AsEnumerable(); if (maxResults > 0) results = results.Take(maxResults); foreach (var go in results) { yield return go.GetInstanceID(); } } private static IEnumerable SearchByLayer(string layerName, bool includeInactive, int maxResults) { int layer = LayerMask.NameToLayer(layerName); if (layer == -1) { // Try parsing as layer number if (!int.TryParse(layerName, out layer) || layer < 0 || layer > 31) { yield break; } } var allObjects = GetAllSceneObjects(includeInactive); var matching = allObjects.Where(go => go.layer == layer); if (maxResults > 0) matching = matching.Take(maxResults); foreach (var go in matching) { yield return go.GetInstanceID(); } } private static IEnumerable SearchByComponent(string componentTypeName, bool includeInactive, int maxResults) { Type componentType = FindComponentType(componentTypeName); if (componentType == null) { McpLog.Warn($"[GameObjectLookup] Component type '{componentTypeName}' not found."); yield break; } var allObjects = GetAllSceneObjects(includeInactive); var count = 0; foreach (var go in allObjects) { if (go.GetComponent(componentType) != null) { yield return go.GetInstanceID(); count++; if (maxResults > 0 && count >= maxResults) yield break; } } } /// /// Gets all GameObjects in the current scene. /// 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; var rootObjects = scene.GetRootGameObjects(); foreach (var root in rootObjects) { foreach (var go in GetObjectAndDescendants(root, includeInactive)) { yield return go; } } } private static IEnumerable GetObjectAndDescendants(GameObject obj, bool includeInactive) { if (!includeInactive && !obj.activeInHierarchy) yield break; yield return obj; foreach (Transform child in obj.transform) { foreach (var descendant in GetObjectAndDescendants(child.gameObject, includeInactive)) { yield return descendant; } } } /// /// Finds a component type by name, searching loaded assemblies. /// /// /// Delegates to UnityTypeResolver.ResolveComponent() for unified type resolution. /// 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. /// public static string GetGameObjectPath(GameObject obj) { if (obj == null) return string.Empty; var path = obj.name; var parent = obj.transform.parent; while (parent != null) { path = parent.name + "/" + path; parent = parent.parent; } return path; } } }