当使用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闪退


文章评论