Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 12 Next capture
2021 2022 2023
1 capture
12 Jun 22 - 12 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
1
Question by jwulf · Feb 20, 2017 at 01:07 PM · c#instantiatecloneinstantiationreferences

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:

alt text

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!

cloningtest.jpg (111.9 kB)
Comment
Add comment · Show 2
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image ThisGuyThatsHere · Feb 20, 2017 at 04:52 PM 0
Share

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.

avatar image jwulf ThisGuyThatsHere · Feb 20, 2017 at 07:47 PM 0
Share

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.

3 Replies

· Add your reply
  • Sort: 
avatar image
1
Best Answer

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.

Comment
Add comment · Show 4 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Bunny83 · Feb 20, 2017 at 07:49 PM 0
Share

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

avatar image jwulf Bunny83 · Feb 20, 2017 at 09:16 PM 0
Share

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.

avatar image UnityCoach · Feb 20, 2017 at 07:49 PM 0
Share

Of course! Nicely done!

avatar image jwulf · Feb 20, 2017 at 09:09 PM 0
Share

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.

avatar image
2

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.

Comment
Add comment · Show 6 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image jwulf · Feb 20, 2017 at 02:45 PM 0
Share

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.)

avatar image UnityCoach jwulf · Feb 20, 2017 at 07:17 PM 0
Share

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.

avatar image UnityCoach jwulf · Feb 20, 2017 at 07:18 PM 0
Share

I agree with you though, it's weird that instantiating an object doesn't behave like to you copy/paste it.

avatar image jwulf UnityCoach · Feb 20, 2017 at 07:35 PM 0
Share

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)

Show more comments
Show more comments
avatar image
0

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;
     }
 }

Comment
Add comment · Show 6 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image jwulf · Feb 20, 2017 at 04:06 PM 0
Share

Ah, now I see what you mean! Of course I forgot to set that... one Second...

avatar image jwulf · Feb 20, 2017 at 04:08 PM 0
Share

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.

avatar image NoseKills jwulf · Feb 20, 2017 at 04:49 PM 0
Share

But where do you set myFooBehaviors 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.

avatar image jwulf NoseKills · Feb 20, 2017 at 04:59 PM 0
Share

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.

Show more comments
Show more comments

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

291 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

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


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges