diff --git a/Editor/Tools/UpdateComponentTool.cs b/Editor/Tools/UpdateComponentTool.cs
index 390dce0..e86e29c 100644
--- a/Editor/Tools/UpdateComponentTool.cs
+++ b/Editor/Tools/UpdateComponentTool.cs
@@ -238,13 +238,54 @@ private Type FindComponentType(string componentName)
return null;
}
+
+ ///
+ /// Recursively search for a field through the type hierarchy
+ ///
+ /// The type to start searching from
+ /// The name of the field to find
+ /// The FieldInfo if found, null otherwise
+ private FieldInfo GetFieldRecursive(Type type, string fieldName)
+ {
+ while (type != null && type != typeof(object))
+ {
+ FieldInfo field = type.GetField(fieldName,
+ BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
+ if (field != null)
+ return field;
+ type = type.BaseType;
+ }
+ return null;
+ }
+
+ ///
+ /// Recursively search for a property through the type hierarchy
+ ///
+ /// The type to start searching from
+ /// The name of the property to find
+ /// The PropertyInfo if found, null otherwise
+ private PropertyInfo GetPropertyRecursive(Type type, string propertyName)
+ {
+ while (type != null && type != typeof(object))
+ {
+ PropertyInfo prop = type.GetProperty(propertyName,
+ BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
+ if (prop != null)
+ return prop;
+ type = type.BaseType;
+ }
+ return null;
+ }
///
/// Update component data based on the provided JObject
+ /// Uses SerializedObject API as primary method (handles base class fields and nested paths),
+ /// with reflection as fallback for non-serialized properties.
///
/// The component to update
/// The data to apply to the component
- /// True if the component was updated successfully
+ /// Error message if any fields failed to update
+ /// True if all fields were updated successfully
private bool UpdateComponentData(Component component, JObject componentData, out string errorMessage)
{
errorMessage = "";
@@ -261,21 +302,36 @@ private bool UpdateComponentData(Component component, JObject componentData, out
// Record object for undo
Undo.RecordObject(component, $"Update {componentType.Name} fields");
+ // Create SerializedObject for the primary approach
+ SerializedObject serializedObject = new SerializedObject(component);
+
// Process each field or property in the component data
foreach (var property in componentData.Properties())
{
string fieldName = property.Name;
JToken fieldValue = property.Value;
- // Skip null values
- if (string.IsNullOrEmpty(fieldName) || fieldValue.Type == JTokenType.Null)
+ // Skip null field names
+ if (string.IsNullOrEmpty(fieldName))
+ {
+ continue;
+ }
+
+ // Primary approach: Try SerializedObject API first
+ // This handles base class fields and nested paths automatically
+ SerializedProperty serializedProperty = serializedObject.FindProperty(fieldName);
+
+ if (serializedProperty != null)
{
+ SetSerializedPropertyValue(serializedProperty, fieldValue);
continue;
}
- // Try to update field
- FieldInfo fieldInfo = componentType.GetField(fieldName,
- BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
+ // Fallback: Use reflection with recursive base class search
+ // This handles non-serialized properties that SerializedObject can't access
+
+ // Try to find field (including in base classes)
+ FieldInfo fieldInfo = GetFieldRecursive(componentType, fieldName);
if (fieldInfo != null)
{
@@ -284,11 +340,10 @@ private bool UpdateComponentData(Component component, JObject componentData, out
continue;
}
- // Try to update property if not found as a field
- PropertyInfo propertyInfo = componentType.GetProperty(fieldName,
- BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
+ // Try to find property (including in base classes)
+ PropertyInfo propertyInfo = GetPropertyRecursive(componentType, fieldName);
- if (propertyInfo != null)
+ if (propertyInfo != null && propertyInfo.CanWrite)
{
object value = ConvertJTokenToValue(fieldValue, propertyInfo.PropertyType);
propertyInfo.SetValue(component, value);
@@ -296,12 +351,352 @@ private bool UpdateComponentData(Component component, JObject componentData, out
}
fullSuccess = false;
- errorMessage = $"Field or Property with name '{fieldName}' not found on component '{componentType.Name}'";
+ errorMessage = $"Field or Property with name '{fieldName}' not found on component '{componentType.Name}'";
+ McpLogger.LogWarning($"[MCP Unity] {errorMessage}");
}
+
+ // Apply all SerializedObject changes
+ serializedObject.ApplyModifiedProperties();
return fullSuccess;
}
+ ///
+ /// Update component data using Unity's SerializedObject API
+ /// This properly handles all serialized fields including private ones in base classes
+ ///
+ /// The component to update
+ /// The data to apply to the component
+ /// Error message if any fields failed to update
+ /// True if all fields were updated successfully
+ private bool UpdateComponentDataSerialized(Component component, JObject componentData, out string errorMessage)
+ {
+ errorMessage = "";
+ SerializedObject serializedObject = new SerializedObject(component);
+ bool allFieldsFound = true;
+
+ foreach (var property in componentData.Properties())
+ {
+ string fieldPath = property.Name;
+ JToken fieldValue = property.Value;
+
+ // Skip null values
+ if (string.IsNullOrEmpty(fieldPath) || fieldValue.Type == JTokenType.Null)
+ {
+ continue;
+ }
+
+ // SerializedObject.FindProperty uses Unity's serialization path
+ SerializedProperty serializedProperty = serializedObject.FindProperty(fieldPath);
+
+ if (serializedProperty == null)
+ {
+ McpLogger.LogWarning($"[MCP Unity] SerializedProperty '{fieldPath}' not found on {component.GetType().Name}");
+ errorMessage = $"Property '{fieldPath}' not found";
+ allFieldsFound = false;
+ continue;
+ }
+
+ SetSerializedPropertyValue(serializedProperty, fieldValue);
+ }
+
+ serializedObject.ApplyModifiedProperties();
+ return allFieldsFound;
+ }
+
+ ///
+ /// Set a SerializedProperty value from a JToken
+ ///
+ /// The SerializedProperty to set
+ /// The JToken value to apply
+ private void SetSerializedPropertyValue(SerializedProperty prop, JToken value)
+ {
+ switch (prop.propertyType)
+ {
+ case SerializedPropertyType.Integer:
+ prop.intValue = value.ToObject();
+ break;
+
+ case SerializedPropertyType.Boolean:
+ prop.boolValue = value.ToObject();
+ break;
+
+ case SerializedPropertyType.Float:
+ prop.floatValue = value.ToObject();
+ break;
+
+ case SerializedPropertyType.String:
+ prop.stringValue = value.ToObject();
+ break;
+
+ case SerializedPropertyType.Color:
+ if (value.Type == JTokenType.Object)
+ {
+ JObject color = (JObject)value;
+ prop.colorValue = new Color(
+ color["r"]?.ToObject() ?? 0f,
+ color["g"]?.ToObject() ?? 0f,
+ color["b"]?.ToObject() ?? 0f,
+ color["a"]?.ToObject() ?? 1f
+ );
+ }
+ break;
+
+ case SerializedPropertyType.Vector2:
+ if (value.Type == JTokenType.Object)
+ {
+ JObject v2 = (JObject)value;
+ prop.vector2Value = new Vector2(
+ v2["x"]?.ToObject() ?? 0f,
+ v2["y"]?.ToObject() ?? 0f
+ );
+ }
+ break;
+
+ case SerializedPropertyType.Vector3:
+ if (value.Type == JTokenType.Object)
+ {
+ JObject v3 = (JObject)value;
+ prop.vector3Value = new Vector3(
+ v3["x"]?.ToObject() ?? 0f,
+ v3["y"]?.ToObject() ?? 0f,
+ v3["z"]?.ToObject() ?? 0f
+ );
+ }
+ break;
+
+ case SerializedPropertyType.Vector4:
+ if (value.Type == JTokenType.Object)
+ {
+ JObject v4 = (JObject)value;
+ prop.vector4Value = new Vector4(
+ v4["x"]?.ToObject() ?? 0f,
+ v4["y"]?.ToObject() ?? 0f,
+ v4["z"]?.ToObject() ?? 0f,
+ v4["w"]?.ToObject() ?? 0f
+ );
+ }
+ break;
+
+ case SerializedPropertyType.Quaternion:
+ if (value.Type == JTokenType.Object)
+ {
+ JObject q = (JObject)value;
+ prop.quaternionValue = new Quaternion(
+ q["x"]?.ToObject() ?? 0f,
+ q["y"]?.ToObject() ?? 0f,
+ q["z"]?.ToObject() ?? 0f,
+ q["w"]?.ToObject() ?? 1f
+ );
+ }
+ break;
+
+ case SerializedPropertyType.Rect:
+ if (value.Type == JTokenType.Object)
+ {
+ JObject rect = (JObject)value;
+ prop.rectValue = new Rect(
+ rect["x"]?.ToObject() ?? 0f,
+ rect["y"]?.ToObject() ?? 0f,
+ rect["width"]?.ToObject() ?? 0f,
+ rect["height"]?.ToObject() ?? 0f
+ );
+ }
+ break;
+
+ case SerializedPropertyType.Bounds:
+ if (value.Type == JTokenType.Object)
+ {
+ JObject bounds = (JObject)value;
+ JObject center = bounds["center"] as JObject;
+ JObject size = bounds["size"] as JObject;
+ prop.boundsValue = new Bounds(
+ center != null ? new Vector3(
+ center["x"]?.ToObject() ?? 0f,
+ center["y"]?.ToObject() ?? 0f,
+ center["z"]?.ToObject() ?? 0f
+ ) : Vector3.zero,
+ size != null ? new Vector3(
+ size["x"]?.ToObject() ?? 0f,
+ size["y"]?.ToObject() ?? 0f,
+ size["z"]?.ToObject() ?? 0f
+ ) : Vector3.one
+ );
+ }
+ break;
+
+ case SerializedPropertyType.Enum:
+ if (value.Type == JTokenType.String)
+ {
+ // Find enum value by name
+ string[] enumNames = prop.enumNames;
+ string enumValue = value.ToObject();
+ int index = Array.IndexOf(enumNames, enumValue);
+ if (index >= 0)
+ {
+ prop.enumValueIndex = index;
+ }
+ else
+ {
+ // Try case-insensitive match
+ for (int i = 0; i < enumNames.Length; i++)
+ {
+ if (string.Equals(enumNames[i], enumValue, StringComparison.OrdinalIgnoreCase))
+ {
+ prop.enumValueIndex = i;
+ break;
+ }
+ }
+ }
+ }
+ else if (value.Type == JTokenType.Integer)
+ {
+ prop.enumValueIndex = value.ToObject();
+ }
+ break;
+
+ case SerializedPropertyType.ObjectReference:
+ // Handle asset references
+ if (value.Type == JTokenType.String)
+ {
+ string assetPath = value.ToObject();
+ if (!string.IsNullOrEmpty(assetPath))
+ {
+ UnityEngine.Object asset = null;
+
+ // Try to load as asset path
+ if (assetPath.StartsWith("Assets/") || assetPath.StartsWith("Packages/"))
+ {
+ asset = AssetDatabase.LoadAssetAtPath(assetPath);
+ }
+
+ // If not found, try to find by name
+ if (asset == null)
+ {
+ string[] guids = AssetDatabase.FindAssets(assetPath);
+ if (guids.Length > 0)
+ {
+ string path = AssetDatabase.GUIDToAssetPath(guids[0]);
+ asset = AssetDatabase.LoadAssetAtPath(path);
+ }
+ }
+
+ prop.objectReferenceValue = asset;
+ }
+ else
+ {
+ prop.objectReferenceValue = null;
+ }
+ }
+ else if (value.Type == JTokenType.Object)
+ {
+ JObject obj = (JObject)value;
+ string guid = obj["guid"]?.ToObject();
+ string path = obj["path"]?.ToObject();
+
+ if (!string.IsNullOrEmpty(guid))
+ {
+ path = AssetDatabase.GUIDToAssetPath(guid);
+ }
+
+ if (!string.IsNullOrEmpty(path))
+ {
+ prop.objectReferenceValue = AssetDatabase.LoadAssetAtPath(path);
+ }
+ }
+ else if (value.Type == JTokenType.Null)
+ {
+ prop.objectReferenceValue = null;
+ }
+ break;
+
+ case SerializedPropertyType.LayerMask:
+ prop.intValue = value.ToObject();
+ break;
+
+ case SerializedPropertyType.Vector2Int:
+ if (value.Type == JTokenType.Object)
+ {
+ JObject v2i = (JObject)value;
+ prop.vector2IntValue = new Vector2Int(
+ v2i["x"]?.ToObject() ?? 0,
+ v2i["y"]?.ToObject() ?? 0
+ );
+ }
+ break;
+
+ case SerializedPropertyType.Vector3Int:
+ if (value.Type == JTokenType.Object)
+ {
+ JObject v3i = (JObject)value;
+ prop.vector3IntValue = new Vector3Int(
+ v3i["x"]?.ToObject() ?? 0,
+ v3i["y"]?.ToObject() ?? 0,
+ v3i["z"]?.ToObject() ?? 0
+ );
+ }
+ break;
+
+ case SerializedPropertyType.RectInt:
+ if (value.Type == JTokenType.Object)
+ {
+ JObject rectInt = (JObject)value;
+ prop.rectIntValue = new RectInt(
+ rectInt["x"]?.ToObject() ?? 0,
+ rectInt["y"]?.ToObject() ?? 0,
+ rectInt["width"]?.ToObject() ?? 0,
+ rectInt["height"]?.ToObject() ?? 0
+ );
+ }
+ break;
+
+ case SerializedPropertyType.BoundsInt:
+ if (value.Type == JTokenType.Object)
+ {
+ JObject boundsInt = (JObject)value;
+ JObject position = boundsInt["position"] as JObject;
+ JObject size = boundsInt["size"] as JObject;
+ prop.boundsIntValue = new BoundsInt(
+ position != null ? new Vector3Int(
+ position["x"]?.ToObject() ?? 0,
+ position["y"]?.ToObject() ?? 0,
+ position["z"]?.ToObject() ?? 0
+ ) : Vector3Int.zero,
+ size != null ? new Vector3Int(
+ size["x"]?.ToObject() ?? 0,
+ size["y"]?.ToObject() ?? 0,
+ size["z"]?.ToObject() ?? 0
+ ) : Vector3Int.one
+ );
+ }
+ break;
+
+ case SerializedPropertyType.Generic:
+ // For complex types, try to iterate children
+ if (value.Type == JTokenType.Object)
+ {
+ JObject obj = (JObject)value;
+ foreach (var child in obj.Properties())
+ {
+ SerializedProperty childProp = prop.FindPropertyRelative(child.Name);
+ if (childProp != null)
+ {
+ SetSerializedPropertyValue(childProp, child.Value);
+ }
+ }
+ }
+ break;
+
+ case SerializedPropertyType.ArraySize:
+ prop.intValue = value.ToObject();
+ break;
+
+ default:
+ McpLogger.LogWarning($"[MCP Unity] Unsupported property type: {prop.propertyType} for {prop.name}");
+ break;
+ }
+ }
+
///
/// Convert a JToken to a value of the specified type
///
@@ -387,10 +782,77 @@ private object ConvertJTokenToValue(JToken token, Type targetType)
);
}
- // Handle UnityEngine.Object types;
- if (targetType == typeof(UnityEngine.Object))
+ // Handle UnityEngine.Object types (assets) by path or GUID
+ if (typeof(UnityEngine.Object).IsAssignableFrom(targetType))
{
- return token.ToObject();
+ if (token.Type == JTokenType.String)
+ {
+ string assetPath = token.ToObject();
+
+ if (string.IsNullOrEmpty(assetPath))
+ {
+ return null;
+ }
+
+ // Try to load as asset path
+ if (assetPath.StartsWith("Assets/") || assetPath.StartsWith("Packages/"))
+ {
+ UnityEngine.Object asset = AssetDatabase.LoadAssetAtPath(assetPath, targetType);
+ if (asset != null)
+ {
+ return asset;
+ }
+ McpLogger.LogWarning($"[MCP Unity] Could not load asset at path: {assetPath}");
+ }
+
+ // Try to find by name in project
+ string[] guids = AssetDatabase.FindAssets($"{assetPath} t:{targetType.Name}");
+ if (guids.Length > 0)
+ {
+ string path = AssetDatabase.GUIDToAssetPath(guids[0]);
+ return AssetDatabase.LoadAssetAtPath(path, targetType);
+ }
+
+ // Final fallback - search without type filter
+ guids = AssetDatabase.FindAssets(assetPath);
+ if (guids.Length > 0)
+ {
+ string path = AssetDatabase.GUIDToAssetPath(guids[0]);
+ UnityEngine.Object asset = AssetDatabase.LoadAssetAtPath(path, targetType);
+ if (asset != null)
+ {
+ return asset;
+ }
+ }
+
+ McpLogger.LogWarning($"[MCP Unity] Could not find asset: {assetPath}");
+ return null;
+ }
+ else if (token.Type == JTokenType.Object)
+ {
+ // Handle object with guid or path property
+ JObject obj = (JObject)token;
+ string guid = obj["guid"]?.ToObject();
+ string path = obj["path"]?.ToObject();
+
+ if (!string.IsNullOrEmpty(guid))
+ {
+ string assetPath = AssetDatabase.GUIDToAssetPath(guid);
+ if (!string.IsNullOrEmpty(assetPath))
+ {
+ return AssetDatabase.LoadAssetAtPath(assetPath, targetType);
+ }
+ McpLogger.LogWarning($"[MCP Unity] Could not find asset with GUID: {guid}");
+ }
+ else if (!string.IsNullOrEmpty(path))
+ {
+ return AssetDatabase.LoadAssetAtPath(path, targetType);
+ }
+ }
+ else if (token.Type == JTokenType.Null)
+ {
+ return null;
+ }
}
// Handle enum types