C# Events in Unity
Part of the Game Architecture in Unity section of Coddy's Unity journey. Lesson 12 of 64.
When the score changes, several things care: the score text, a sound, an achievement. If the ScoreKeeper calls each of them, it needs a field for every one, and each new feature means editing it. An event turns that around: the ScoreKeeper announces the change, and whoever cares subscribes.
Two C# words make this work. Action<int> is the type of a method that takes one int and returns nothing. An event of that type is a list of such methods: other code adds its method with +=, and the owner calls them all with Invoke. In plain C#:
using System;
class Alarm
{
public event Action<int> Rang;
public void Ring(int hour)
{
Rang?.Invoke(hour);
}
}
class Sleeper
{
public void WakeUp(int hour)
{
Console.WriteLine("Up at " + hour);
}
}
// elsewhere
Alarm alarm = new Alarm();
Sleeper sleeper = new Sleeper();
alarm.Rang += sleeper.WakeUp;
alarm.Ring(7); // Up at 7In Unity the publisher and its listeners are components, usually on different GameObjects:
using System;
using UnityEngine;
public class ScoreKeeper : MonoBehaviour
{
public event Action<int> OnScoreChanged;
private int score;
public void AddPoints(int points)
{
score += points;
OnScoreChanged?.Invoke(score);
}
}// ScoreLabel.cs, on the HUD's text
public ScoreKeeper keeper;
public TMP_Text label;
void Start()
{
keeper.OnScoreChanged += ShowScore;
}
void ShowScore(int score)
{
label.text = "Score: " + score;
}Action<int> means each listener is a method that takes one int, here the new score. Invoke calls every subscribed method, in the order they subscribed. With no listener the event is null, so a plain OnScoreChanged(score) would throw a NullReferenceException, and ?. skips the call instead. The event keyword limits other scripts to += and -=: in ScoreLabel, keeper.OnScoreChanged(5) or keeper.OnScoreChanged = null does not compile.
The ScoreKeeper has no field for the label and does not know it exists. A combo counter or a sound player subscribes the same way and the ScoreKeeper stays as it is, and a test scene with no HUD runs without errors. A listener finds the publisher through an Inspector reference, as here, or any way you know, such as GetComponent or a singleton. Removing a listener with -= matters once objects get destroyed, which is the next lesson.
Challenge
EasyMake the fighter announce its combo.
- In
Fighter.cs, addusing System;andpublic event Action<int> OnComboChanged;. - Replace the lines that use
comboTextandflamewithOnComboChanged?.Invoke(combo);, and delete both fields. - In the
Start()ofComboTextandComboFlame, writefighter.OnComboChanged += Show;and+= Grow;.
Press Play and hit Space a few times: the combo grows, then clears. Then Check.
Try it yourself
using UnityEngine;
using UnityEngine.InputSystem;
using TMPro;
public class Fighter : MonoBehaviour
{
public float comboWindow = 1f;
public TMP_Text comboText;
public Transform flame;
private int combo;
private float sinceHit;
void Update()
{
if (Keyboard.current.spaceKey.wasPressedThisFrame)
{
combo++;
sinceHit = 0f;
comboText.text = "Combo x" + combo;
flame.localScale = Vector3.one * (0.5f + 0.25f * combo);
}
else if (combo > 0)
{
sinceHit += Time.deltaTime;
if (sinceHit > comboWindow)
{
combo = 0;
comboText.text = "";
flame.localScale = Vector3.one * 0.5f;
}
}
}
}
All lessons in Game Architecture in Unity
1Components, Not Hierarchies
Small ComponentsA Base Enemy ScriptAbstract ComponentsComponents Working TogetherRecap: Modular TurretRecap: Twin Cannon4Serialized Data
Serializable ClassesLists in the InspectorEnums in the InspectorRecap: Loot TableRecap: Roll the Loot2Interfaces in Unity
IDamageableInterfaces and TryGetComponentGetComponentsRecap: Interact KeyRecap: Nearest in Reach3Events
C# Events in UnitySubscribe in OnEnableUnityEvent FieldsEvents Drive the UIRecap: Achievement PopupRecap: Popup TimerPractice on your own: Unity playground