Unity 中的反射使用详解

1. Unity 中反射的特殊性

Unity 中的反射使用与标准 C# 基本相同,但需要注意以下特殊点:

1.1 Unity 对象特殊处理

csharp

// MonoBehaviour 的特殊性
public class Player : MonoBehaviour
{
    public int health = 100;
    private string playerName = "Hero";
    
    public void TakeDamage(int amount)
    {
        health -= amount;
    }
    
    private void OnDestroy()
    {
        // Unity 生命周期方法
    }
}

// 获取 MonoBehaviour 类型
Type playerType = typeof(Player);

// Unity 特定的特性
[System.Serializable]
public class Item
{
    public string name;
    public int value;
}

1.2 Unity 编辑器和运行时反射

csharp

#if UNITY_EDITOR
using UnityEditor;
#endif

public class ReflectionExamples : MonoBehaviour
{
    // Unity Editor 中的反射使用
#if UNITY_EDITOR
    [MenuItem("Tools/List Components")]
    public static void ListAllComponents()
    {
        GameObject[] allObjects = GameObject.FindObjectsOfType<GameObject>();
        foreach (GameObject go in allObjects)
        {
            Component[] components = go.GetComponents<Component>();
            foreach (Component comp in components)
            {
                Debug.Log($"Object: {go.name}, Component: {comp.GetType().Name}");
            }
        }
    }
#endif
}

2. Unity 中常见的反射应用场景

2.1 动态获取和设置组件属性

csharp

public class ComponentReflector : MonoBehaviour
{
    public GameObject targetObject;
    public string componentTypeName = "Rigidbody";
    public string propertyName = "mass";
    public float newValue = 2.0f;
    
    void Start()
    {
        if (targetObject == null) return;
        
        // 动态获取组件
        Type componentType = Type.GetType($"UnityEngine.{componentTypeName}, UnityEngine");
        if (componentType != null)
        {
            Component component = targetObject.GetComponent(componentType);
            if (component != null)
            {
                // 获取属性信息
                PropertyInfo property = componentType.GetProperty(propertyName);
                if (property != null && property.CanWrite)
                {
                    // 获取当前值
                    object currentValue = property.GetValue(component);
                    Debug.Log($"Current {propertyName}: {currentValue}");
                    
                    // 设置新值
                    property.SetValue(component, newValue);
                    Debug.Log($"Set {propertyName} to: {newValue}");
                }
                else
                {
                    Debug.LogError($"Property '{propertyName}' not found or read-only");
                }
            }
        }
    }
}

2.2 查找带有特定特性的组件

csharp

// 自定义特性
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class ExposeInEditorAttribute : Attribute
{
    public string DisplayName { get; private set; }
    
    public ExposeInEditorAttribute(string displayName = null)
    {
        DisplayName = displayName;
    }
}

// 使用特性的组件
public class ConfigurableObject : MonoBehaviour
{
    [ExposeInEditor("移动速度")]
    public float moveSpeed = 5.0f;
    
    [ExposeInEditor("跳跃高度")]
    public float jumpHeight = 2.0f;
    
    [SerializeField, ExposeInEditor("血量")]
    private int health = 100;
    
    // 普通字段,不会被暴露
    public float rotationSpeed = 90.0f;
}

