- Home /
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 [i];
Clonable clone = Instantiate (original, original.transform.position, Quaternion.identity, null) as Clonable;
clones [i] = 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:
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!
Not really sure about "the null" problem, but if you clone an object with FooBehaviour you'll obviously get a second one if you add it once again. $$anonymous$$g. I don't thank you really understand Instantiate, it clones everything connected with this object + scripts run from start.
TheGuyThatsHere of course I know that Instantiate clones everything - that's why I am checking the value of myFooBehavior beforehand, so the Clonable does not a second one. But it still does, because it does not remember the FooBehavior which the "original" added and stored under "myFooBehavior". That's "the null problem" here.
Answer by Bunny83 · Feb 20, 2017 at 07:45 PM
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.
ps: Another way that doesn't require a serialized variable or a property would be to use a combination of GetComponent and AddComponent. So ins$$anonymous$$d of blindly adding a new FooBehavior component you first check if there is one in case your variable is null
myFooBehavior = GetComponent<FooBehavior>();
if (myFooBehavior == null)
myFooBehavior = gameObject.AddComponent<FooBehavior> ();
This will work without a serialized variable
But that would lead to more added FooBehaviors again - the clone will then add a second FooBehavior (because the component is still cloned) to which myFooBehavior then points, but the first one will remain (unused) on the GameObject.
That's it, thank you very much!
I thought I had tried that before, because it came to my $$anonymous$$d before posting - but appearently I didn't! :D Thanks for pointing that out and explaining the logic behind it.
I guess as a rule of thumb one might try to remember, Instantiate() works like serializing an object and then deserializing into a new instance.
Answer by UnityCoach · Feb 20, 2017 at 02:32 PM
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.
That's indeed possibly, thanks for the suggestion, but it will get messy / not be impossible if I need to have two (or more) FooBehaviours in Clonable, which I need to distinguish; e.g.
private FooBehavior primaryFoo;
private FooBehavior secondaryFoo;
In that case, I couldn't use the GetComponent-Part of your suggestion, so I would end up with 4 FooBehaviors, of which 2 aren't stored as reference in Clonable.
(Actually, this whole problem only attracted my attention because of exactly this situation => a class which uses AddComponent in Awake() twice and then is cloned leading to 4 added components in the clone.)
You should consider using Start then, as Awake is similar to a Constructor, it is called prior to any initialisation. Or you may create your own Instantiate wrapper method, that calls an Init on the instance right after it's instantiated it.
I agree with you though, it's weird that instantiating an object doesn't behave like to you copy/paste it.
Yeah, well no matter if I use Awake(), Start() or any custom Init-$$anonymous$$ethod - the approach I am looking for does not seem to be possibe because, as you word it, the instantiated object doesn't behave like copy/paste. So in my case, I either have to clear any FooBehaviors in Awake() (or wherever I do the AddComponent-Call afterwards), which would work in this case, because I don't need any other FooBehaviors, but would be bad if I did, so is not a real general solution... In the end, I guess (unless there really is still some mistake I made) the answer might be that Instantiate() does just not create deep copies.
But still - what I think is weird about it is that a "mixture" works: The Components created with AddComponent still exist in the clone; also the script that created them (of course); just not the reference between those. That's imo confusing/unintuitive, especially because the manual says:
When you clone a GameObject or Component, all child objects and components will also be cloned with their properties set like those of the original object.
(https://docs.unity3d.com/ScriptReference/Object.Instantiate.html)
Answer by Kossuranta · Feb 20, 2017 at 03:07 PM
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;
}
}
Ah, now I see what you mean! Of course I forgot to set that... one Second...
I changed the line as you suggested, so now it's
myFooBehavior = gameObject.AddComponent<FooBehavior>();
but still in the clone, myFooBehavior is null on Awake and a second FooBehavior is added.
But where do you set myFooBehavior
s original value before instantiating? How do you expect it to get a non-null value to begin with?
Your inspector screenshot shows none of your scripts have variables that are assignable via inspector, so all fields you declare in your scripts will have their default values (null for objects)
If you just declare a variable FooBehavior myFooBehavior;
in the class, it won't automatically get a non-null value even if you have a component like that added to the object in the inspector.
You would have to make that variable public (or use [SerializeField]) and drag the component into that field in the inspector. Then you could expect it to have a value.
Well, I set the value before instantiating (the clone) on the original in the line
myFooBehavior = gameObject.AddComponent<FooBehavior>();
which is in Awake(). And that works, I can use the FooBehavior stored under myFooBehavior whenever I want with that "Clonable" instance. $$anonymous$$onoBehaviors can have non-serialized member variables of course.
But what happens is, when I clone the object via Instantiate(), the clone's value of its member "myFooBehavior" is appearantly null again - so it adds a second FooBehavior.
Please note: In the original question's code, I forgot to assign the result of AddComponent to myFooBehavior; fixed that after the hint of $$anonymous$$ossuranta but the result is still the same.
Your answer
Follow this Question
Related Questions
Why could I delete my clones onlY in the same order of instantiation? 2 Answers
Instantiated clones arent behaving the same as the prefab 1 Answer
Instantiating a GameObject from scene that changes 1 Answer
How do I make a clone of a prefab appear on the correct layer? [5.2.2f1] 1 Answer
Instantiate creating infinite clones 0 Answers