In the ideal system: your main classes don’t know anything about the UI. They provide all the hooks necessary for the UI to manipulate them, but if you nuke your entire UI code, you should not get any compiler errors. This way, you can replace the UI with other systems (a different UI for a console, keyboard/mouse, or touchscreen, a network-contrlled player maybe, or an AI-controlled character, to name a few).
So in this ideal system, it’s easy to see how, say, your UI can display your player’s health, or tell your player to jump: the UI does have a reference to PlayerObject, and can manipulate it as it sees fit. The remaining question is, what about when the PlayerObject needs to tell the UI something, like say you want the lifebar to flash because the player just got hit. Without PlayerObject having a reference to the UI, how do you send that message?
Delegates and events to the rescue! A delegate is basically a reference to a function itself, which can be invoked freely even if you don’t necessarily know what the function even is.
So basically you’ll have something like this:
public class PlayerObject : MonoBehaviour {
public event System.Action<float> ActionOnReceiveDamage;
....
void OnCollisionEnter(Collision c) {
if (blah blah have taken damage) {
ActionOnReceiveDamage?.Invoke(damageAmount);
}
}
To break this down:
- System.Action is a kind of builtin delegate. You can define your own delegates fairly easily, but I often find it helpful to just use System.Action most of the time. I usually only define my own delegate if I need the delegate to return something, which Action doesn’t support.
- The event keyword on it means that the action may have 0, 1, or more listeners. (Non-event actions may only have 1 listener, which sometimes is preferred, especially for delegates with return types.)
- The ?.Invoke syntax means that if the event is null it won’t cause an exception.
Now on the UI side, you’ll do something like:
public class PlayerUI : MonoBehaviour {
public PlayerObject myPlayer; //assign this however you like
void Awake() {
myPlayer.ActionOnReceiveDamage += RespondToPlayerDamage; //note: NO parameters/parentheses in this line
}
private void RespondToPlayerDamage(float dmg) {
Debug.Log($"We've received {dmg} damage.");
}
}
Notes on this one:
- The += on the line in Awake means the function is being added to the event’s listeners. You can use -= if you want to stop listening. If the delegate wasn’t an event, we’d just be using =
- The RespondToPlayerDamage function must exactly match the method signature (e.g. return and parameter types) of the delegate. Action is always void Something(foo).
I sort of threw a lot of info at you here, let me know if there’s too much or if I skipped something. There’s definitely more to learn about delegates that couldn’t fit onto a forum post.