Cloning Objects with Instantiate() - variables/references for added Components not stored?

I ran into the following behavior of unity where I can’t find an explanation for - though I highly suspect, the reason is my misunderstanding of Instantiate() or something, so I hope someone can point me to my error.

To describe the problem, I created 3 classes just to reproduce it:

In FooBehavior.cs a MonoBehaviour which is just that - an “empty” behavior:

public class FooBehavior : MonoBehaviour {

}

In Clonable.cs a MonoBehaviour which will get a FooBehavior via AddComponent() in its Awake()-Method and will later be cloned via Instantiate():

public class Clonable : MonoBehaviour {
	FooBehavior myFooBehavior;

	void Awake () {
		if (myFooBehavior != null) {
			return; // The FooBehavior-Component has been added in the "original", this must be a clone. No need to add it again.
		}
		Debug.Log ("Clonable.Awake(). myFooBehavior is " + ((myFooBehavior == null) ? "null" : "already defined"));
		myFooBehavior = gameObject.AddComponent<FooBehavior> ();
	}

	public int NumberOfFooBehaviors() {
		return GetComponents<FooBehavior> ().Length;
	}
}

In CloningManager.cs the class which will take care of the cloning and “testing” the result with a debug-output:

public class CloningManager : MonoBehaviour {
	Clonable[] originals;
	Clonable[] clones;

	void Start() {
		// Clone all Clonables
		originals = GetComponentsInChildren<Clonable> ();
		clones = new Clonable[originals.Length];
		for (int i = 0; i < originals.Length; i++) {
			Clonable original = originals *;*
  •   		Clonable clone = Instantiate (original, original.transform.position, Quaternion.identity, null) as Clonable;*
    

_ clones = clone;_
* }*
* // Use Coroutine to ensure the Debugging happens on the next frame (so when Start() is completed).*
* StartCoroutine(DebugClonables());*
* }*
* IEnumerator DebugClonables() {*
* yield return new WaitForEndOfFrame ();*
* foreach (Clonable c in originals) {*
* Debug.Log (“I am an original and I have " + c.NumberOfFooBehaviors () + " FooBehaviors attached”);*
* }*
* foreach (Clonable c in clones) {*
* Debug.Log (“I am a clone and I have " + c.NumberOfFooBehaviors () + " FooBehaviors attached”);*
* }*
* }*
* }*

The cloning itself works fine, but in the clone (I only added one “original” for testing purposes) appearantly myFooBehavior is still null and thus another FooBehavior is added, as you can see in the console output:
[88435-cloningtest.jpg|88435]_
Can I prevent this from happening? Am I understanding Instantiate() incorrectly and it is to be expected, that a cloned instance does not “remember” its references (in this case, the one pointing from myFooBehavior to the added FooBehavior-Component)?
What I also tried is giving FooBehavior and Clonable the Attribute [System.Serializable] as well as making Clonable.myFooBehavior public - still the same output.
Thanks for any advice!
_

Hum, interesting…

With time, I’ve come to use as less assignments as possible in Awake, and rather use accessors, like :

private FooBehavior _myFooBehavior;
public FooBehavior myFooBehavior
{
    get
    {
        if (_myFooBehavior == null) // if not assigned
        {
            _myFooBehavior = GetComponent<FooBehaviour>(); // find it
            if (_myFooBehavior == null) // if none assigned
                _myFooBehavior = AddComponent<FooBehaviour>(); // add one
        }
        return _myFooBehavior;
    }
}

With this implementation, you can access it anytime.

The reason is quite simple. Your class variable “myFooBehavior” is not serialized since it’s a private variable and it doesn’t have the SerializeField attribute. Therefore when you create a clone / instantiate the object, that variable will not be set to anything in the new instance.

So one solution is to make the variable serialized by either making the variable public or add the SerializeField attribute:

public FooBehavior myFooBehavior;

or

[SerializeField]
FooBehavior myFooBehavior;

In both cases the variable should now show up in the inspector like all serialized values. If you don’t want the variable to show up in the inspector, but you still want it to be serialized, just add the HideInInspector attribute

[HideInInspector]
public FooBehavior myFooBehavior;

or

[SerializeField, HideInInspector]
FooBehavior myFooBehavior;

Of course using a property like @UnityCoach showed also works, but it requires an additional GetComponent call. Also when you have multiple classes of the same type attached you might access the wrong component. Though that are usually rare cases.

If I understood correctly what you mean the mistake is that you never set value for myFooBehavior. It’s null at awake and with these codes will always be null.

Clonable.cs

public class Clonable : MonoBehaviour
{
    FooBehavior myFooBehavior;
    void Awake()
    {
        if (myFooBehavior != null)
        {
            return; // The FooBehavior-Component has been added in the "original", this must be a clone. No need to add it again.
        }
        Debug.Log("Clonable.Awake(). myFooBehavior is " + ((myFooBehavior == null) ? "null" : "already defined"));
        myFooBehavior = gameObject.AddComponent<FooBehavior>(); //Set the value of myFooBehavior when the component is added.
    }
    public int NumberOfFooBehaviors()
    {
        return GetComponents<FooBehavior>().Length;
    }
}

A bit old, but another tip:
If the scripts of the original object had some reference to a class which isn’t a MonoBehaviour descendant, wont be cloned neither, and can’t appear in the inspector, even if you use the SerializeField attribute or put the variable on public. I just discover it, and I suppose have all the sense… Thank you for the info of the thread anyway, was very helpfull for me :slight_smile: