Alternative using Action insead of unityevent

Hi I think these have been some very good lectures and examples on using unity events, I do however think its a bit messy for the use case with what we’re using them for.

Here’s an alternative, in Health.cs make the following changes:

public event Action<float> OnTakeDamage;

In the method that is called for taking damage invoke the event by adding:

  if (OnTakeDamage != null)
            {
                OnTakeDamage(damage);
            }

Then add the following class onto the damagetext spawner that also spawns the canvas etc:

namespace RPG.UI
{
    public class DamageTextCanvasSpawner : MonoBehaviour
    {
        [SerializeField] GameObject _damageTextCanvas;

        Health _health;
        private void Awake()
        {
            _health = GetComponentInParent<Health>();
        }

        private void OnEnable()
        {
            _health.OnTakeDamage += SpawnDamageText;
        }
//Spawns the damage text canvas and sets the damage text value to the damage received.
        private void SpawnDamageText(float damage)
        {
           GameObject damageText = Instantiate(_damageTextCanvas, transform);
            damageText.GetComponentInChildren<DamageText>().SetDamageText(damage);
        }

        private void OnDisable()
        {
            _health.OnTakeDamage -= SpawnDamageText;
        }
    }
}

Then on the text component add the following:

namespace RPG.UI
{
    public class DamageText : MonoBehaviour
    {
        public void SetDamageText(float damageReceived)
        {
            GetComponent<TextMeshProUGUI>().text = damageReceived.ToString();
        }
    }
}

I’m using Text mesh pro change the component to an ordinary Text if thats’ what you’re using.

This event can also be used to update the player’s health display without having to be called every update which is better for performance, In the players healthdisplay class (whatever you’ve called it) add:

 private void UpdateHealthDisplay(float damageReceived)
        {
            string healthPercentage = String.Format("{0:0}/{1:0}", _playerHealth.CurrentHealthPoints,
            _playerHealth.GetMaxHealthPoints());
            _playerHealthText.text = healthPercentage;
        }

Please note my code displays the current health and max health rather than the percentage, also I think my variable names are different, they basically are just accessing the players current and max health.
Remember to always subscribe and unsubscribe onEnable and OnDisable with events:

 private void OnEnable()
        {
            _playerHealth.OnTakeDamage += UpdateHealthDisplay;
        }
   private void OnDisable()
        {
            _playerHealth.OnTakeDamage -= UpdateHealthDisplay;
        }

The same can be used for enemy health display but it’s a bit more complicated.

1 Like

For the HealthDisplay, this is absolutely the way to go. This is the classic observer pattern in action, and eliminates that aweful update.

For the DamageTextSpawner, the approach is fine, but it does set up a dependency between the Spawner and Health. While this may not seem like a big deal, imagine that the exact same piece of code could be used to show experience gained or even leveraged to display status messages (with the simple addition of a string method)… This is the power of using the UnityEvent, it allows us to be completely independent of the sending message class, and that is the real power of the observer method.

Can you please elaborate on how what I’ve done has created an extra dependency in comparison if I were to do the same using a unity event, Because I’m unaware of it.

I thought a unity event would work very similar except for not having to instantiate an instance of the class to be able to subscribe to the event? Unlike with a unity event you don’t have to have an instance to subscribe to the event.

Other than that wouldn’t it be exactly the same in the code?

If you use a Unity Event, you can hook up the call from OnDeath to the inspector to the Spawner. The UnityEvent acts as the wrapper, and the Spawner never needs to know that it’s hooked to a Health Component, only that it has been told to spawn a DamageText with the float called in the method. In this situation, there’s no need for

using RPG.Health;

in the DamageText Spawner. The method doesn’t need to know about the Health at all, because it’s hooked up in the inspector.

1 Like

So from my understanding, would you say in unity programming using unity events are generally better for adhering to the open-closed principle in the SOLID principles compared to action and custom delegates?

And if so is there a use case where using an event-like action or custom delegate is a better alternative?

That is correct

Not every situation lends itself to a Unity Event assignable in the inspector. While it’s easy enough to pass the damage through the Spawner to the Prefab, if that Prefab needed to respond to further events on the Health component, it would need to subscribe directly to that event. Then the event would be likely be a better choice.

In Shops and Abilities, we’re actually going to pass custom delegates around in the method calls for abilities, so that I might call a Targetting routine with a data structure and a callback method that I want called when the targetting routine finishes. This form of delegate passing is very common in asynchronous operations… rather than waiting for a blocking routine to finish, you just pass in the callback for the asynch operation to call when the routine is finished.

1 Like

Thank you I have found your response very useful, I went to change my code to use unity events instead but then found I couldn’t update the healthbars and the players health text etc(which is why I think I went to use events in the first place but I can’t remember). This is because I couldn’t access the objects with the scripts necessary while editing the prefab for the player or enemies.

Ill bare in mind for the future though that if I can access the object that it’s better to use a unity event.

How to subscribe to a UnityEvent in Code:

void OnEnable()
{
    _health.OnTakeDamage.AddListener(UpdateHealthDisplay);
}
void OnDisable()
{
    _health.OntakeDamage.RemoveListener(UpdateHealthDisplay);
}
1 Like