Unity Class深拷贝问题分析

深拷贝

前言

在Unity项目中,我们面临一个读取数据表并深拷贝该类的问题。具体情况是这样的:我们需要从数据表中读取人物的数据,但在战斗过程中,人物的数据会不断发生变化。因此,我们需要一个数据类来存储人物的数据,并且希望在不修改原始数据表的情况下,创建一个副本用于战斗。

为了实现这一逻辑,我们采取了以下步骤:首先,我们将Json或Xml格式的数据表反序列化为原始数据类,使用工具进行反序列化操作。然后,我们对原始数据类进行深拷贝,创建一个战斗数据类的副本。通过这种方式,我们既可以使用原始数据表中的数据,又能在战斗过程中对战斗数据类进行修改,而不会影响原始数据表的功能。

这样做的好处是,我们可以在战斗中独立使用战斗数据类,而不会影响原始数据表的完整性。同时,通过深拷贝的方式,我们确保战斗数据类是一个全新的对象,可以独立于原始数据类进行修改,避免了对象引用带来的问题。

常用解决方案

1.手动复制字段

如果类的字段较少且结构简单,可以手动复制每个字段来创建新的对象。这需要逐个复制类的每个字段,并确保复制的是字段的值而不是引用。

public class MyClass
{
    
    
    public int myInt;
    public string myString;

    public MyClass DeepCopy()
    {
    
    
        MyClass newObject = new MyClass();
        newObject.myInt = myInt;
        newObject.myString = myString;
        return newObject;
    }
}

2.使用序列化工具

使用JsonUtility、MsgPack、Protobuf 等工具库进行序列化和反序列化功能,并能够处理更复杂的类结构。通过将对象序列化为字节流,然后再反序列化为新的对象,可以实现深拷贝。
比如下面这种:

TIP:别用这个,这是我当前项目的,没有对应类用不了

    /// <summary>
    /// Json文件转实体类
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    /// <returns></returns>
    public static List<T> JsonToObject<T>(string value) where T : ConfigJsonBase
    {
    
    
        List<T> lst = new List<T>();
        var test = JsonConvert.DeserializeObject<ConfigJsonContainer>(value, settings);
        test.CopyListToDic();
        foreach (var item in test.dataMap)
        {
    
    
            T line1 = (T)item.Value;
            lst.Add(line1);
        }
        return lst;
    }

相当于再次从表中序列化一次获得一个全新的拷贝

3.使用Instantiate方法(只能用于MonoBehaviour)

使用 UnityEngine.Object.Instantiate方法:如果需要复制 Unity 引擎的 GameObjectMonoBehaviour,可以使用 Instantiate方法来创建它们的副本。这个方法会创建一个全新的实例,包括所有的组件和属性,并将它们与原始对象解耦。

public class CopyExample : MonoBehaviour
{
    
    
    public GameObject originalObject;
    private GameObject copiedObject;

    public void PerformCopy()
    {
    
    
        copiedObject = Instantiate(originalObject);
        // 对复制对象进行进一步操作...
    }
}

4.重写运算符赋值

下面这个案例是通过重写&运算符实现创建一个新的类并将所有字段赋值。
通过使用&运算符创建新对象时,可以通过将原始对象作为第一个参数传递给运算符,并忽略第二个参数,以触发运算符的重载。新的 newCard对象将具有与原始对象相同的字段值。

这种方法依赖于运算符的重写,并且在使用时需要注意运算符的语义和正确使用方式。另外,由于 & 运算符通常与按位与操作相关,重写它来创建新对象可能会使代码的可读性降低。因此,在实际使用时,建议谨慎选择是否使用这种重写运算符的方法,以确保代码的清晰性和可维护性。

public class CfgCardProperties : ConfigJsonBase
{
    
    
    public string _CardName;
    public List<string> _CardIconPath;
    public bool _IsShowCardUnder;

    public static CfgCardProperties operator &(CfgCardProperties card, CfgCardProperties cfg)
    {
    
    
        CfgCardProperties newCard = new CfgCardProperties();
        card.ID = cfg.ID;
        card._CardName = cfg._CardName;
        card._CardIconPath = cfg._CardIconPath != null ? new List<string>(cfg._CardIconPath) : null;
        return newCard;
    }
}

用法是

var cfg = new CfgCardProperties();
cfg &= originalCfgs[i];

5.使用Visual Scripting中提供的拷贝函数(推荐)

Unity Visual Scripting中,CloneViaFakeSerialization节点可以用于实现对象的深拷贝。该节点在 Bolt 或其他 Unity 可视化脚本工具中提供,它通过序列化和反序列化对象来创建其副本。

CloneViaFakeSerialization节点的工作原理如下:

  1. 将要克隆的对象进行序列化,将其转换为字节流。
  2. 将字节流反序列化为一个新的对象。

在上面例子中只需要一行就可以实现类的深拷贝

using Unity.VisualScripting;

cfg= originalCfgs[i].CloneViaFakeSerialization();

猜你喜欢

转载自blog.csdn.net/a71468293a/article/details/131205203