C# - 使用反射复制UnityEvent信息

时间:2016-08-23 04:05:48

标签: c# unity3d reflection delegates

我需要将事件从一个UnityEvent复制到另一个UnityEvent,因为一旦我想到这一点,我将在运行时将目标切换到另一个对象,到目前为止我所拥有的是:

MethodInfo info = UnityEventBase.GetValidMethodInfo (event1.GetPersistentTarget (i), event1.GetPersistentMethodName (i), Type.EmptyTypes);
UnityAction action = Delegate.CreateDelegate (typeof (UnityAction), info) as UnityAction;

event2.AddListener (action);

我得到ArgumentNullException: Argument cannot be null.,如果我将Type.EmptyTypes更改为new Type[] { typeof (float) },我会得到ArgumentException: method argument length mismatch

问题在于我不知道该放什么,因为我不知道它是什么类型(因为Unity Events可以发送bool,float等)。

Unity Docs不会涵盖这一点,所以希望其他人在过去取得成功。

3 个答案:

答案 0 :(得分:0)

对于任何在未来遇到这种情况的人来说,这都有效:

MethodInfo info = UnityEventBase.GetValidMethodInfo (event1.GetPersistentTarget (i), event1.GetPersistentMethodName (i), new Type[] { typeof (float) });
            UnityAction execute = () => info.Invoke (event1.GetPersistentTarget (i), new object[] { 180f });
            event2.AddListener (execute);

它只是不会在检查器中公开复制的侦听器,因此仍在寻找完美的解决方案。

答案 1 :(得分:0)

这可以通过反射并递归复制UnityEvent类的每个值字段来实现。由于性能下降,我不会在运行时使用它,但是对于编辑器来说非常有用。我使用一个静态帮助器类和一个扩展来克隆列表。

ReflectionHelper.cs

using System;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;

namespace CEUtilities.Helpers
{
    public static class ReflectionHelper
    {
        /// <summary>
        /// Gets all fields from an object and its hierarchy inheritance.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="flags">The flags.</param>
        /// <returns>All fields of the type.</returns>
        public static List<FieldInfo> GetAllFields(this Type type, BindingFlags flags)
        {
            // Early exit if Object type
            if (type == typeof(System.Object))
            {
                return new List<FieldInfo>();
            }

            // Recursive call
            var fields = type.BaseType.GetAllFields(flags);
            fields.AddRange(type.GetFields(flags | BindingFlags.DeclaredOnly));
            return fields;
        }

        /// <summary>
        /// Perform a deep copy of the class.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj">The object.</param>
        /// <returns>A deep copy of obj.</returns>
        /// <exception cref="System.ArgumentNullException">Object cannot be null</exception>
        public static T DeepCopy<T>(T obj)
        {
            if (obj == null)
            {
                throw new ArgumentNullException("Object cannot be null");
            }
            return (T)DoCopy(obj);
        }


        /// <summary>
        /// Does the copy.
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <returns></returns>
        /// <exception cref="System.ArgumentException">Unknown type</exception>
        private static object DoCopy(object obj)
        {
            if (obj == null)
            {
                return null;
            }

            // Value type
            var type = obj.GetType();
            if (type.IsValueType || type == typeof(string))
            {
                return obj;
            }

            // Array
            else if (type.IsArray)
            {
                Type elementType = type.GetElementType();
                var array = obj as Array;
                Array copied = Array.CreateInstance(elementType, array.Length);
                for (int i = 0; i < array.Length; i++)
                {
                    copied.SetValue(DoCopy(array.GetValue(i)), i);
                }
                return Convert.ChangeType(copied, obj.GetType());
            }

            // Unity Object
            else if (typeof(UnityEngine.Object).IsAssignableFrom(type))
            {
                return obj;
            }

            // Class -> Recursion
            else if (type.IsClass)
            {
                var copy = Activator.CreateInstance(obj.GetType());

                var fields = type.GetAllFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
                foreach (FieldInfo field in fields)
                {
                    var fieldValue = field.GetValue(obj);
                    if (fieldValue != null)
                    {
                        field.SetValue(copy, DoCopy(fieldValue));
                    }
                }

                return copy;
            }

            // Fallback
            else
            {
                throw new ArgumentException("Unknown type");
            }
        }
    }
}

UnityEventExtension.cs

using UnityEngine.Events;

using CEUtilities.Helpers;

namespace UnityEngine
{
    public static class UnityEventExtension
    {
        /// <summary>
        /// Clones the specified unity event list.
        /// </summary>
        /// <param name="ev">The unity event.</param>
        /// <returns>Cloned UnityEvent</returns>
        public static T Clone<T>(this T ev) where T : UnityEventBase
        {
            return ReflectionHelper.DeepCopy(ev);
        }
    }
}

然后可以像这样使用

this.OnStart = target.OnStart.Clone();

答案 2 :(得分:0)

我知道这很老,但是我一天中的大部分时间都在上网搜索以帮助我写点东西。我最终想到了可以使用的这个简单功能。这仅在编辑器中有效(但是之后又会在何时以其他方式进行此操作?)。

只要这是一个UnityEvent,它就会将具有其参数值(对象,字符串,整数,浮点数,空隙和布尔值)的统一事件从一个目标UnityEvent复制到另一个目标上的另一个事件。 / p>

欢迎每个人下载并根据需要进行构建。在这里查看: https://gist.github.com/wesleywh/1c56d880c0289371ea2dc47661a0cdaf