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. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

A question about event handling and non-static events

Discussion in 'Scripting' started by Cirrocumulus, Aug 13, 2017.

  1. Cirrocumulus

    Cirrocumulus

    Joined:
    Apr 9, 2017
    Posts:
    28
    Hello,

    I think I've finally grasped the basic idea behind delegates and events and I'm trying to introduce this mechanism to a test project I'm working on.

    I have a Whatever prefab with a script which disables its own GameObject after a certain random period of time. I also have a Manager class (on an empty GameObject) which instantiates several Whatever prefabs at runtime and is notified of their demise via a standard method call (the prefab has a link to the Manager object through a public variable in the Editor). I tried to do that with events and delegate and was wondering if I was doing it right. Most tutorials I read and watched use a static event, so that's what I tried first.

    Inside the Whatever class, I have this (outside of Start or anything else, it's at the top with the variable declarations):

    Code (CSharp):
    1. public delegate void WhateverEventHandler(GameObject sender);
    2. public static event WhateverEventHandler BugDeath;
    Then, in the same class, when the prefab dies, I have this:

    Code (CSharp):
    1. if (WhateverDeath != null)
    2.         {
    3.             WhateverDeath (this.gameObject);
    4.         }
    In the Manager class, immediately after instantiating each prefab, I subscribe to the event with this:

    Code (CSharp):
    1. Whatever.WhateverDeath += DeathHandle;
    And a simple debugging method:

    Code (CSharp):
    1. void DeathHandle(GameObject obj)
    2.     {
    3.         Debug.Log (obj.name + " had died");
    4.     }
    Problem is each time a specific instance of the prefab dies, the DeathHandle() method gets called for each and every prefab instance in the scene. I presume this is because the event is being declared as static but I'm not sure I understand why this is happening.

    Then I tried to make the event non-static by removing the static keyword and, in the Manager class, instead of referring to "Whatever.WhateverDeath" I used the event in each instance of the prefab, right after it's been instantiated, like so (simplified, the whole thing is in a for loop which creates an object pool):

    Code (CSharp):
    1. GameObject obj = Instantiate (WhateverPrefab, Vector3.zero, Quaternion.identity);
    2. Whatever bc = obj.GetComponent<Whatever> ();
    3. bc.WhateverDeath += DeathHandle;
    This works, but I don't understand exactly why. If I'm supposed to use events and delegates as an application-wide event system, should I somehow address the class directly, or is it normal to have a separate event for each instance of the class (which I think is what happening in the last example)?

    Thanks a lot.
     
  2. Rotary-Heart

    Rotary-Heart

    Joined:
    Dec 18, 2012
    Posts:
    804
    If you don't want to receive any more the event on those GameObjects you need to unsubscribe from it using WhateverDeath -= DeathHandle. You keep getting the call since all your dead GameObjects are still subscribed to the event.

    Think of it like a news teller email as long as you are subscribed you will receive the news emails. They don't care if you no longer are using that email, they will keep sending it to you. If you want to stop receiving emails you need to unsubscribe from it.
     
  3. Cirrocumulus

    Cirrocumulus

    Joined:
    Apr 9, 2017
    Posts:
    28
    I forgot, indeed, to unsubscribe from the handler when an object is disabled (big mistake), but this still doesn't explain why, when the event is static, I get as many events as there are objects even on the first object death. Or is the non-static version (with a subscription through GetComponent) the correct way to do this?
     
  4. Rotary-Heart

    Rotary-Heart

    Joined:
    Dec 18, 2012
    Posts:
    804
    Alright so what you are saying is that when it dies the manager receives the event multiple times from all the instantiated ones? So your output is like

    "Whatever1 had died"
    "Whatever2 had died"
    "Whatever3 had died"
    "Whatever4 had died"
    "Whatever5 had died"

    When only Whatever1 died?
     
  5. Cirrocumulus

    Cirrocumulus

    Joined:
    Apr 9, 2017
    Posts:
    28
    No, if there are 5 instances in the scene and Whatever 2 has died, I get:

    Whatever 2 has died
    Whatever 2 has died
    Whatever 2 has died
    Whatever 2 has died
    Whatever 2 has died

    (only when using the static version of the event)
     
  6. Rotary-Heart

    Rotary-Heart

    Joined:
    Dec 18, 2012
    Posts:
    804
    Can you paste your code of how you are checking when it's dead and sending the invoke?
     
  7. Cirrocumulus

    Cirrocumulus

    Joined:
    Apr 9, 2017
    Posts:
    28
    Sure. In Update() I have:
    Code (CSharp):
    1. if (Time.time > lifespan)
    2. {
    3.     Die ();
    4. }
    And:

    Code (CSharp):
    1.     void Die()
    2.     {
    3.         gameObject.SetActive (false);
    4.         CountDeaths++; // simple static counter
    5.         if (WhateverDeath != null)
    6.         {
    7.             WhateverDeath (this.gameObject);
    8.         }
    9.     }
     
  8. Rotary-Heart

    Rotary-Heart

    Joined:
    Dec 18, 2012
    Posts:
    804
    Alright what I'm pretty sure is happening is that since it's a static delegate you are subscribing multiple times to the same (int this case 5) so when you call it from your dead whatever it detects that there is 5 subscribers and sends it to all of them (which in this case is the same class).

    If you want to keep the static approach you can unsubscribe before subscribing to it

    WhateverDeath -= DeathHandle
    WhateverDeath += DeathHandle

    This way you are sure you are only subscribed once. Or change it to the non static you mentioned.
     
  9. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    This isn't really a event problem. Its a misunderstanding of what static means. Static means that a member is attached to the class, and not to any instance. That means you only ever have one version of a static member.

    So when you make Whatever.WhateverDeath static, that means there is only one event. Every single instance of Whatever shares this same event. So when you did this:

    You've actually subscribed to the same event five times.

    If you are going to use a static event, just subscribe once in Awake. No need to subscribe for every instance.
     
  10. dpgdemos

    dpgdemos

    Joined:
    Apr 28, 2014
    Posts:
    24
    @Rotary-Heart and @Kiwasi are correct. In addition, it is common practice in Unity to subscribe and unsubscribe events in OnEnable and OnDisable, respectively.
     
    Kiwasi likes this.
  11. Cirrocumulus

    Cirrocumulus

    Joined:
    Apr 9, 2017
    Posts:
    28
    Actually the last @Rotary-Heart saw the problem the other way around (my single Manager is subscribing to the Whatever class of which there are many and therefore doesn't need, I think, to unsubscribe when the Whatever objects die). But it did put me on the right track. I slept on it and realised that I was subscribing to the static event *inside* the for loop which was creating the pool. I moved the "+=" statement outside the main loop and now Manager is subscribing to the static event only once. What's nice is that since the delegate method has "this.gameObject" as an argument, it allows the static event to notify which instance was sending the message.

    I came back here to explain my original mistake and saw that @Kiwasi figured it out a little bit earlier that I did...

    Thanks a lot!

    EDIT: BTW, if anyone ever stumbles upon this thread, you've probably read it everywhere already but the benefits of delegates and events are enormous and reduce greatly interdependencies between your classes. I spent 48 hours trying to understand this concept until it clicked in. My Whatever class has now lost its last reference to the Manager class (no GetComponent, no Inspector, no FindObjectWithName, no nothing). It does what it has to do and doesn't care about anyone else :)
     
    Last edited: Aug 14, 2017
    RemDust, dpgdemos and Kiwasi like this.