From ccb9ad98cd3f1b50d9a9d88ad988f399ef1cefa5 Mon Sep 17 00:00:00 2001 From: weberwang Date: Sun, 28 Sep 2025 15:59:39 +0800 Subject: [PATCH] fix:Fix the bug where the execution method fails to find the game object due to the missing `by_` prefix. --- .../Editor/Tools/ManageGameObject.cs | 425 +++++++++++------- 1 file changed, 251 insertions(+), 174 deletions(-) diff --git a/UnityMcpBridge/Editor/Tools/ManageGameObject.cs b/UnityMcpBridge/Editor/Tools/ManageGameObject.cs index c3357ed9..db5a2891 100644 --- a/UnityMcpBridge/Editor/Tools/ManageGameObject.cs +++ b/UnityMcpBridge/Editor/Tools/ManageGameObject.cs @@ -27,7 +27,7 @@ public static class ManageGameObject Converters = new List { new Vector3Converter(), - new Vector2Converter(), + new Vector2Converter(), new QuaternionConverter(), new ColorConverter(), new RectConverter(), @@ -35,7 +35,7 @@ public static class ManageGameObject new UnityEngineObjectConverter() } }); - + // --- Main Handler --- public static object HandleCommand(JObject @params) @@ -867,7 +867,7 @@ string searchMethod // return Response.Success( // $"GameObject '{targetGo.name}' modified successfully.", // GetGameObjectData(targetGo)); - + } private static object DeleteGameObject(JToken targetToken, string searchMethod) @@ -950,23 +950,23 @@ private static object GetComponentsFromTarget(string target, string searchMethod // --- Get components, immediately copy to list, and null original array --- Component[] originalComponents = targetGo.GetComponents(); List componentsToIterate = new List(originalComponents ?? Array.Empty()); // Copy immediately, handle null case - int componentCount = componentsToIterate.Count; + int componentCount = componentsToIterate.Count; originalComponents = null; // Null the original reference - // Debug.Log($"[GetComponentsFromTarget] Found {componentCount} components on {targetGo.name}. Copied to list, nulled original. Starting REVERSE for loop..."); - // --- End Copy and Null --- - + // Debug.Log($"[GetComponentsFromTarget] Found {componentCount} components on {targetGo.name}. Copied to list, nulled original. Starting REVERSE for loop..."); + // --- End Copy and Null --- + var componentData = new List(); - + for (int i = componentCount - 1; i >= 0; i--) // Iterate backwards over the COPY { Component c = componentsToIterate[i]; // Use the copy - if (c == null) + if (c == null) { // Debug.LogWarning($"[GetComponentsFromTarget REVERSE for] Encountered a null component at index {i} on {targetGo.name}. Skipping."); continue; // Safety check } // Debug.Log($"[GetComponentsFromTarget REVERSE for] Processing component: {c.GetType()?.FullName ?? "null"} (ID: {c.GetInstanceID()}) at index {i} on {targetGo.name}"); - try + try { var data = Helpers.GameObjectSerializer.GetComponentData(c, includeNonPublicSerialized); if (data != null) // Ensure GetComponentData didn't return null @@ -990,7 +990,7 @@ private static object GetComponentsFromTarget(string target, string searchMethod } } // Debug.Log($"[GetComponentsFromTarget] Finished REVERSE for loop."); - + // Cleanup the list we created componentsToIterate.Clear(); componentsToIterate = null; @@ -1105,7 +1105,7 @@ string searchMethod return removeResult; // Return error EditorUtility.SetDirty(targetGo); - // Use the new serializer helper + // Use the new serializer helper return Response.Success( $"Component '{typeName}' removed from '{targetGo.name}'.", Helpers.GameObjectSerializer.GetGameObjectData(targetGo) @@ -1154,7 +1154,7 @@ string searchMethod return setResult; // Return error EditorUtility.SetDirty(targetGo); - // Use the new serializer helper + // Use the new serializer helper return Response.Success( $"Properties set for component '{compName}' on '{targetGo.name}'.", Helpers.GameObjectSerializer.GetGameObjectData(targetGo) @@ -1233,11 +1233,18 @@ private static List FindObjectsInternal( if (string.IsNullOrEmpty(searchMethod)) { if (targetToken?.Type == JTokenType.Integer) - searchMethod = "by_id"; + searchMethod = "id"; // Use shorter format as default else if (!string.IsNullOrEmpty(searchTerm) && searchTerm.Contains('/')) - searchMethod = "by_path"; + searchMethod = "path"; // Use shorter format as default else - searchMethod = "by_name"; // Default fallback + searchMethod = "name"; // Default fallback, use shorter format + } + + // Normalize search method - add by_ prefix if not present for internal processing + string normalizedSearchMethod = searchMethod; + if (!string.IsNullOrEmpty(searchMethod) && !searchMethod.StartsWith("by_") && !searchMethod.Equals("by_id_or_name_or_path")) + { + normalizedSearchMethod = "by_" + searchMethod; } GameObject rootSearchObject = null; @@ -1254,7 +1261,7 @@ private static List FindObjectsInternal( } } - switch (searchMethod) + switch (normalizedSearchMethod) { case "by_id": if (int.TryParse(searchTerm, out int instanceId)) @@ -1275,13 +1282,39 @@ private static List FindObjectsInternal( .GetComponentsInChildren(searchInactive) .Select(t => t.gameObject) : GetAllSceneObjects(searchInactive); - results.AddRange(searchPoolName.Where(go => go.name == searchTerm)); + + // Debug logging for name search + var searchPool = searchPoolName.ToList(); + + var matchingObjects = searchPool.Where(go => go.name == searchTerm).ToList(); + + results.AddRange(matchingObjects); break; case "by_path": // Path is relative to scene root or rootSearchObject - Transform foundTransform = rootSearchObject - ? rootSearchObject.transform.Find(searchTerm) - : GameObject.Find(searchTerm)?.transform; + Transform foundTransform = null; + if (rootSearchObject) + { + foundTransform = rootSearchObject.transform.Find(searchTerm); + } + else + { + // Search in all loaded scenes, not just active scene + for (int i = 0; i < SceneManager.sceneCount && foundTransform == null; i++) + { + var scene = SceneManager.GetSceneAt(i); + if (scene.isLoaded) + { + var rootObjects = scene.GetRootGameObjects(); + foreach (var root in rootObjects) + { + foundTransform = root.transform.Find(searchTerm); + if (foundTransform != null) + break; + } + } + } + } if (foundTransform != null) results.Add(foundTransform.gameObject); break; @@ -1352,7 +1385,27 @@ private static List FindObjectsInternal( break; } } - GameObject objByPath = GameObject.Find(searchTerm); + + // Try to find by path in all loaded scenes + GameObject objByPath = null; + for (int i = 0; i < SceneManager.sceneCount && objByPath == null; i++) + { + var scene = SceneManager.GetSceneAt(i); + if (scene.isLoaded) + { + var rootObjects = scene.GetRootGameObjects(); + foreach (var root in rootObjects) + { + var transform = root.transform.Find(searchTerm); + if (transform != null) + { + objByPath = transform.gameObject; + break; + } + } + } + } + if (objByPath != null) { results.Add(objByPath); @@ -1374,23 +1427,35 @@ private static List FindObjectsInternal( { return new List { results[0] }; } - return results.Distinct().ToList(); // Ensure uniqueness } // Helper to get all scene objects efficiently private static IEnumerable GetAllSceneObjects(bool includeInactive) { - // SceneManager.GetActiveScene().GetRootGameObjects() is faster than FindObjectsOfType() - var rootObjects = SceneManager.GetActiveScene().GetRootGameObjects(); var allObjects = new List(); - foreach (var root in rootObjects) + + // Search in all loaded scenes, not just the active one + for (int i = 0; i < SceneManager.sceneCount; i++) { - allObjects.AddRange( - root.GetComponentsInChildren(includeInactive) - .Select(t => t.gameObject) - ); + var scene = SceneManager.GetSceneAt(i); + if (scene.isLoaded) + { + var rootObjects = scene.GetRootGameObjects(); + Debug.Log($"[ManageGameObject] Scanning scene '{scene.name}' - {rootObjects.Length} root objects"); + + foreach (var root in rootObjects) + { + var sceneObjects = root.GetComponentsInChildren(includeInactive) + .Select(t => t.gameObject) + .ToList(); + allObjects.AddRange(sceneObjects); + Debug.Log($"[ManageGameObject] Scene '{scene.name}' root '{root.name}' contains {sceneObjects.Count} objects (includeInactive: {includeInactive})"); + } + } } + + Debug.Log($"[ManageGameObject] Total objects found: {allObjects.Count}"); return allObjects; } @@ -1617,8 +1682,8 @@ private static bool SetProperty(object target, string memberName, JToken value) BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase; - // Use shared serializer to avoid per-call allocation - var inputSerializer = InputSerializer; + // Use shared serializer to avoid per-call allocation + var inputSerializer = InputSerializer; try { @@ -1640,8 +1705,9 @@ private static bool SetProperty(object target, string memberName, JToken value) propInfo.SetValue(target, convertedValue); return true; } - else { - Debug.LogWarning($"[SetProperty] Conversion failed for property '{memberName}' (Type: {propInfo.PropertyType.Name}) from token: {value.ToString(Formatting.None)}"); + else + { + Debug.LogWarning($"[SetProperty] Conversion failed for property '{memberName}' (Type: {propInfo.PropertyType.Name}) from token: {value.ToString(Formatting.None)}"); } } else @@ -1649,16 +1715,17 @@ private static bool SetProperty(object target, string memberName, JToken value) FieldInfo fieldInfo = type.GetField(memberName, flags); if (fieldInfo != null) // Check if !IsLiteral? { - // Use the inputSerializer for conversion + // Use the inputSerializer for conversion object convertedValue = ConvertJTokenToType(value, fieldInfo.FieldType, inputSerializer); - if (convertedValue != null || value.Type == JTokenType.Null) // Allow setting null + if (convertedValue != null || value.Type == JTokenType.Null) // Allow setting null { fieldInfo.SetValue(target, convertedValue); return true; } - else { - Debug.LogWarning($"[SetProperty] Conversion failed for field '{memberName}' (Type: {fieldInfo.FieldType.Name}) from token: {value.ToString(Formatting.None)}"); - } + else + { + Debug.LogWarning($"[SetProperty] Conversion failed for field '{memberName}' (Type: {fieldInfo.FieldType.Name}) from token: {value.ToString(Formatting.None)}"); + } } else { @@ -1805,12 +1872,17 @@ private static bool SetNestedProperty(object target, string path, JToken value, if (value is JArray jArray) { // Try converting to known types that SetColor/SetVector accept - if (jArray.Count == 4) { + if (jArray.Count == 4) + { try { Color color = value.ToObject(inputSerializer); material.SetColor(finalPart, color); return true; } catch { } try { Vector4 vec = value.ToObject(inputSerializer); material.SetVector(finalPart, vec); return true; } catch { } - } else if (jArray.Count == 3) { + } + else if (jArray.Count == 3) + { try { Color color = value.ToObject(inputSerializer); material.SetColor(finalPart, color); return true; } catch { } // ToObject handles conversion to Color - } else if (jArray.Count == 2) { + } + else if (jArray.Count == 2) + { try { Vector2 vec = value.ToObject(inputSerializer); material.SetVector(finalPart, vec); return true; } catch { } } } @@ -1825,13 +1897,16 @@ private static bool SetNestedProperty(object target, string path, JToken value, else if (value.Type == JTokenType.String) { // Try converting to Texture using the serializer/converter - try { + try + { Texture texture = value.ToObject(inputSerializer); - if (texture != null) { + if (texture != null) + { material.SetTexture(finalPart, texture); return true; } - } catch { } + } + catch { } } Debug.LogWarning( @@ -1851,7 +1926,8 @@ private static bool SetNestedProperty(object target, string path, JToken value, finalPropInfo.SetValue(currentObject, convertedValue); return true; } - else { + else + { Debug.LogWarning($"[SetNestedProperty] Final conversion failed for property '{finalPart}' (Type: {finalPropInfo.PropertyType.Name}) from token: {value.ToString(Formatting.None)}"); } } @@ -1867,7 +1943,8 @@ private static bool SetNestedProperty(object target, string path, JToken value, finalFieldInfo.SetValue(currentObject, convertedValue); return true; } - else { + else + { Debug.LogWarning($"[SetNestedProperty] Final conversion failed for field '{finalPart}' (Type: {finalFieldInfo.FieldType.Name}) from token: {value.ToString(Formatting.None)}"); } } @@ -1949,25 +2026,25 @@ private static object ConvertJTokenToType(JToken token, Type targetType, JsonSer } catch (JsonSerializationException jsonEx) { - Debug.LogError($"JSON Deserialization Error converting token to {targetType.FullName}: {jsonEx.Message}\nToken: {token.ToString(Formatting.None)}"); - // Optionally re-throw or return null/default - // return targetType.IsValueType ? Activator.CreateInstance(targetType) : null; - throw; // Re-throw to indicate failure higher up + Debug.LogError($"JSON Deserialization Error converting token to {targetType.FullName}: {jsonEx.Message}\nToken: {token.ToString(Formatting.None)}"); + // Optionally re-throw or return null/default + // return targetType.IsValueType ? Activator.CreateInstance(targetType) : null; + throw; // Re-throw to indicate failure higher up } catch (ArgumentException argEx) { Debug.LogError($"Argument Error converting token to {targetType.FullName}: {argEx.Message}\nToken: {token.ToString(Formatting.None)}"); - throw; + throw; } catch (Exception ex) { - Debug.LogError($"Unexpected error converting token to {targetType.FullName}: {ex}\nToken: {token.ToString(Formatting.None)}"); - throw; + Debug.LogError($"Unexpected error converting token to {targetType.FullName}: {ex}\nToken: {token.ToString(Formatting.None)}"); + throw; } // If ToObject succeeded, it would have returned. If it threw, we wouldn't reach here. - // This fallback logic is likely unreachable if ToObject covers all cases or throws on failure. - // Debug.LogWarning($"Conversion failed for token to {targetType.FullName}. Token: {token.ToString(Formatting.None)}"); - // return targetType.IsValueType ? Activator.CreateInstance(targetType) : null; + // This fallback logic is likely unreachable if ToObject covers all cases or throws on failure. + // Debug.LogWarning($"Conversion failed for token to {targetType.FullName}. Token: {token.ToString(Formatting.None)}"); + // return targetType.IsValueType ? Activator.CreateInstance(targetType) : null; } // --- ParseJTokenTo... helpers are likely redundant now with the serializer approach --- @@ -1983,7 +2060,7 @@ private static Vector3 ParseJTokenToVector3(JToken token) } if (token is JArray arr && arr.Count >= 3) { - return new Vector3(arr[0].ToObject(), arr[1].ToObject(), arr[2].ToObject()); + return new Vector3(arr[0].ToObject(), arr[1].ToObject(), arr[2].ToObject()); } Debug.LogWarning($"Could not parse JToken '{token}' as Vector3 using fallback. Returning Vector3.zero."); return Vector3.zero; @@ -1992,13 +2069,13 @@ private static Vector3 ParseJTokenToVector3(JToken token) private static Vector2 ParseJTokenToVector2(JToken token) { // ... (implementation - likely replaced by Vector2Converter) ... - if (token is JObject obj && obj.ContainsKey("x") && obj.ContainsKey("y")) + if (token is JObject obj && obj.ContainsKey("x") && obj.ContainsKey("y")) { return new Vector2(obj["x"].ToObject(), obj["y"].ToObject()); } if (token is JArray arr && arr.Count >= 2) { - return new Vector2(arr[0].ToObject(), arr[1].ToObject()); + return new Vector2(arr[0].ToObject(), arr[1].ToObject()); } Debug.LogWarning($"Could not parse JToken '{token}' as Vector2 using fallback. Returning Vector2.zero."); return Vector2.zero; @@ -2012,47 +2089,47 @@ private static Quaternion ParseJTokenToQuaternion(JToken token) } if (token is JArray arr && arr.Count >= 4) { - return new Quaternion(arr[0].ToObject(), arr[1].ToObject(), arr[2].ToObject(), arr[3].ToObject()); + return new Quaternion(arr[0].ToObject(), arr[1].ToObject(), arr[2].ToObject(), arr[3].ToObject()); } Debug.LogWarning($"Could not parse JToken '{token}' as Quaternion using fallback. Returning Quaternion.identity."); return Quaternion.identity; } private static Color ParseJTokenToColor(JToken token) { - // ... (implementation - likely replaced by ColorConverter) ... + // ... (implementation - likely replaced by ColorConverter) ... if (token is JObject obj && obj.ContainsKey("r") && obj.ContainsKey("g") && obj.ContainsKey("b") && obj.ContainsKey("a")) { return new Color(obj["r"].ToObject(), obj["g"].ToObject(), obj["b"].ToObject(), obj["a"].ToObject()); } if (token is JArray arr && arr.Count >= 4) { - return new Color(arr[0].ToObject(), arr[1].ToObject(), arr[2].ToObject(), arr[3].ToObject()); + return new Color(arr[0].ToObject(), arr[1].ToObject(), arr[2].ToObject(), arr[3].ToObject()); } Debug.LogWarning($"Could not parse JToken '{token}' as Color using fallback. Returning Color.white."); return Color.white; } private static Rect ParseJTokenToRect(JToken token) { - // ... (implementation - likely replaced by RectConverter) ... + // ... (implementation - likely replaced by RectConverter) ... if (token is JObject obj && obj.ContainsKey("x") && obj.ContainsKey("y") && obj.ContainsKey("width") && obj.ContainsKey("height")) { return new Rect(obj["x"].ToObject(), obj["y"].ToObject(), obj["width"].ToObject(), obj["height"].ToObject()); } if (token is JArray arr && arr.Count >= 4) { - return new Rect(arr[0].ToObject(), arr[1].ToObject(), arr[2].ToObject(), arr[3].ToObject()); + return new Rect(arr[0].ToObject(), arr[1].ToObject(), arr[2].ToObject(), arr[3].ToObject()); } Debug.LogWarning($"Could not parse JToken '{token}' as Rect using fallback. Returning Rect.zero."); return Rect.zero; } private static Bounds ParseJTokenToBounds(JToken token) { - // ... (implementation - likely replaced by BoundsConverter) ... + // ... (implementation - likely replaced by BoundsConverter) ... if (token is JObject obj && obj.ContainsKey("center") && obj.ContainsKey("size")) { // Requires Vector3 conversion, which should ideally use the serializer too - Vector3 center = ParseJTokenToVector3(obj["center"]); // Or use obj["center"].ToObject(inputSerializer) - Vector3 size = ParseJTokenToVector3(obj["size"]); // Or use obj["size"].ToObject(inputSerializer) + Vector3 center = ParseJTokenToVector3(obj["center"]); // Or use obj["center"].ToObject(inputSerializer) + Vector3 size = ParseJTokenToVector3(obj["size"]); // Or use obj["size"].ToObject(inputSerializer) return new Bounds(center, size); } // Array fallback for Bounds is less intuitive, maybe remove? @@ -2065,109 +2142,109 @@ private static Bounds ParseJTokenToBounds(JToken token) } // --- End Redundant Parse Helpers --- - /// - /// Finds a specific UnityEngine.Object based on a find instruction JObject. - /// Primarily used by UnityEngineObjectConverter during deserialization. - /// - // Made public static so UnityEngineObjectConverter can call it. Moved from ConvertJTokenToType. - public static UnityEngine.Object FindObjectByInstruction(JObject instruction, Type targetType) - { - string findTerm = instruction["find"]?.ToString(); - string method = instruction["method"]?.ToString()?.ToLower(); - string componentName = instruction["component"]?.ToString(); // Specific component to get - - if (string.IsNullOrEmpty(findTerm)) - { - Debug.LogWarning("Find instruction missing 'find' term."); - return null; - } - - // Use a flexible default search method if none provided - string searchMethodToUse = string.IsNullOrEmpty(method) ? "by_id_or_name_or_path" : method; - - // If the target is an asset (Material, Texture, ScriptableObject etc.) try AssetDatabase first - if (typeof(Material).IsAssignableFrom(targetType) || - typeof(Texture).IsAssignableFrom(targetType) || - typeof(ScriptableObject).IsAssignableFrom(targetType) || - targetType.FullName.StartsWith("UnityEngine.U2D") || // Sprites etc. - typeof(AudioClip).IsAssignableFrom(targetType) || - typeof(AnimationClip).IsAssignableFrom(targetType) || - typeof(Font).IsAssignableFrom(targetType) || - typeof(Shader).IsAssignableFrom(targetType) || - typeof(ComputeShader).IsAssignableFrom(targetType) || - typeof(GameObject).IsAssignableFrom(targetType) && findTerm.StartsWith("Assets/")) // Prefab check - { + /// + /// Finds a specific UnityEngine.Object based on a find instruction JObject. + /// Primarily used by UnityEngineObjectConverter during deserialization. + /// + // Made public static so UnityEngineObjectConverter can call it. Moved from ConvertJTokenToType. + public static UnityEngine.Object FindObjectByInstruction(JObject instruction, Type targetType) + { + string findTerm = instruction["find"]?.ToString(); + string method = instruction["method"]?.ToString()?.ToLower(); + string componentName = instruction["component"]?.ToString(); // Specific component to get + + if (string.IsNullOrEmpty(findTerm)) + { + Debug.LogWarning("Find instruction missing 'find' term."); + return null; + } + + // Use a flexible default search method if none provided + string searchMethodToUse = string.IsNullOrEmpty(method) ? "by_id_or_name_or_path" : method; + + // If the target is an asset (Material, Texture, ScriptableObject etc.) try AssetDatabase first + if (typeof(Material).IsAssignableFrom(targetType) || + typeof(Texture).IsAssignableFrom(targetType) || + typeof(ScriptableObject).IsAssignableFrom(targetType) || + targetType.FullName.StartsWith("UnityEngine.U2D") || // Sprites etc. + typeof(AudioClip).IsAssignableFrom(targetType) || + typeof(AnimationClip).IsAssignableFrom(targetType) || + typeof(Font).IsAssignableFrom(targetType) || + typeof(Shader).IsAssignableFrom(targetType) || + typeof(ComputeShader).IsAssignableFrom(targetType) || + typeof(GameObject).IsAssignableFrom(targetType) && findTerm.StartsWith("Assets/")) // Prefab check + { // Try loading directly by path/GUID first UnityEngine.Object asset = AssetDatabase.LoadAssetAtPath(findTerm, targetType); - if (asset != null) return asset; - asset = AssetDatabase.LoadAssetAtPath(findTerm); // Try generic if type specific failed - if (asset != null && targetType.IsAssignableFrom(asset.GetType())) return asset; - - - // If direct path failed, try finding by name/type using FindAssets - string searchFilter = $"t:{targetType.Name} {System.IO.Path.GetFileNameWithoutExtension(findTerm)}"; // Search by type and name - string[] guids = AssetDatabase.FindAssets(searchFilter); - - if (guids.Length == 1) - { - asset = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guids[0]), targetType); - if (asset != null) return asset; - } - else if (guids.Length > 1) - { - Debug.LogWarning($"[FindObjectByInstruction] Ambiguous asset find: Found {guids.Length} assets matching filter '{searchFilter}'. Provide a full path or unique name."); - // Optionally return the first one? Or null? Returning null is safer. - return null; - } - // If still not found, fall through to scene search (though unlikely for assets) - } - - - // --- Scene Object Search --- - // Find the GameObject using the internal finder - GameObject foundGo = FindObjectInternal(new JValue(findTerm), searchMethodToUse); - - if (foundGo == null) - { - // Don't warn yet, could still be an asset not found above - // Debug.LogWarning($"Could not find GameObject using instruction: {instruction}"); - return null; - } - - // Now, get the target object/component from the found GameObject - if (targetType == typeof(GameObject)) - { - return foundGo; // We were looking for a GameObject - } - else if (typeof(Component).IsAssignableFrom(targetType)) - { - Type componentToGetType = targetType; - if (!string.IsNullOrEmpty(componentName)) - { - Type specificCompType = FindType(componentName); - if (specificCompType != null && typeof(Component).IsAssignableFrom(specificCompType)) - { - componentToGetType = specificCompType; - } - else - { - Debug.LogWarning($"Could not find component type '{componentName}' specified in find instruction. Falling back to target type '{targetType.Name}'."); - } - } - - Component foundComp = foundGo.GetComponent(componentToGetType); - if (foundComp == null) - { - Debug.LogWarning($"Found GameObject '{foundGo.name}' but could not find component of type '{componentToGetType.Name}'."); - } - return foundComp; - } - else - { - Debug.LogWarning($"Find instruction handling not implemented for target type: {targetType.Name}"); - return null; - } - } + if (asset != null) return asset; + asset = AssetDatabase.LoadAssetAtPath(findTerm); // Try generic if type specific failed + if (asset != null && targetType.IsAssignableFrom(asset.GetType())) return asset; + + + // If direct path failed, try finding by name/type using FindAssets + string searchFilter = $"t:{targetType.Name} {System.IO.Path.GetFileNameWithoutExtension(findTerm)}"; // Search by type and name + string[] guids = AssetDatabase.FindAssets(searchFilter); + + if (guids.Length == 1) + { + asset = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guids[0]), targetType); + if (asset != null) return asset; + } + else if (guids.Length > 1) + { + Debug.LogWarning($"[FindObjectByInstruction] Ambiguous asset find: Found {guids.Length} assets matching filter '{searchFilter}'. Provide a full path or unique name."); + // Optionally return the first one? Or null? Returning null is safer. + return null; + } + // If still not found, fall through to scene search (though unlikely for assets) + } + + + // --- Scene Object Search --- + // Find the GameObject using the internal finder + GameObject foundGo = FindObjectInternal(new JValue(findTerm), searchMethodToUse); + + if (foundGo == null) + { + // Don't warn yet, could still be an asset not found above + // Debug.LogWarning($"Could not find GameObject using instruction: {instruction}"); + return null; + } + + // Now, get the target object/component from the found GameObject + if (targetType == typeof(GameObject)) + { + return foundGo; // We were looking for a GameObject + } + else if (typeof(Component).IsAssignableFrom(targetType)) + { + Type componentToGetType = targetType; + if (!string.IsNullOrEmpty(componentName)) + { + Type specificCompType = FindType(componentName); + if (specificCompType != null && typeof(Component).IsAssignableFrom(specificCompType)) + { + componentToGetType = specificCompType; + } + else + { + Debug.LogWarning($"Could not find component type '{componentName}' specified in find instruction. Falling back to target type '{targetType.Name}'."); + } + } + + Component foundComp = foundGo.GetComponent(componentToGetType); + if (foundComp == null) + { + Debug.LogWarning($"Found GameObject '{foundGo.name}' but could not find component of type '{componentToGetType.Name}'."); + } + return foundComp; + } + else + { + Debug.LogWarning($"Find instruction handling not implemented for target type: {targetType.Name}"); + return null; + } + } /// @@ -2180,17 +2257,17 @@ private static Type FindType(string typeName) { return resolvedType; } - + // Log the resolver error if type wasn't found if (!string.IsNullOrEmpty(error)) { Debug.LogWarning($"[FindType] {error}"); } - + return null; } } - + /// /// Robust component resolver that avoids Assembly.LoadFrom and supports assembly definitions. /// Prioritizes runtime (Player) assemblies over Editor assemblies. @@ -2369,7 +2446,7 @@ public static List GetAIPropertySuggestions(string userInput, List GetRuleBasedSuggestions(string userInput, List