unity-mcp/MCPForUnity/Editor/Helpers/GameObjectLookup.cs

315 lines
11 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace MCPForUnity.Editor.Helpers
{
/// <summary>
/// Utility class for finding and looking up GameObjects in the scene.
/// Provides search functionality by name, tag, layer, component, path, and instance ID.
/// </summary>
public static class GameObjectLookup
{
/// <summary>
/// Supported search methods for finding GameObjects.
/// </summary>
public enum SearchMethod
{
ByName,
ByTag,
ByLayer,
ByComponent,
ByPath,
ById
}
/// <summary>
/// Parses a search method string into the enum value.
/// </summary>
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
};
}
/// <summary>
/// Finds a single GameObject based on the target and search method.
/// </summary>
/// <param name="target">The target identifier (name, ID, path, etc.)</param>
/// <param name="searchMethod">The search method to use</param>
/// <param name="includeInactive">Whether to include inactive objects</param>
/// <returns>The found GameObject or null</returns>
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;
}
/// <summary>
/// Finds a GameObject by its instance ID.
/// </summary>
public static GameObject FindById(int instanceId)
{
return EditorUtility.InstanceIDToObject(instanceId) as GameObject;
}
/// <summary>
/// Searches for GameObjects and returns their instance IDs.
/// </summary>
/// <param name="searchMethod">The search method string (by_name, by_tag, etc.)</param>
/// <param name="searchTerm">The term to search for</param>
/// <param name="includeInactive">Whether to include inactive objects</param>
/// <param name="maxResults">Maximum number of results to return (0 = unlimited)</param>
/// <returns>List of instance IDs</returns>
public static List<int> SearchGameObjects(string searchMethod, string searchTerm, bool includeInactive = false, int maxResults = 0)
{
var method = ParseSearchMethod(searchMethod);
return SearchGameObjects(method, searchTerm, includeInactive, maxResults);
}
/// <summary>
/// Searches for GameObjects and returns their instance IDs.
/// </summary>
/// <param name="method">The search method</param>
/// <param name="searchTerm">The term to search for</param>
/// <param name="includeInactive">Whether to include inactive objects</param>
/// <param name="maxResults">Maximum number of results to return (0 = unlimited)</param>
/// <returns>List of instance IDs</returns>
public static List<int> SearchGameObjects(SearchMethod method, string searchTerm, bool includeInactive = false, int maxResults = 0)
{
var results = new List<int>();
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<int> 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<int> SearchByPath(string path, bool includeInactive)
{
// 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)
{
Debug.LogWarning("[GameObjectLookup] SearchByPath with includeInactive=true: " +
"GameObject.Find() cannot find inactive objects. Use by_name search instead.");
}
var found = GameObject.Find(path);
if (found != null)
{
yield return found.GetInstanceID();
}
}
private static IEnumerable<int> 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<int> 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<int> SearchByComponent(string componentTypeName, bool includeInactive, int maxResults)
{
Type componentType = FindComponentType(componentTypeName);
if (componentType == null)
{
Debug.LogWarning($"[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;
}
}
}
/// <summary>
/// Gets all GameObjects in the current scene.
/// </summary>
public static IEnumerable<GameObject> GetAllSceneObjects(bool includeInactive)
{
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<GameObject> 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;
}
}
}
/// <summary>
/// Finds a component type by name, searching loaded assemblies.
/// </summary>
/// <remarks>
/// Delegates to UnityTypeResolver.ResolveComponent() for unified type resolution.
/// </remarks>
public static Type FindComponentType(string typeName)
{
return UnityTypeResolver.ResolveComponent(typeName);
}
/// <summary>
/// Gets the hierarchical path of a GameObject.
/// </summary>
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;
}
}
}