Properly Registering to Delegates

Editing as this was originally poorly worded. … Open Mouth insert Foot…

There are some issues and concepts that I fell should be addressed.

  1. Using Start instead of Awake to get the Component (I think this is addressed in a latter video [Awake versus Start].
  2. Delegates should always be unregistered (Disposed of properly); typically registered in OnEnable() and unregistered in OnDisable() This important to ensuring that when the event fires it does not call methods that no longer exist in the game.

" All generalizations are false , including this one." - Mark Twain

There are often exceptions. Programming is very contextual. Take for instance:

If this is indeed a rule, then you’re screwed when you need a delegate to be subscribed/unsubscribed for any smaller length of time than the time an object is enabled. For example, when a pause button is enabled and then unsubscribe and then resubscribe when pause is disabled… or when a particular buff is occuring on a player/enemy.

While I hate to admit this one, there are hacky situations when you’re using start instead of awake to get around race conditions. Sometimes it’s easier than adding a lot of defensive code.

While we’re on context, you need to remember, these courses are here to teach beginner and early intermetiate level programmers. So while you’re right, there are often better ways to accomplish many tasks in the courses, they’re often bringing topics with slightly different applications to show different approaches…

Lets not forget, they’re also human. They do make mistakes. It’s not fun to have someone pick you apart, is it?

2 Likes

Yes you are correct there are exception to rules and these are general rules, so maybe not the world always. Should be Usually be registered in OnEnable() and unregistered in OnDisable()
But they should be disposed of properly (Unregistered at some point).

This is mentioned in some of the videos, even when they Talk about SEO.

Why I think it is important that should be mentioned that we need to unregistered them.

I have picked up some new tricks and thought about doing some things differently.
They have done a really good Job and I have Enjoyed the series, I have also enjoyed some of the discussions here, and interesting how others solved the same problem from the challenges, some totally differently, others generally the same.

Thank you for pointing out my error in wording, I am sorry if you took it as me picking it apart.

1 Like

There are circumstances where this is not absolutely necessary, usually when the subscriber is on the same GameObject as the script you’re subscribing to. In this instance, when the GameObject is destroyed, there is no real opportunity to get an error. As a failsafe, however, it never hurts to put the Unsubscribe in these instances in OnDestroy().

1 Like

You’re not wrong, I share your belief too, but I’m not in charge, so we follow their rules :wink:

It was just the absolute rule kind of wording gets my panties in the bunch. I’ve seen it get new programmers in more binds than not. Like the common, “always avoid singletons, they’re evil.”

It really is. Some people have me scratching my head wondering if I’ve missed something or they’ve gone deep down the rabbit hole… and other solutions have been so simple, they’re elegent.

1 Like

I think this is important to remember when talking about coding standards and practices. There is a difficult balance to maintain with beginners. You’d want a beginner to not learn the bad way of doing things, but it is so easy to make things complicated and overwhelming.

I have seen many really well written programs through the years that was a real treat to work with. But at the same time they confused so many beginner - and some intermediate - programmers simply because of how loosely coupled things were, etc.

So, while I totally agree that we need standards and practices, I feel that a beginner course may not necessarily be the place for it. Some beginners are still struggling with understanding the scope of a variable - for instance - and now we are throwing the scope of an event subscription into the mix.

Just my two-cents

2 Likes

On Destroy is a good place as well, It depends on where you are using them. In the example we are using them in the same game object, and also using them between the UI.

My favorite use of Events is for utilizing Game Events as Scriptable Objects. A real good example of this would be to use it in the Health Script to notify thing that something has died.

[CreateAssetMenu(fileName = "GameObjectFloatGameEvent", menuName = "RPG/GameObjectIntGameEvent")]
public class GameObjectIntGameEvent : ScriptableObject
{
    private event Action<GameObject, float> _event;

    public void RegisterListener(Action<GameObject, float> action)
    {
        _event += action;
    }

    public void UnregisterListener(Action<GameObject, float> action)
    {
        _event -= action;
    }
    
    public void Invoke(GameObject sender, float amount)
    {
        _event?.Invoke(sender, amount);
    }
}

We could use a string, or enum, or scriptable object that identifies the type of object what ever we want to use to describe it.

In Unity Create This Game Event called Character Died.
CharacterDiedGameEvent

Now in our Heath Script Instead of Creating an Award Exp Method we use the Game Event.
In Health.cs

        [SerializeField] private GameObjectFloatGameEvent characterDied;
        [SerializeField] private GameObjectFloatGameEvent onHealthChanged;
        
        private float GetAwardExp()
        {
            float expAmount = 0;
            BaseStats stats = GetComponent<BaseStats>();
            if (stats) expAmount = stats.GetStatValue(Stat.ExperienceReward);

            return expAmount;
        }

        private void Die()
        {
            if (IsDead) return;
            IsDead = true;
            _actionScheduler.CancelCurrentAction();

            if (_hasAnimator)  _animator.SetTrigger(_dieHash);

            if (characterDied) characterDied.Invoke(this.gameObject, GetAwardExp());
        }
        

In Experience

        [SerializeField] private GameObjectFloatGameEvent characterDied;
        [SerializeField] private GameObjectFloatGameEvent onExperincedChange;

        private void OnEnable()
        {
            if (characterDied) characterDied.RegisterListener(GainExperience);
        }

        private void OnDisable()
        {
            if (characterDied) characterDied.UnregisterListener(GainExperience);
        }

        private void GainExperience(GameObject sender, float amount)
        {
            if (sender == gameObject) return;
            GainExperience(amount);// This is the Public Gain Experience from the course
        }

        public void GainExperience(float amount)
        {
            value += amount;
            if (onExperincedChange) onExperincedChange.Invoke(this, value);
        }

Now later if I decide that I want a system that keeps track of the number of different Enemies the Player has killed I just write that system and use the character died game event, I can have another system that keeps track of how many times the player died. If this was a multiplier style game or we had a party system where the AI Player that killed Enemies we could add an extra parameter to the Game Event for the Instigator.
Doing it this way decouples the systems. Health knows nothing about Experience, It just knows it has stats and it Died. It doesn’t care who wants to know that it died or what other systems my need to be notified. The same Goes for the Experience, it knows nothing about any of the other systems just that it needs to change it’s value.

I have also used the same Game Event Type to help Decouple the UI from my other systems, I have one Health Display for both the enemy ui and the player ui, they register to the Health Changed Game Event, One UI Script that is the same for Player Health and Enemy Health, it just Reacts to the Game Event, if the sender is not correct then it ignores the Game Event, I also have a GameObjectStatFloatGameEvent that I use for when we level up so the things that need to know what the max value is can listen for that value being changed.

The Listeners need to unregistered them selves from the event if they are disabled/destroyed. Yes this is a slightly advanced event system, the system taught in the course is a little Simpler, but the UI that the course is using is dependent on these events, the base concept of handling listening to and not listening to the event is the same. I think Sam did mention something about a Possible Race Condition when discussing the Delegates and Events or in the Actions for leveling up video, I don’t remember. The race conditions/null reference might even by talked about in the data hazards or hunting down race conditions.

When and where you Register to listeners is important for

The problem is the way I was registering to to The Experience Changed Game Event in Base Stats.
See the Discussion Properly Registering to Delegates

Privacy & Terms