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

AddEventListener in Awake or OnEnable for Monobehaviours

Discussion in 'Scripting' started by eco_bach, Jan 30, 2018.

  1. eco_bach

    eco_bach

    Joined:
    Jul 8, 2013
    Posts:
    1,601
    Can any Unity C# experts share their recommended practice for adding (and removing ) event listeners, whether built in or custom?

    Do you always add new event listeners in the Awake method or in OnEnable?

    Does it matter?

    And do you also always detach your event listeners in OnDestroy and OnDisable respectively?
     
  2. mahdiii

    mahdiii

    Joined:
    Oct 30, 2014
    Posts:
    855
    it is highly recommended to use OnEnable and OnDisable to Add and Remove listeners
    So you are sure when your gameobject is activated then listeners are added and when it becomes disable, they are removed
    But however it is not always necessary
     
  3. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    I seldom if ever use Awake, if and only if, as the documentation states:
    https://docs.unity3d.com/ScriptReference/MonoBehaviour.Awake.html

    Adding/removing event listeners is a form of passing information (you're passing one object to the other to be registered).

    Note that doesn't just leave OnEnable though. There is also 'Start'.

    So you use 'OnEnable' and 'OnDisable' to add/remove if you expect the listener to only work while the script is enabled/active.

    But can use 'Start' and 'OnDestroy' for if you want the listener to work even if the script is not enabled. You know, in case you want to enable the script on some event. Of course an argument could be made that the order of responsibility is reversed by doing it this way, and rather some other object should be doing the enabling... but that's an argument I'm not here to have. If you want to do it in this order. It's a legitimate way of doing it.

    Thing is that Start is only called in this case if the script was enabled/active from the get go. Which is where the whole order of responsibility argument might come in.

    In the end, OnEnable and OnDisable are probably the most effective common place to do this sort of behaviour. If you find yourself doing it another way (in awake, start, ondestroy).... ask yourself 'why' you're doing it there. If you don't have a good reason, you probably should be putting them back on OnEnable and OnDisable.
     
  4. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,384
    Depends on how you want it to behave. If you want calls to it when it's not enabled then unsubscribing in OnDisable is not what you want to do. If you only want it to respond when it's enabled then using OnEnable/Disable to subscribe/unsub will get you what you want.

    Using Awake() is common practice to setup references between objects. After everything has an Awake() pass then you can communicate in Start() or Update() or whatever. Generally you don't use Awake() to communicate unless you're controlling the initialization process pretty explicitly. Null refs due to script sequence can sneak in if you try to use something before it's ready.

    In general they're pretty straightforward. Sub/unsub when you know it's safe and when you want to have/not have the calls.
     
  5. eco_bach

    eco_bach

    Joined:
    Jul 8, 2013
    Posts:
    1,601
    Thanks. Great info. So my takeaway is that in general DO NOT use Awake for setting up listeners, and in general a best practice is to use OnEnable, OnDisable.

    But then the obvious question is what SHOULD be placed in Awake?
     
    Last edited: Jan 31, 2018
  6. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,196
    Setting up stuff that's not reliant on any other scripts having done anything. So calling GetComponent to get stuff locally, setting up non-serialized fields, etc.

    Not only is it safe to do those things in Awake, if you do it, then you can use the values you set up in Awake from other scripts.


    Note that Awake runs immediately when an object is generated - like when you Instantiate a prefab with the script on it, or call AddComponent. So here's an example where you have to use Awake to have things work:

    Code (csharp):
    1. public class Projectile: MonoBehaviour {
    2.     private Rigidbody rb;
    3.  
    4.     private void Awake() {
    5.         rb = GetComponent<Rigidbody>();
    6.     }
    7.  
    8.     public void Fire(Vector3 velocity) {
    9.         rb.velocity = velocity;
    10.     }
    11. }
    12.  
    13. //some other place:
    14. private GameObject rocketPrefab;
    15.  
    16. void Fire() {
    17.     var rocket = Instantiate(rocketPrefab, transform.position + transform.forward, transform.rotation);
    18.     //Projectile.Awake has already run, Projectile.Start has not.
    19.     rocket.GetComponent<Projectile>().Fire(transform.forward);
    20. }
    If you replace Awake with Start in the example above, trying to fire the projectile would nullref, as it wouldn't have cached it's Rigidbody yet. Start runs at the start of the next frame.
     
  7. guneyozsan

    guneyozsan

    Joined:
    Feb 1, 2012
    Posts:
    99
    My setup is like this:

    Call method even if object is disabled and never enabled before:
    (I use it mostly to enable some object when something happens without having a reference to it. Excellent for UI. For example pause menu.)
    Subscribe at Awake
    Unsubscribe at OnDestroy

    Call method even if object is disabled, but only after object is enabled at least once:
    This is a very special case and I use it very rarely.
    Subscribe at Start
    Unsubscribe at OnDestroy

    Call method only if object is enabled:
    (This synergises better with game logic. Usually you don't need to fire these methods when objects are disabled.)
    Subscribe at OnEnable
    Unsubscribe at OnDisable

    Very late edit (2023 lol): Be careful that, OnDestroy() is never called if the object is first disabled and then destroyed. In this case, you will have memory leaks.
     
    Last edited: Feb 16, 2023
    wangsitan and brenowca like this.
  8. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    Why don't you just stay flexible and use

    Awake or Start => Subscribe
    OnDestroy => Unsubscribe

    And let the subscriving method decide whether to process the event, ignore the event, or do partial processing?

    Code (csharp):
    1.  
    2. private void OnEventRaised(<sender>, <eventArgs>)
    3. {
    4.     if (enabled)
    5.     {
    6.          // only do this when enabled, no matter if the object is currently active
    7.     }
    8.  
    9.     if (isActiveAndEnabled)
    10.     {
    11.         // only do this when enabled and when the object is active
    12.     }
    13.  
    14.     if (gameObject.isActive)
    15.     {
    16.  
    17.     }
    18.  
    19.     if ( /* whatever you like ... */ )
    20.     {
    21.         ...
    22.     }
    23.  
    24.     // always do this
    25. }
    26.  
     
    Last edited: Jul 21, 2018
    Munchy2007 likes this.
  9. mahdiii

    mahdiii

    Joined:
    Oct 30, 2014
    Posts:
    855
    It leads a lot of problems, less readability.
    Why do you like to call a function (or trigger event) when a gameobject is inactive?
    It is not standard that we call (trigger) something when a gameobject is inactive
    I always use OnEnable and OnDisable and it is OK.
    Also you can add listeners when you really need and remove them when you don't.
    It causes better performance as well because it invokes only listeners that really listen
     
    phobos2077 likes this.
  10. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    A lot of problems? Could you elaborate?

    Readability: Well, if you don't need all of the cases, you don't need to bother having a branch for them. However, if you need to process multiple things based on these conditions, you need multiple listeners that subscribe in different places. If they depend on each other in some way, then you're surely going to run into inconsistent behaviour.

    There are reasons. For instance, hiding an object (or parts of it) by deactivating one of the objects further up in the hierarchy instead of going through all the renderers and disabling them.
    Common use case, unless you move the object to some place where it cannot be seen.

    Using Behaviour.enabled, Behaviour.isActiveAndEnabled, GameObject.activeSelf, GameObject.activeInHierarchy as a "filter", you're free to handle the event in so many different ways and combinations. It's also more obvious, given the facts that the OnEnable and OnDisable methods run in various circumstances.

    For instance, caling SetActive on a GameObject triggers OnDisable on its components and on the components in its hierarchy, but the components itself will stay enabled, but won't be activeAndEnabled.
    You won't be able to handle the event anymore, as it'll unsubscribe.


    If that enough for your use cases, it's totally fine.

    Well, let's take a look:
    - OnEnable / OnDisable will always be called when Behaviour.enabled is set (given that the value differs from current state)
    - OnEnable / OnDisable will always be called when GameObject.SetActive(<value>) is called, no matter if that's the object of the script instance itself, or somewhere up in the hierarchy (also given that the supplied value changes the state)

    This being said, now consider how delegates actually work. They need to maintain an invocation list, no matter whether they use Arrays, Lists or LinkedLists. Additionally, they're supposed to be immutable, that is the list always needs to be recreated in some way. Depending on the implementation you're dealing with, it may allocate that new structure, add an entry (which is fast) or remove an entry (somewhere from index 0 - Count-1) if it even existed. If it existed, remove the entry and ensure the gap is removed.

    On top of that, the syntax sugar for subscription does actually hide a lot of the stuff that needs to be don, including reflection.

    If you want to get an rough idea what's happening when you subscribe / unsubscribe with all the things mentioned above (immutability etc.), try to implement a simple version of it using the interface-based listener pattern.

    Now that all depends on how often you subscribe/unsubscribe. I still see the flexibility (which does really not harm readability) as the main argument for my way of handling subscribing/unsubscribing.
     
    Last edited: Jul 22, 2018
    Munchy2007 likes this.
  11. mahdiii

    mahdiii

    Joined:
    Oct 30, 2014
    Posts:
    855
    Unity gives a lot of ability to you but it does not mean you need all of them.
    Discipline and some simple rules are not bad
    When you use this approach (call a function (trigger an event) when a gameobject is inactive) it leads spagetti codes and creates confusion. You should care more. need more debug because you add extra problem (events can be invoked even when your gameobjects are inactive)


    When gameobjects are inactive, I must suppose they are not active else they were active.
    If you need to listen events, why do you deactivate them?
    In some situations, you can assign codes to empty gameobjects, if they are not dependent on other real gameobjects.
    You can deactivate components like renderer, why do you need to deactivate completely and still listen to events?

    Can you indicate an example such that you really need to call a function (trigger) when your gameobject is inactive and can not achieve it with other approaches?

    Much flexibility can harm readability and debugging in my opinion.
    This is one reason that frameworks have been created and introduced.
     
    Last edited: May 3, 2019
    phobos2077 likes this.
  12. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    Do you want to say it's less disciplined to be in better control?

    Why does it lead to spaghetti code? The snippet I've shown above is not deeply branched, and as mentioned, most of the time you won't need all of them. For your case, the only thing that's interesting would be one additional statement.
    It also shows the clear intent, do something whenever the instance is enabled... or do something whenever the instance is enabled and its go is active as well.

    This is actually pretty explicit. Doesn't even require much more in-depth knowledge about when an object gets OnEbaled, OnDisabled

    The first statement seems to represent one of your own rules, it is nowhere mentioned that they must be completely inactive, i.e. not listen to events and such. It can be useful, even though it may not seem to be useful from your perspective.

    And if you implement such event handlers, that act on enabled objects when GOs are inactive, well, there will be a reason for it.
    As for the question, I've already given an example in my previous post.
     
  13. mahdiii

    mahdiii

    Joined:
    Oct 30, 2014
    Posts:
    855
    It is completely obvious. When your gameobjects are inactive but can be fired, it surely adds extra complexity.
    However I only use OnEnable OnDisable without any conditions and never ever face any problems
     
    phobos2077 likes this.
  14. Hakazaba

    Hakazaba

    Joined:
    Jul 1, 2015
    Posts:
    119
    Hi i have a strange situation and need some advice on this. Im using an asset which sends out an event on its OnEnable. And i need to have a separate component subscribe to that event before its actually fired. It seems that even if the component is on the same gameobject, theres still a chance that it doesn't subscribe before the event is fired when using Awake
     
  15. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
  16. WizardGameDev

    WizardGameDev

    Joined:
    Jul 25, 2012
    Posts:
    62
    If you need to wake an object up as part of the handler then you definitely are better off with awake than with onenable. We need to manage objects that are not active just the same as we need to manage them if they are active. If an object is inactive for whatever reason it is like you lose the handler to it if you use onenable. Going with Awake you still get your event references and callbacks and can then use them to activate game objects. I would agree there could be code smell if you are using a pattern that increases coupling but part of the beauty of having it in awake vs onenable is you have less volatility by design. If another object turns off a gameobject (which there is literally zero code protection, compiler protection or unity protection for then the event stops happening. I know this feeling and it is hard to debug something that isn't happening. Looking for why an event isn't firing is one of the most frustrating debugging efforts. I would rather always have the event consistently firing and deal with that with clear clean code than having my events effected by the status of active vs inactive which for gameobjects means you often still need to manage them even if they are not active.

    So that is my 2 cents for awake. Onenable has been stressing me out.