升级XR插件版本
This commit is contained in:
349
Packages/MCPForUnity/Editor/Helpers/ComponentOps.cs
Normal file
349
Packages/MCPForUnity/Editor/Helpers/ComponentOps.cs
Normal file
@@ -0,0 +1,349 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MCPForUnity.Editor.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Low-level component operations extracted from ManageGameObject and ManageComponents.
|
||||
/// Provides pure C# operations without JSON parsing or response formatting.
|
||||
/// </summary>
|
||||
public static class ComponentOps
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a component to a GameObject with Undo support.
|
||||
/// </summary>
|
||||
/// <param name="target">The target GameObject</param>
|
||||
/// <param name="componentType">The type of component to add</param>
|
||||
/// <param name="error">Error message if operation fails</param>
|
||||
/// <returns>The added component, or null if failed</returns>
|
||||
public static Component AddComponent(GameObject target, Type componentType, out string error)
|
||||
{
|
||||
error = null;
|
||||
|
||||
if (target == null)
|
||||
{
|
||||
error = "Target GameObject is null.";
|
||||
return null;
|
||||
}
|
||||
|
||||
if (componentType == null || !typeof(Component).IsAssignableFrom(componentType))
|
||||
{
|
||||
error = $"Type '{componentType?.Name ?? "null"}' is not a valid Component type.";
|
||||
return null;
|
||||
}
|
||||
|
||||
// Prevent adding duplicate Transform
|
||||
if (componentType == typeof(Transform))
|
||||
{
|
||||
error = "Cannot add another Transform component.";
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check for 2D/3D physics conflicts
|
||||
string conflictError = CheckPhysicsConflict(target, componentType);
|
||||
if (conflictError != null)
|
||||
{
|
||||
error = conflictError;
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Component newComponent = Undo.AddComponent(target, componentType);
|
||||
if (newComponent == null)
|
||||
{
|
||||
error = $"Failed to add component '{componentType.Name}' to '{target.name}'. It might be disallowed.";
|
||||
return null;
|
||||
}
|
||||
|
||||
// Apply default values for specific component types
|
||||
ApplyDefaultValues(newComponent);
|
||||
|
||||
return newComponent;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = $"Error adding component '{componentType.Name}': {ex.Message}";
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a component from a GameObject with Undo support.
|
||||
/// </summary>
|
||||
/// <param name="target">The target GameObject</param>
|
||||
/// <param name="componentType">The type of component to remove</param>
|
||||
/// <param name="error">Error message if operation fails</param>
|
||||
/// <returns>True if component was removed successfully</returns>
|
||||
public static bool RemoveComponent(GameObject target, Type componentType, out string error)
|
||||
{
|
||||
error = null;
|
||||
|
||||
if (target == null)
|
||||
{
|
||||
error = "Target GameObject is null.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (componentType == null)
|
||||
{
|
||||
error = "Component type is null.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prevent removing Transform
|
||||
if (componentType == typeof(Transform))
|
||||
{
|
||||
error = "Cannot remove Transform component.";
|
||||
return false;
|
||||
}
|
||||
|
||||
Component component = target.GetComponent(componentType);
|
||||
if (component == null)
|
||||
{
|
||||
error = $"Component '{componentType.Name}' not found on '{target.name}'.";
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Undo.DestroyObjectImmediate(component);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = $"Error removing component '{componentType.Name}': {ex.Message}";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a property value on a component using reflection.
|
||||
/// </summary>
|
||||
/// <param name="component">The target component</param>
|
||||
/// <param name="propertyName">The property or field name</param>
|
||||
/// <param name="value">The value to set (JToken)</param>
|
||||
/// <param name="error">Error message if operation fails</param>
|
||||
/// <returns>True if property was set successfully</returns>
|
||||
public static bool SetProperty(Component component, string propertyName, JToken value, out string error)
|
||||
{
|
||||
error = null;
|
||||
|
||||
if (component == null)
|
||||
{
|
||||
error = "Component is null.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(propertyName))
|
||||
{
|
||||
error = "Property name is null or empty.";
|
||||
return false;
|
||||
}
|
||||
|
||||
Type type = component.GetType();
|
||||
BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase;
|
||||
string normalizedName = ParamCoercion.NormalizePropertyName(propertyName);
|
||||
|
||||
// Try property first - check both original and normalized names for backwards compatibility
|
||||
PropertyInfo propInfo = type.GetProperty(propertyName, flags)
|
||||
?? type.GetProperty(normalizedName, flags);
|
||||
if (propInfo != null && propInfo.CanWrite)
|
||||
{
|
||||
try
|
||||
{
|
||||
object convertedValue = PropertyConversion.ConvertToType(value, propInfo.PropertyType);
|
||||
// Detect conversion failure: null result when input wasn't null
|
||||
if (convertedValue == null && value.Type != JTokenType.Null)
|
||||
{
|
||||
error = $"Failed to convert value for property '{propertyName}' to type '{propInfo.PropertyType.Name}'.";
|
||||
return false;
|
||||
}
|
||||
propInfo.SetValue(component, convertedValue);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = $"Failed to set property '{propertyName}': {ex.Message}";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Try field - check both original and normalized names for backwards compatibility
|
||||
FieldInfo fieldInfo = type.GetField(propertyName, flags)
|
||||
?? type.GetField(normalizedName, flags);
|
||||
if (fieldInfo != null && !fieldInfo.IsInitOnly)
|
||||
{
|
||||
try
|
||||
{
|
||||
object convertedValue = PropertyConversion.ConvertToType(value, fieldInfo.FieldType);
|
||||
// Detect conversion failure: null result when input wasn't null
|
||||
if (convertedValue == null && value.Type != JTokenType.Null)
|
||||
{
|
||||
error = $"Failed to convert value for field '{propertyName}' to type '{fieldInfo.FieldType.Name}'.";
|
||||
return false;
|
||||
}
|
||||
fieldInfo.SetValue(component, convertedValue);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = $"Failed to set field '{propertyName}': {ex.Message}";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Try non-public serialized fields - traverse inheritance hierarchy
|
||||
// Type.GetField() with NonPublic only finds fields declared directly on that type,
|
||||
// so we need to walk up the inheritance chain manually
|
||||
fieldInfo = FindSerializedFieldInHierarchy(type, propertyName)
|
||||
?? FindSerializedFieldInHierarchy(type, normalizedName);
|
||||
if (fieldInfo != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
object convertedValue = PropertyConversion.ConvertToType(value, fieldInfo.FieldType);
|
||||
// Detect conversion failure: null result when input wasn't null
|
||||
if (convertedValue == null && value.Type != JTokenType.Null)
|
||||
{
|
||||
error = $"Failed to convert value for serialized field '{propertyName}' to type '{fieldInfo.FieldType.Name}'.";
|
||||
return false;
|
||||
}
|
||||
fieldInfo.SetValue(component, convertedValue);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = $"Failed to set serialized field '{propertyName}': {ex.Message}";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
error = $"Property or field '{propertyName}' not found on component '{type.Name}'.";
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all public properties and fields from a component type.
|
||||
/// </summary>
|
||||
public static List<string> GetAccessibleMembers(Type componentType)
|
||||
{
|
||||
var members = new List<string>();
|
||||
if (componentType == null) return members;
|
||||
|
||||
BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;
|
||||
|
||||
foreach (var prop in componentType.GetProperties(flags))
|
||||
{
|
||||
if (prop.CanWrite && prop.GetSetMethod() != null)
|
||||
{
|
||||
members.Add(prop.Name);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var field in componentType.GetFields(flags))
|
||||
{
|
||||
if (!field.IsInitOnly)
|
||||
{
|
||||
members.Add(field.Name);
|
||||
}
|
||||
}
|
||||
|
||||
// Include private [SerializeField] fields - traverse inheritance hierarchy
|
||||
// Type.GetFields with NonPublic only returns fields declared directly on that type,
|
||||
// so we need to walk up the chain to find inherited private serialized fields
|
||||
var seenFieldNames = new HashSet<string>(members); // Avoid duplicates with public fields
|
||||
Type currentType = componentType;
|
||||
while (currentType != null && currentType != typeof(object))
|
||||
{
|
||||
foreach (var field in currentType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly))
|
||||
{
|
||||
if (field.GetCustomAttribute<SerializeField>() != null && !seenFieldNames.Contains(field.Name))
|
||||
{
|
||||
members.Add(field.Name);
|
||||
seenFieldNames.Add(field.Name);
|
||||
}
|
||||
}
|
||||
currentType = currentType.BaseType;
|
||||
}
|
||||
|
||||
members.Sort();
|
||||
return members;
|
||||
}
|
||||
|
||||
// --- Private Helpers ---
|
||||
|
||||
/// <summary>
|
||||
/// Searches for a non-public [SerializeField] field through the entire inheritance hierarchy.
|
||||
/// Type.GetField() with NonPublic only returns fields declared directly on that type,
|
||||
/// so this method walks up the chain to find inherited private serialized fields.
|
||||
/// </summary>
|
||||
private static FieldInfo FindSerializedFieldInHierarchy(Type type, string fieldName)
|
||||
{
|
||||
if (type == null || string.IsNullOrEmpty(fieldName))
|
||||
return null;
|
||||
|
||||
BindingFlags privateFlags = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly;
|
||||
Type currentType = type;
|
||||
|
||||
// Walk up the inheritance chain
|
||||
while (currentType != null && currentType != typeof(object))
|
||||
{
|
||||
// Search for the field on this specific type (case-insensitive)
|
||||
foreach (var field in currentType.GetFields(privateFlags))
|
||||
{
|
||||
if (string.Equals(field.Name, fieldName, StringComparison.OrdinalIgnoreCase) &&
|
||||
field.GetCustomAttribute<SerializeField>() != null)
|
||||
{
|
||||
return field;
|
||||
}
|
||||
}
|
||||
currentType = currentType.BaseType;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string CheckPhysicsConflict(GameObject target, Type componentType)
|
||||
{
|
||||
bool isAdding2DPhysics =
|
||||
typeof(Rigidbody2D).IsAssignableFrom(componentType) ||
|
||||
typeof(Collider2D).IsAssignableFrom(componentType);
|
||||
|
||||
bool isAdding3DPhysics =
|
||||
typeof(Rigidbody).IsAssignableFrom(componentType) ||
|
||||
typeof(Collider).IsAssignableFrom(componentType);
|
||||
|
||||
if (isAdding2DPhysics)
|
||||
{
|
||||
if (target.GetComponent<Rigidbody>() != null || target.GetComponent<Collider>() != null)
|
||||
{
|
||||
return $"Cannot add 2D physics component '{componentType.Name}' because the GameObject '{target.name}' already has a 3D Rigidbody or Collider.";
|
||||
}
|
||||
}
|
||||
else if (isAdding3DPhysics)
|
||||
{
|
||||
if (target.GetComponent<Rigidbody2D>() != null || target.GetComponent<Collider2D>() != null)
|
||||
{
|
||||
return $"Cannot add 3D physics component '{componentType.Name}' because the GameObject '{target.name}' already has a 2D Rigidbody or Collider.";
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void ApplyDefaultValues(Component component)
|
||||
{
|
||||
// Default newly added Lights to Directional
|
||||
if (component is Light light)
|
||||
{
|
||||
light.type = LightType.Directional;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user