// 反射查找带有特性的字段
public class AttributeFinder : MonoBehaviour
{
    void Start()
    {
        ConfigurableObject obj = GetComponent<ConfigurableObject>();
        if (obj == null) return;
        
        Type type = typeof(ConfigurableObject);
        
        // 查找所有字段
        FieldInfo[] fields = type.GetFields(
            BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
        
        foreach (FieldInfo field in fields)
        {
            // 检查是否有 ExposeInEditorAttribute
            ExposeInEditorAttribute attribute = 
                field.GetCustomAttribute<ExposeInEditorAttribute>();
            
            if (attribute != null)
            {
                string displayName = string.IsNullOrEmpty(attribute.DisplayName) 
                    ? field.Name 
                    : attribute.DisplayName;
                
                object value = field.GetValue(obj);
                Debug.Log($"{displayName}: {value}");
            }
        }
        
        // 也可以查找属性
        PropertyInfo[] properties = type.GetProperties(
            BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
        
        foreach (PropertyInfo property in properties)
        {
            ExposeInEditorAttribute attribute = 
                property.GetCustomAttribute<ExposeInEditorAttribute>();
            
            if (attribute != null)
            {
                string displayName = string.IsNullOrEmpty(attribute.DisplayName) 
                    ? property.Name 
                    : attribute.DisplayName;
                
                object value = property.GetValue(obj);
                Debug.Log($"{displayName}: {value}");
            }
        }
    }
}

2.3 动态添加组件和执行方法

csharp

public class DynamicComponentManager : MonoBehaviour
{
    public void AddComponentDynamically(string componentName)
    {
        // 构建完整的类型名称
        string fullTypeName = componentName;
        if (!fullTypeName.Contains("UnityEngine"))
        {
            // 尝试从当前程序集查找
            fullTypeName = $"{componentName}, Assembly-CSharp";
        }
        
        Type componentType = Type.GetType(fullTypeName);
        if (componentType != null && 
            typeof(Component).IsAssignableFrom(componentType))
        {
            gameObject.AddComponent(componentType);
            Debug.Log($"Added component: {componentType.Name}");
        }
        else
        {
            Debug.LogError($"Cannot find component type: {componentName}");
        }
    }
    
    public void InvokeMethodDynamically(string methodName, object parameter = null)
    {
        MonoBehaviour[] components = GetComponents<MonoBehaviour>();
        
        foreach (MonoBehaviour component in components)
        {
            Type type = component.GetType();
            MethodInfo method = null;
            
            // 尝试查找方法
            if (parameter == null)
            {
                method = type.GetMethod(methodName, Type.EmptyTypes);
                if (method != null)
                {
                    method.Invoke(component, null);
                    return;
                }
            }
            else
            {
                // 查找带参数的方法
                MethodInfo[] methods = type.GetMethods();
                foreach (MethodInfo m in methods)
                {
                    if (m.Name == methodName && 
                        m.GetParameters().Length == 1 &&
                        m.GetParameters()[0].ParameterType == parameter.GetType())
                    {
                        m.Invoke(component, new object[] { parameter });
                        return;
                    }
                }
            }
        }
        
        Debug.LogWarning($"Method {methodName} not found on any component");
    }
}

2.4 序列化和反序列化系统

csharp

using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public class GameData
{
    public string playerName;
    public int level;
    public float experience;
    public List<string> inventory;
}

public class ReflectionSerializer : MonoBehaviour
{
    public GameData gameData;
    
    // 使用反射将对象转换为字典
    public Dictionary<string, object> ObjectToDictionary(object obj)
    {
        Dictionary<string, object> dict = new Dictionary<string, object>();
        
        Type type = obj.GetType();
        FieldInfo[] fields = type.GetFields(
            BindingFlags.Public | BindingFlags.Instance);
        
        foreach (FieldInfo field in fields)
        {
            object value = field.GetValue(obj);
            dict[field.Name] = value;
        }
        
        PropertyInfo[] properties = type.GetProperties(
            BindingFlags.Public | BindingFlags.Instance);
        
        foreach (PropertyInfo property in properties)
        {
            if (property.CanRead)
            {
                object value = property.GetValue(obj);
                dict[property.Name] = value;
            }
        }
        
        return dict;
    }
    
    // 从字典恢复对象
    public T DictionaryToObject<T>(Dictionary<string, object> dict) where T : new()
    {
        T obj = new T();
        Type type = typeof(T);
        
        foreach (var kvp in dict)
        {
            FieldInfo field = type.GetField(kvp.Key);
            if (field != null && field.FieldType == kvp.Value.GetType())
            {
                field.SetValue(obj, kvp.Value);
                continue;
            }
            
            PropertyInfo property = type.GetProperty(kvp.Key);
            if (property != null && property.CanWrite && 
                property.PropertyType == kvp.Value.GetType())
            {
                property.SetValue(obj, kvp.Value);
            }
        }
        
        return obj;
    }
}

3. Unity 中的性能优化技巧

3.1 缓存反射结果

csharp

public class ReflectionCacheManager : MonoBehaviour
{
    private static Dictionary<string, FieldInfo> fieldCache = new Dictionary<string, FieldInfo>();
    private static Dictionary<string, MethodInfo> methodCache = new Dictionary<string, MethodInfo>();
    
    public static FieldInfo GetCachedField(Type type, string fieldName)
    {
        string key = $"{type.FullName}.{fieldName}";
        
        if (!fieldCache.ContainsKey(key))
        {
            fieldCache[key] = type.GetField(fieldName, 
                BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
        }
        
        return fieldCache[key];
    }
    
    public static MethodInfo GetCachedMethod(Type type, string methodName, Type[] parameterTypes = null)
    {
        string key = $"{type.FullName}.{methodName}";
        if (parameterTypes != null)
        {
            key += "." + string.Join(",", parameterTypes.Select(t => t.Name));
        }
        
        if (!methodCache.ContainsKey(key))
        {
            methodCache[key] = parameterTypes == null 
                ? type.GetMethod(methodName)
                : type.GetMethod(methodName, parameterTypes);
        }
        
        return methodCache[key];
    }
}

3.2 使用委托优化方法调用

csharp

public class OptimizedMethodInvoker : MonoBehaviour
{
    private delegate void DamageDelegate(int amount);
    private Dictionary<Type, DamageDelegate> damageDelegates = new Dictionary<Type, DamageDelegate>();
    
    void Start()
    {
        // 预编译委托
        PrecompileDamageDelegates();
    }
    
    void PrecompileDamageDelegates()
    {
        MonoBehaviour[] components = GetComponents<MonoBehaviour>();
        
        foreach (MonoBehaviour component in components)
        {
            Type type = component.GetType();
            MethodInfo method = type.GetMethod("TakeDamage", new Type[] { typeof(int) });
            
            if (method != null)
            {
                // 创建委托
                DamageDelegate damageDelegate = (DamageDelegate)Delegate.CreateDelegate(
                    typeof(DamageDelegate), component, method);
                
                damageDelegates[type] = damageDelegate;
            }
        }
    }
    
    public void ApplyDamage(int damage)
    {
        foreach (var kvp in damageDelegates)
        {
            kvp.Value(damage); // 性能接近直接调用
        }
    }
}

3.3 表达式树优化属性访问

csharp

using System.Linq.Expressions;

public class FastPropertyAccessor
{
    private Dictionary<string, Func<object, object>> getterCache = new Dictionary<string, Func<object, object>>();
    private Dictionary<string, Action<object, object>> setterCache = new Dictionary<string, Action<object, object>>();
    
    public Func<object, object> CreateGetter(FieldInfo field)
    {
        string key = $"{field.DeclaringType.FullName}.{field.Name}";
        
        if (!getterCache.ContainsKey(key))
        {
            // 创建表达式树: (object obj) => (object)((T)obj).field
            var param = Expression.Parameter(typeof(object), "obj");
            var cast = Expression.Convert(param, field.DeclaringType);
            var fieldAccess = Expression.Field(cast, field);
            var castResult = Expression.Convert(fieldAccess, typeof(object));
            
            var lambda = Expression.Lambda<Func<object, object>>(castResult, param);
            getterCache[key] = lambda.Compile();
        }
        
        return getterCache[key];
    }
    
    public Action<object, object> CreateSetter(FieldInfo field)
    {
        string key = $"{field.DeclaringType.FullName}.{field.Name}";
        
        if (!setterCache.ContainsKey(key))
        {
            // 创建表达式树: (object obj, object value) => ((T)obj).field = (TField)value
            var objParam = Expression.Parameter(typeof(object), "obj");
            var valueParam = Expression.Parameter(typeof(object), "value");
            
            var castObj = Expression.Convert(objParam, field.DeclaringType);
            var castValue = Expression.Convert(valueParam, field.FieldType);
            
            var fieldAccess = Expression.Field(castObj, field);
            var assign = Expression.Assign(fieldAccess, castValue);
            
            var lambda = Expression.Lambda<Action<object, object>>(assign, objParam, valueParam);
            setterCache[key] = lambda.Compile();
        }
        
        return setterCache[key];
    }
}

4. Unity 特定的反射工具类

4.1 组件查找器

csharp

public static class UnityReflectionHelper
{
    // 查找所有实现特定接口的组件
    public static List<T> FindComponentsOfInterface<T>() where T : class
    {
        List<T> results = new List<T>();
        
        MonoBehaviour[] allMonoBehaviours = GameObject.FindObjectsOfType<MonoBehaviour>();
        
        foreach (MonoBehaviour mono in allMonoBehaviours)
        {
            if (mono is T)
            {
                results.Add(mono as T);
            }
        }
        
        return results;
    }
    
    // 通过特性查找组件
    public static List<Component> FindComponentsWithAttribute<TAttribute>() 
        where TAttribute : Attribute
    {
        List<Component> results = new List<Component>();
        
        Component[] allComponents = GameObject.FindObjectsOfType<Component>();
        
        foreach (Component component in allComponents)
        {
            Type type = component.GetType();
            if (type.GetCustomAttribute<TAttribute>() != null)
            {
                results.Add(component);
            }
        }
        
        return results;
    }
    
    // 复制组件值
    public static void CopyComponentValues(Component source, Component target)
    {
        if (source.GetType() != target.GetType())
        {
            Debug.LogError("Component types don't match");
            return;
        }
        
        Type type = source.GetType();
        FieldInfo[] fields = type.GetFields(
            BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
        
        foreach (FieldInfo field in fields)
        {
            // 跳过 Unity 的特殊字段
            if (field.Name == "name" || field.Name == "hideFlags" || 
                field.Name == "gameObject" || field.Name == "transform")
                continue;
                
            object value = field.GetValue(source);
            field.SetValue(target, value);
        }
    }
}

4.2 事件系统动态绑定

csharp

public class DynamicEventBinder : MonoBehaviour
{
    public void BindUnityEvent<T>(UnityEvent<T> unityEvent, object target, string methodName)
    {
        Type targetType = target.GetType();
        MethodInfo method = targetType.GetMethod(methodName, new Type[] { typeof(T) });
        
        if (method != null)
        {
            unityEvent.AddListener((value) => 
            {
                method.Invoke(target, new object[] { value });
            });
        }
    }
    
    public void BindButtonClick(Button button, object target, string methodName)
    {
        Type targetType = target.GetType();
        MethodInfo method = targetType.GetMethod(methodName, Type.EmptyTypes);
        
        if (method != null)
        {
            button.onClick.AddListener(() => 
            {
                method.Invoke(target, null);
            });
        }
    }
}

5. 注意事项和限制

5.1 IL2CPP 限制

csharp

// IL2CPP 下反射的限制
public class IL2CPPReflectionWarning : MonoBehaviour
{
    void Start()
    {
        #if ENABLE_IL2CPP
        Debug.LogWarning("IL2CPP may have restrictions on dynamic code generation");
        
        // 以下可能在 IL2CPP 下无法工作
        // var assembly = Assembly.Load("DynamicAssembly");
        // var type = assembly.GetType("DynamicType");
        // var instance = Activator.CreateInstance(type);
        #endif
    }
}

5.2 AOT 编译平台

csharp

// 为 AOT 平台预先生成代码
[AOT.MonoPInvokeCallback(typeof(Action))]
public static void PreGenerateCode()
{
    // 提前调用可能会用到的反射代码,避免 AOT 剪裁
    Type[] types = {
        typeof(List<int>),
        typeof(Dictionary<string, object>),
        typeof(Vector3),
        typeof(Quaternion)
    };
    
    foreach (Type type in types)
    {
        // 预先生成各种操作
        var fields = type.GetFields();
        var properties = type.GetProperties();
        var methods = type.GetMethods();
    }
}

5.3 安全考虑

csharp

public class SafeReflection : MonoBehaviour
{
    // 白名单机制
    private static HashSet<string> allowedTypes = new HashSet<string>
    {
        "System.String",
        "System.Int32",
        "System.Single",
        "System.Boolean",
        "UnityEngine.Vector3",
        "UnityEngine.Quaternion"
    };
    
    public static bool IsTypeAllowed(string typeName)
    {
        return allowedTypes.Contains(typeName);
    }
    
    public static object SafeCreateInstance(string typeName)
    {
        if (!IsTypeAllowed(typeName))
        {
            Debug.LogError($"Type {typeName} is not allowed for reflection");
            return null;
        }
        
        Type type = Type.GetType(typeName);
        if (type != null)
        {
            return Activator.CreateInstance(type);
        }
        
        return null;
    }
}

6. 实际应用示例

6.1 游戏设置系统

csharp

public class GameSettings : MonoBehaviour
{
    [System.Serializable]
    public class Setting
    {
        public string key;
        public object value;
        public Type valueType;
    }
    
    public List<Setting> settings = new List<Setting>();
    
    public void ApplySettings()
    {
        foreach (Setting setting in settings)
        {
            // 查找所有相关的组件
            MonoBehaviour[] components = FindObjectsOfType<MonoBehaviour>();
            
            foreach (MonoBehaviour component in components)
            {
                Type type = component.GetType();
                FieldInfo field = type.GetField(setting.key, 
                    BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
                
                if (field != null && field.FieldType == setting.valueType)
                {
                    field.SetValue(component, setting.value);
                }
            }
        }
    }
}

6.2 技能系统

csharp

public class SkillSystem : MonoBehaviour
{
    [System.Serializable]
    public class SkillEffect
    {
        public string componentType;
        public string methodName;
        public object[] parameters;
    }
    
    public List<SkillEffect> effects = new List<SkillEffect>();
    
    public void ApplyEffects(GameObject target)
    {
        foreach (SkillEffect effect in effects)
        {
            Type type = Type.GetType(effect.componentType);
            if (type != null)
            {
                Component component = target.GetComponent(type);
                if (component != null)
                {
                    MethodInfo method = type.GetMethod(effect.methodName);
                    if (method != null)
                    {
                        method.Invoke(component, effect.parameters);
                    }
                }
            }
        }
    }
}

总结

Unity 中的反射是一个非常强大的工具,但在使用时需要注意:

  1. 性能:避免在 Update 等频繁调用的方法中使用反射

  2. 平台兼容性:注意 IL2CPP 和 AOT 平台的限制

  3. 缓存优化:对频繁使用的反射结果进行缓存

  4. 安全考虑:特别是对于用户输入的反射调用要进行验证

正确使用反射可以极大提高 Unity 项目的灵活性和可扩展性,特别是在编辑器工具开发、配置系统和插件架构中。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值