5
votes

This should be a fairly simple problem to solve however I've tried several ways of doing it but the results are always the same.

I'm trying to copy a list containing GameObjects into another list. The problem is it seems I'm copying the references since any changes done to the GameObjects of the original list, also affect the ones on the new list, something that I don't want to happen. From what I've read I'm doing a shallow copy instead of a deep copy, so I tried to use the following code to clone each object:

public static class ObjectCopier
    {
        /// <summary>
        /// Perform a deep Copy of the object.
        /// </summary>
        /// <typeparam name="T">The type of object being copied.</typeparam>
        /// <param name="source">The object instance to copy.</param>
        /// <returns>The copied object.</returns>
        public static GameObject Clone<GameObject>(GameObject source)
        {

            if (!typeof(GameObject).IsSerializable)
            {
                throw new ArgumentException("The type must be serializable ", "source: " + source);
            }

            // Don't serialize a null object, simply return the default for that object
            /*if (Object.ReferenceEquals(source, null))
            {
                return default(GameObject);
            }*/

            IFormatter formatter = new BinaryFormatter();
            Stream stream = new MemoryStream();
            using (stream)
            {
                formatter.Serialize(stream, source);
                stream.Seek(0, SeekOrigin.Begin);
                return (GameObject)formatter.Deserialize(stream);
            }
        }
    }

I get the following error:

ArgumentException: The type must be serializable Parameter name: source: SP0 (UnityEngine.GameObject) ObjectCopier.Clone[GameObject] (UnityEngine.GameObject source) (at Assets/Scripts/ScenarioManager.cs:121)

The function posted above is called here:

    void SaveScenario(){
        foreach(GameObject obj in sleManager.listOfSourcePoints){
            tempObj = ObjectCopier.Clone(obj);

            listOfScenarioSourcePoints.Add(tempObj);
            Debug.Log("Saved Scenario Source List Point");
        }
        foreach(GameObject obj in sleManager.listOfDestPoints){
            tempObj = ObjectCopier.Clone(obj);
            listOfScenarioDestPoints.Add(tempObj);
            Debug.Log("Saved Scenario Dest List Point");
        }
    }

    void LoadScenario(){
        sleManager.listOfSourcePoints.Clear();
        sleManager.listOfDestPoints.Clear ();
        foreach(GameObject obj in listOfScenarioSourcePoints){
            tempObj = ObjectCopier.Clone(obj);
            sleManager.listOfSourcePoints.Add(tempObj);
            Debug.Log("Loaded Scenario Source List Point");
        }
        foreach(GameObject obj in listOfScenarioDestPoints){
            tempObj = ObjectCopier.Clone(obj);
            sleManager.listOfDestPoints.Add(tempObj);
            Debug.Log("Loaded Scenario Dest List Point");
        }
    }

Now, the original list is created here:

if (child.name == "DestinationPoints")
{
     parentDestinationPoints = child.gameObject;
     foreach (Transform grandChildDP in parentDestinationPoints.transform)
     {
          //Debug.Log("Added DP object named: " + grandChildDP.name);
          tempObj = grandChildDP.gameObject;
          listOfDestPoints.Add(tempObj);
          tempObj.AddComponent<DestinationControl>();
          tempObj.transform.renderer.material.color = Color.white;
     }
}

// Hide all SourcePoints in the scene
if (child.name == "SourcePoints")
{
    parentSourcePoints = child.gameObject;
    foreach (Transform grandChildSP in parentSourcePoints.transform)
    {
         tempObj = grandChildSP.gameObject;
         listOfSourcePoints.Add(tempObj);
         tempObj.transform.renderer.enabled = false;
    }
}

This "tempObj" has the [SerializeField] property so I must be missing something here. Any help would be greatly appreciated.

EDIT: Forgot to mention, this app is in Unity3D.

3
As far as I know you can't simply create a copy of a GameObject the way you're trying to. I think you need to Instantiate a new GameObject, then add that to the second listSteven Mills

3 Answers

2
votes

I think you need to mark your class GameObject that you are attempting to clone with the [Serializable] attribute. I would also make the clone method generic so that it is not limited to type:

/// <summary>
/// Creates a deep clone of an object using serialization.
/// </summary>
/// <typeparam name="T">The type to be cloned/copied.</typeparam>
/// <param name="o">The object to be cloned.</param>
public static T DeepClone<T>(this T o)
{
    using (MemoryStream stream = new MemoryStream())
    {
        BinaryFormatter formatter = new BinaryFormatter();
        formatter.Serialize(stream, o);
        stream.Position = 0;
        return (T)formatter.Deserialize(stream);
    }
}

I hope this helps.


Edit. Perhaps a deep copy without serialization using some thing like

class A
{
    // copy constructor
    public A(A copy) {}
}

// A referenced class implementing 
class B : IDeepCopy
{
    object Copy() { return new B(); }
}

class C : IDeepCopy
{
    A A;
    B B;
    object Copy()
    {
        C copy = new C();

        // copy property by property in a appropriate way
        copy.A = new A(this.A);
        copy.B = this.B.Copy();
     }
}

I have also just found Copyable which uses reflection to provide deep copies of objects. Again, I hope this was of some use...

0
votes

Use Instantiate in Unity to create a full copy of an object as described here: http://docs.unity3d.com/Documentation/ScriptReference/Object.Instantiate.html?from=MonoBehaviour

var clone = Object.Instantiate(source);
0
votes

I think I was overcomplicating things, ended up saving the values (game objects position) I needed in a XML file (had to do it at some point anyway). Thanks for all help though.