当使用ScriptableObject.CreateInstance()复制一份UnityEngine.Object对象时,会获得了一份完全独立的内容,里面的内容都是默认的,如果此时需要将已有一份UnityEngine.Object对象的内容拷贝到新的UnityEngine.Object对象时,可以使用如下代码
public static class DeepCopyByReflection { /// <summary> /// 安全反射创建对象,(亲测:使用反射创建一个没有默认构造(无参)函数的类对象会导致unity闪退) /// </summary> /// <param name="classType">类的类型</param> /// <param name="instance">反射创建出的对象</param> /// <returns></returns> static bool TryCreateClassInstance(System.Type classType, out System.Object instance) { var ctors = classType.GetConstructors(); var hasDefaultConstructor = classType.IsArray; if (!hasDefaultConstructor) { foreach (var ctor in ctors) { if (ctor.GetParameters().Length == 0) { hasDefaultConstructor = true; break; } } } if (hasDefaultConstructor) { instance = System.Activator.CreateInstance(classType); return true; } instance = null; TBCDebug.LogError("classType:{0} has no default constructor! Please add it!", classType); return false; } /// <summary> /// 是否可以直接引用赋值(包括Unity序列化对象(比例:AnimationCurve、UnityEngine.Object)、值类型、字符串) /// </summary> /// <returns></returns> static bool CanRefAssignment(System.Type fieldType, object fromValue, out object toValue) { toValue = null; if (fieldType.IsClass) { if (typeof(UnityEngine.Object).IsAssignableFrom(fieldType)) { //如果是UnityEngine.Object,需要进行判空,否则会导致闪退 var go_Value = fromValue as UnityEngine.Object; if (go_Value != null) toValue = go_Value; return true; } if (fieldType.Assembly.FullName.StartsWith("UnityEngine.")) //如果这个类是Unity引擎的序列化对象,比如AnimationCurve也得是引用关系,否则会导致闪退 { //直接引用 toValue = fromValue; return true; } } if (fieldType.IsValueType || fieldType == typeof(string)) { //值类型与字符串类型直接赋值 toValue = fromValue; return true; } return false; } /// <summary> /// 深度拷贝两个对象中的所有成员值 /// </summary> /// <param name="from">从form对象</param> /// <param name="to">拷贝到to对象</param> public static void Copy(System.Object fromObj, System.Object toObj) { if (fromObj == null || toObj == null) return; var type = fromObj.GetType(); FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); foreach (var field in fields) { var fromValue = field.GetValue(fromObj); if (CanRefAssignment(field.FieldType, fromValue, out var toValue)) { field.SetValue(toObj, toValue); continue; } if (!field.FieldType.IsClass) { TBCDebug.LogError("未知类型:{0}", field.FieldType); continue; } if (fromValue == null) { field.SetValue(toObj, null); continue; } //1.处理数组 if (fromValue is System.Array fromArray) { System.Array toArray = toValue as System.Array; if (toArray == null) { //new 数组 toArray = System.Array.CreateInstance(field.FieldType, fromArray.Length); field.SetValue(toObj, toValue); } var itemType = fromArray.GetType().GetElementType(); for (int i = 0; i < fromArray.Length; i++) { var itemFromValue = fromArray.GetValue(i); if (CanRefAssignment(itemType, itemFromValue, out var itemToValue)) { toArray.SetValue(itemToValue, i); continue; } if (!itemType.IsClass) { TBCDebug.LogError("未知类型:{0}", itemType); continue; } if (TryCreateClassInstance(itemType, out var newItemValue)) { Copy(itemFromValue, newItemValue); toArray.SetValue(newItemValue, i); } } continue; } //2.处理List与类 if (toValue == null) { if (TryCreateClassInstance(field.FieldType, out toValue)) field.SetValue(toObj, toValue); else continue; } //2.1 处理List if (fromValue is IList fromList) { var toList = toValue as IList; toList.Clear(); foreach (var itemFromValue in fromList) { var itemType = itemFromValue.GetType(); if (CanRefAssignment(itemType, itemFromValue, out var itemToValue)) { toList.Add(itemToValue); continue; } if (!itemType.IsClass) { TBCDebug.LogError("未知类型:{0}", itemType); continue; } if (TryCreateClassInstance(itemType, out var newItemValue)) { Copy(itemFromValue, newItemValue); toList.Add(newItemValue); } } continue; } //2.2.处理常规类 Copy(fromValue, toValue); } } }
下面总结使用反射赋值导致Unity闪退的几个点
1.UnityEngine.Object 重载了==,判断是否为null,实际是指它对象的资源是否为空,在反射时由于会转化成System.Object,所以空的UnityEgnine.Object对象并不为空,直接赋值会导致Untiy闪退,解决方法是 先转成UnityEngine.Object,然后判空,如果是空,直接设置null
2.Unity除了UnityEngine.Object类型资源外,其他可序列化的数据,比如AnimationCurve数据,它内部有指针指向C++内存,如果new出来,反射赋值里面的指针,会导致Unity闪退,原因如下
这里的创建资源,内容保存在C++,C#只是持有指针,但是在析构时,会触发资源销毁,而且原来的Unity的C#对象还是持有该指针,所以会导致Unity闪退
文章评论