在 OnEnable 中订阅
本课属于 Coddy Unity 学习旅程的“Unity 游戏架构”单元。第 13 课,共 64 课。
被销毁的监听器不会自动从事件中移除。事件仍然持有它的方法,下一次 Invoke 会在已销毁的组件上调用该方法。该方法第一次使用 transform 时会抛出 MissingReferenceException,而异常会结束 Invoke,因此后面的监听器也会被跳过。所以,每个 += 都需要对应一个 -=,Unity 为此提供了一对方法:OnEnable 和 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);
}
}当对象通过 SetActive(false) 关闭、脚本被禁用或对象被销毁时,Unity 会在 OnDestroy 之前调用 OnDisable。对象每次重新启用时,都会运行 OnEnable。使用这对方法后,守卫只会在处于激活状态时监听事件:守卫被销毁后会从事件中移除,为过场动画而关闭的守卫会忽略警报,再次启用后则会重新监听。
另外两种搭配会出错。在 Start 中使用 +=,并在 OnDisable 中使用 -= 时,关闭后再启用的守卫不会重新监听,因为 Start 只会运行一次。在 OnEnable 中使用 +=,但不使用 -= 时,每次重新启用都会再次添加 RunTo,一次警报就会调用它两次。移除未订阅的方法不会产生任何效果,因此 OnDisable 中的 -= 不需要检查。
挑战
简单让每个村民只在铃铛启用时监听它。
- 在
Villager.cs中,将bell.OnRing += Hop;从Start()移到新的void OnEnable()中,并删除Start()。 - 添加
void OnDisable(),其中包含bell.OnRing -= Hop;。“检查”会关闭再启用 Cleo 来测试这一点。
按“开始”并敲铃 4 次:Ana 在第 2 次敲铃后回家,并且不会显示错误。然后点击“检查”。
亲手试试
using UnityEngine;
public class Villager : MonoBehaviour
{
public Bell bell;
public int ringsToLeave = 3;
public int hops;
void Start()
{
bell.OnRing += Hop;
}
void Update()
{
// 跳起后落回地面
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);
}
}
}
Unity 游戏架构中的所有课
自己练习: Unity 在线编辑器