C# Events in Unity
Part of the Game Architecture in Unity section of Coddy's Unity journey. Lesson 10 of 50.
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. You know event and Action from C#. In 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
EasyThe fighter's combo grows with every hit of Space and falls back to 0 after comboWindow (1) second without a hit. Right now Fighter.cs sets the combo text and the flame's size itself, so it needs a field for each. Turn it around with an event:
- In
Fighter.cs, declare a public event namedOnComboChangedthat passes the combo (an int). Raise it with the new combo every time the combo changes, the reset to 0 included, and make sure a hit causes no error when nobody listens. - Delete Fighter's
comboTextandflamefields and the lines that use them: the Fighter knows nothing of the HUD. - In
ComboText.csandComboFlame.cs, subscribe theirShowandGrowmethods to the fighter's event inStart(). Both have afighterfield, linked in the scene.
Press Play, hit the bag with Space a few times, wait a second, 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;
}
}
}
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Game Architecture in Unity
1Components, Not Hierarchies
Small ComponentsA Base Enemy ScriptAbstract ComponentsComponents Working TogetherRecap: Modular Turret10Project: Arena Survivor
The HeroEnemy Types3Events
C# Events in UnitySubscribe in OnEnableUnityEvent FieldsEvents Drive the UIRecap: Achievement Popup