Subscribe in OnEnable
Part of the Game Architecture in Unity section of Coddy's Unity journey. Lesson 13 of 64.
A listener that is destroyed does not leave the event by itself. The event still holds its method, and the next Invoke calls it on the destroyed component. The first use of transform in that method throws MissingReferenceException, and since an exception ends the Invoke, the listeners after it are skipped too. So every += needs a -=, and Unity's pair of methods for that is OnEnable and OnDisable:
public class Guard : MonoBehaviour
{
public Alarm alarm;
void OnEnable()
{
alarm.OnTriggered += RunTo;
}
void OnDisable()
{
alarm.OnTriggered -= RunTo;
}
void RunTo(Vector3 spot)
{
transform.LookAt(spot);
}
}Unity calls OnDisable when the object is switched off with SetActive(false), when the script is disabled, and when the object is destroyed, before OnDestroy. OnEnable runs each time it comes back on. With this pair a guard listens exactly while it is active: a destroyed guard is gone from the event, a guard switched off for a cutscene ignores the alarm, and once switched on again it listens again.
Two other pairings go wrong. With += in Start and -= in OnDisable, a guard switched off and on never listens again, because Start runs only once. With += in OnEnable and no -=, every return adds RunTo again, and one alarm calls it twice. Removing a method that is not subscribed does nothing, so -= in OnDisable never needs a check.
Challenge
EasyMake each villager listen to the bell only while it is active.
- In
Villager.cs, movebell.OnRing += Hop;fromStart()into a newvoid OnEnable(), and deleteStart(). - Add
void OnDisable()withbell.OnRing -= Hop;. The Check switches Cleo off and on to test this.
Press Play and ring four times: Ana goes home after ring 2, and no error shows. Then Check.
Try it yourself
using UnityEngine;
public class Villager : MonoBehaviour
{
public Bell bell;
public int ringsToLeave = 3;
public int hops;
void Start()
{
bell.OnRing += Hop;
}
void Update()
{
// fall back to the ground after a hop
Vector3 p = transform.position;
transform.position = new Vector3(p.x, Mathf.MoveTowards(p.y, 0.5f, 4f * Time.deltaTime), p.z);
}
void Hop(int ring)
{
hops++;
transform.position += Vector3.up * 0.8f;
if (hops == ringsToLeave)
{
Debug.Log(name + " goes home");
Destroy(gameObject);
}
}
}
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