315 lines
11 KiB
C#
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)
|
|
{
|
|
McpLog.Warn("[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)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <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;
|
|
}
|
|
}
|
|
}
|
|
|