2

When cloning a button at runtime using Instantiate(), which contained listeners on it's onClick event, the listeners are not present in the clone.

The behaviour can be tested by having a Canvas with a button and this script attached:

void Start () {
    var button = transform.GetChild (0);
    button.GetComponent<Button> ().onClick.AddListener (new UnityAction(() => Debug.Log("Event triggered!")));
    var button2 = Instantiate (button);
    button2.SetParent (transform);
}

The cloned button will not print anything to the console when clicked. Is there a way to clone a GameObject so that it retains event listeners?

Felk
  • 7,720
  • 2
  • 35
  • 65
  • 1
    Hi Felk - this is correct and obvious behaviour. I can't see how it could work any other way. It says so right in `AddListener` doco. I guess, you could write an extension ([intro for any new programmers reading](http://stackoverflow.com/a/35629303/294884)) which instantiates and copies them across. – Fattie Jul 12 '16 at 12:34
  • Correct? Probably. Obvious? No. It does _not_ say so right in the documentation: `This function adds a "non persistent" delegate, which means it will not show up in the inspector, and will be forgotten when you exit play mode in the editor.` – Felk Jul 12 '16 at 12:42
  • 1
    fair enough, and your question is a great one as it will help many. I would just say, having (unluckily for me) used Unity a lot (rather than, say, laying on a beach) it's the "only obvious way it could work". I hope that makes sense. It would just be totally bizarre if something kept it 's connections when you instantiated it; I mean, it would just be an astoundingly strange feature in the overall system!! I can't even imagine working with that; I mean, before you did literally the simples thing (ie, "made any game object whatsoever"), you'd have to write a suite of code to... – Fattie Jul 12 '16 at 13:00
  • 1
    ...to negate that feature, you know! Heh! Again, it would be easy to add an extension with a line of code to do what you say in the particular case. (Also just purely as a curiosity, it's hard to see much use-case for that; another button with the same function.) – Fattie Jul 12 '16 at 13:01
  • My use case is simply a factory that produces some "original" widgets that get displayed in multiple locations (cloned), but should have their click behaviour be the same. – Felk Jul 12 '16 at 13:07

1 Answers1

3

Runtime listeners are not persistent and then not serialized. As a result they are not passed on when you clone the button.

Either you'd have to add the method to a script and attach the script to your prefab for it to be serialized along or assign it by code like you do for the first one.

  • Instantiation. When you call Instantiate() on either a prefab, or a gameobject that lives in the scene, or on anything else for that matter (everything that derives from UnityEngine.Object can be serialized), we serialize the object, then create a new object, and then we “deserialize” the data onto the new object. (We then run the same serialization code again in a different variant, where we use it to report which other UnityEngine.Object’s are being referenced. We then check for all referenced UnityEngine.Object’s if they are part of the data being Instantiated(). If the reference is pointing to something “external” (like a texture) we keep that reference as it is, if it is pointing to something “internal” (like a child gameobject), we patch the reference to the corresponding copy).

http://blogs.unity3d.com/2014/06/24/serialization-in-unity/

As for AddListener

  • This function adds a "non persistent" delegate, which means it will not show up in the inspector, and will be forgotten when you exit play mode in the editor. These differ from "persistent" listeners which you can add during edit-time in the inspector, and which persist between edit and play mode.

https://docs.unity3d.com/ScriptReference/Events.UnityEvent.AddListener.html

For those reasons, I assume the non-persistent is not serialized and then not passed to the cloned object.

Everts
  • 10,408
  • 2
  • 34
  • 45
  • Since the documentation page doesn't state it, does this imply that `Instantiate()` clones by Serializing and Deserializing, with listeners being transient? – Felk Jul 12 '16 at 11:28
  • Thanks. I got it to work by putting a custom script on the objects to be cloned, which adds the listener then in `Start()` – Felk Jul 12 '16 at 13:08