Since I generally tend to avoid updates where the code doesn’t have to run in every frame, I’ve made a few optimizations by using an observer pattern instead, and would like to get feedback on whether it’s better this way or kind of redundant:
This is the Mana.cs:
using System;
using UnityEngine;
namespace RPG.Attributes
{
public class Mana : MonoBehaviour
{
[SerializeField] float maxMana = 200;
[SerializeField] float manaRegenRate = 1;
IManaObserver observer = null;
public Action<float, float> onManaUse;
float currentMana = 0;
void Awake()
{
currentMana = maxMana;
}
void Update()
{
if (Mathf.Approximately(currentMana, maxMana)) return;
RegenerateMana();
}
public float MaxMana { get => maxMana; }
public float CurrentMana { get => currentMana; }
public IManaObserver Observer { set { observer = value; } }
public bool UseMana(float mana)
{
if (currentMana - mana < 0)
{
return false;
}
currentMana -= mana;
onManaUse?.Invoke(currentMana, maxMana);
return true;
}
void RegenerateMana()
{
if (currentMana < maxMana)
{
currentMana += manaRegenRate * Time.deltaTime;
NotifyObserver();
if (currentMana > maxMana)
{
currentMana = maxMana;
}
}
}
void NotifyObserver()
{
observer.OnManaChanged(currentMana, maxMana);
}
}
}
This is my ManaDisplay.cs, which is responsible for rendering the amount of mana the player has left. All changes in the Mana.cs are updated accordingly via a call-back:
using RPG.Attributes;
using TMPro;
using UnityEngine;
namespace RPG.Abilities.UI
{
public class ManaDisplay : MonoBehaviour, IManaObserver
{
TextMeshProUGUI manaText;
Mana mana;
void Awake()
{
manaText = GetComponent<TextMeshProUGUI>();
mana = GameObject.FindWithTag("Player").GetComponent<Mana>();
mana.Observer = this;
}
void Start()
{
OnManaChanged(mana.CurrentMana, mana.MaxMana);
}
void OnEnable()
{
if (mana != null)
{
mana.onManaUse += OnManaChanged;
}
}
void OnDisable()
{
if (mana != null)
{
mana.onManaUse -= OnManaChanged;
}
}
public void OnManaChanged(float currentMana, float maxMana)
{
manaText.text = $"Mana: {Mathf.FloorToInt(currentMana)} / {maxMana}";
}
}
}
And finally the interface:
namespace RPG.Attributes
{
public interface IManaObserver
{
public void OnManaChanged(float currentMana, float maxMana);
}
}
The reason I’m still using Update() in Mana.cs is because of the way we use it in the Ability.cs. We do the check twice if we have enough Mana to perform the operation, first when overriding the Use() method and when we call TargetAcquired() when we lose mana while selecting the targets somehow or for whatever reason, which is still unclear to me.