Menu
CoddyTech

在 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 中的 -= 不需要检查。

challenge icon

挑战

简单

让每个村民只在铃铛启用时监听它。

  1. 在 Villager.cs 中,将 bell.OnRing += Hop; 从 Start() 移到新的 void OnEnable() 中,并删除 Start()。
  2. 添加 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 在线编辑器