Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Event-Subsription not working

Discussion in 'Scripting' started by Piflik, Mar 3, 2016.

  1. Piflik

    Piflik

    Joined:
    Sep 11, 2011
    Posts:
    289
    I have a curious problems with UnityEvents. When I instantiate a Prefab which has a Behaviour with a UnityEvent atached, I seem to be unable to subscribe to this Event (I also tried System.Action, with the same result).

    Code (CSharp):
    1.  
    2. UnityEvent onOpened = new UnityEvent();
    3. void Start() {
    4.     onOpened.AddListener(() => Debug.Log("This is not"));
    5. }
    6.  
    7. public void BoxOpenCallback() {
    8.     Debug.Log("This is printed in the console");
    9.     onOpened.Invoke();
    10. }
    (Note: the subscription happens here in Start only to test. I actually want to subscribe from a different script)

    Nearly identical code works on a different object that is not instantiated on runtime, but was placed by hand using the Editor.

    Is there something I am missing, or is that a Bug?

    Edit: As a workaround I made the event static, since there will always only a single object, which is firing its event at any given time, but if that is a bug/unintended behaviour, then this should be addressed.
     
    Last edited: Mar 3, 2016
  2. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    When and how do you call the callback? How is it all set up?
     
  3. SneeKeeFahk

    SneeKeeFahk

    Joined:
    Jan 25, 2015
    Posts:
    26
    I don't know about the UnityEvent object however in standard .NET this is how you would accomplish this:

    i cant post code from behind out proxy at work but heres a DotNetFiddle illustrating event handling
    https://dotnetfiddle.net/h1R2YV
     
  4. Piflik

    Piflik

    Joined:
    Sep 11, 2011
    Posts:
    289
    The callback is called from a StateMachineBehaviour in OnStateExit. It definitely runs after I try to subscribe, because the function in which I call AddListener actually triggers the entry into that animator state (also I used Debug.Log() to verify the order of operations). And as I said, it does work when I make the onOpened event static, which is an acceptable workaround in this case, but will not always be applicable.

    I also tried standard C# events, delegates and named methods instead of Lambdas, but they had the same behaviour: They work if they are static or on objects already present in the scene, but not on freshly instantiated prefabs.
     
  5. SneeKeeFahk

    SneeKeeFahk

    Joined:
    Jan 25, 2015
    Posts:
    26
    Can you post a code sample of where you are instantiating the prefab and wiring the event?
     
  6. Piflik

    Piflik

    Joined:
    Sep 11, 2011
    Posts:
    289
    The whole code is spread out over three classes, the first of which is the one in my first post, which contains the event.

    This is where the objects are created (SetupPacks.cs)
    Code (CSharp):
    1.  
    2. void Start () {
    3.     foreach (LevelPack levelPack in LevelPacks)) {
    4.         Box box = (Instantiate(_boxPrefab, transform.position, Quaternion.identity) as GameObject).GetComponent<Box>();
    5.         //some setup
    6.         //....
    7.         LevelPack packCapture = levelPack;
    8.         box.GetComponent<Button>().onClick.AddListener(() => _levelSelection.OpenPackage(packCapture, box));
    9.     }
    10. }
    And here I wire the event (LevelSelect.cs)
    Code (CSharp):
    1.  
    2. public void OpenPackage(LevelPack pack, Box box) {
    3.     //some setup and other function calls and events
    4.     //...  
    5.     box.onOpened.RemoveAllListeners();
    6.     box.onOpened.AddListener(() => _cardDeck.ScatterCards());
    7.  
    8.     box.Open();
    9. }
    The only thing I changed in this code to make it work, was writing 'static' in front of the event and capitalizing the 'box' in the last code snippet. Only the way the event is invoked was changed (it is even easier, because it does not require the callback in the Box class, but that was not the problem, since the Debug.Log in that callback was printed to the console).

    Other events (on objects already present in the scene), that I create/wire exactly the same, in this function, work as expected.
     
  7. SneeKeeFahk

    SneeKeeFahk

    Joined:
    Jan 25, 2015
    Posts:
    26
    OK well one thing that *might* be a problem is the variables going out of scope and garbage collection is destroying the objects. Maybe create a simple structure that has a Box and LevelPack property, add them to a list defined outside of start then wire the event.

    Something like this;
    Code (CSharp):
    1.     struct BoxAndPack{
    2.         public Box box;
    3.         public LevelPack levelPack;
    4.         public PackCapture packCapture;
    5.         public void click(){
    6.             _levelSelection.OpenPackage(packCapture, box);
    7.         }
    8.     }
    9.  
    10.     List<BoxAndPack> boxesAndPacks = new List<BoxAndPack>();
    11.  
    12.     void Start(){
    13.         foreach(LevelPack levelPack in LevelPacks){
    14.             var temp = new BoxAndPack(){
    15.                 box = (Instantiate(_boxPrefab, transform.position, Quaternion.identity) as GameObject).GetComponent<Box>(),
    16.                 levelPack = levelPack,
    17.                 packCapture = packCapture
    18.             };
    19.          
    20.             temp.box.OnClick.AddListener(() => temp.click());
    21.             boxesAndPacks.add(temp);
    22.         }
    23.     }
    I'm not exactly sure if thats your problem but it would be easy enough to test.
     
  8. Piflik

    Piflik

    Joined:
    Sep 11, 2011
    Posts:
    289
    Both the pack and the box are valid objects. I use their properties in the codesegments I removed from the examples above, since these lines were not relevant to the issue at hand. The local references to these objects might be GCed, but the objects themselves are not (the box is a component in the scene, the levelPacks are already stored in a static array in a different class). Also, the minimal example in my first post already doesn't work, without any variables that can go out of scope.
     
    Last edited: Mar 4, 2016
  9. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    Nvm, forget about that. I'll come back to it later.

    I've been trying to reproduce it but it appears to be different. Do you mind to create a sample that's ready to test and doesn't work for you?
     
    Last edited: Mar 5, 2016
  10. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    I still remain with the idea that I had deleted yesterday. The only time it doesn't work for me is when I immediately raise the event after I instantiated the object. And that's logical, because the initialization via Awake and Start will be run in the next frame so the subscriber added in Start won't actually exist for the event at this time.

    What happens if you invoke it in Start after the subscription ? Does it log the text to the console?
     
  11. Piflik

    Piflik

    Joined:
    Sep 11, 2011
    Posts:
    289
    I tried to reproduce it in a minimal scene and was unable to. Apparently it has something to do with the exact way I am doing it in the real project. I'll have to dive a bit deeper into it an monday when I am back at work.
     
  12. Piflik

    Piflik

    Joined:
    Sep 11, 2011
    Posts:
    289
    I managed to find the error of my ways. It did not lie with the way the event was created or wired, but how it was invoked. Apparently I managed to call the event not on the instanced object, but on the Prefab itself, and obviously neither was Start ever called on the not-instantiated Prefab, nor did anything ever subsribe to it.

    Root-cause was a misunderstanding of StateMachineBehaviours. Apparently these are inherently 'static' (not really, but that's the closest analogy I found; they are not instantiated) and can only reference Prefabs directly. If I use the animator-component, that is handed to the OnStateExit method, I can reference the actual gameobject and call the correct event.
     
    Suddoha likes this.