HuldaGnodim 发表于 2021-11-15 06:36

unity怎么抛出事件,监听事件?

建议先去学习一下C#的委托、事件,以及观察者模式。实际上监听的事件,都有一个回调函数,进行通知。

Ilingis 发表于 2021-11-15 06:41

建议先去学习一下C#的委托、事件,以及观察者模式。实际上监听的事件,都有一个回调函数,进行通知。

量子计算9 发表于 2021-11-15 06:46

抛出事件就是调用某一个类型委托的事件实例,那么绑定在该事件委托链上的一系列监听者都会被调用,C#和Unity都有相应的支持。听起来有点拗口,下面分别举个简单的小例子。
1.用C#的委托与事件实现事件的抛出与监听
创建一个可以在start函数中抛出事件的脚本组件
public class Delegator : MonoBehaviour
{
    // 声明一个委托类型,该类型的返回类型为void、参数为string
    public delegate void ShowYourName(string prefix);
    // 创建showYourName委托类型的实例(事件)
    public ShowYourName showYourNameEvent;

    // Start is called before the first frame update
    void Start()
    {
      // 调用该事件
      showYourNameEvent("我的名字是");
    }
}
再创建一个监听该事件的事件处理者脚本组件
public class EventHandler : MonoBehaviour
{
    private void OnEnable()
    {
      // 注意,Delegator在Start函数中调用事件
      // 那么注册事件的监听函数就要在Start函数之前,否侧会出BUG
      // 搜寻Delegator
      GameObject obj = GameObject.Find("Delegator");
      if (obj != null)
      {
            // 获取delegator组件
            Delegator delegator = obj.GetComponent<Delegator>();

            //将自己的事件处理函数加入委托链,等待该事件被调用
            delegator.showYourNameEvent += showNameEventHandleFunc;
      }
    }

    // Start is called before the first frame update
    void Start()
    {}

    // 处理事件的函数
    void showNameEventHandleFunc(string prefix)
    {
      Debug.Log(prefix + gameObject.name);
    }
}
将它们add到场景中的GameObject上



委托事件

可以看到监听函数被成功调用!
2.Unity对委托事件的封装
UnityApi中采用UnityEvent<>泛型类实现了委托与事件,“<>”中可以指定0到4个参数类型,但是并没有限定返回值的类型。



UnityEvent

C#中也有类似的实现,那就是Function<>和Action<>,只不过在使用这两个泛型时需要指定返回值类型。用法都大相径庭。
                        // 这里就可以理解为在声明一个参数为(int, int, int, int)型的委托
public class MyIntEvent : UnityEvent<int, int, int, int>
{
}

public class ExampleClass : MonoBehaviour
{
    // 事件实例
    public MyIntEvent m_MyEvent;

    void Start()
    {
      if (m_MyEvent == null)
            m_MyEvent = new MyIntEvent();
       // 在委托链上添加监听者
      m_MyEvent.AddListener(Ping);
    }

    void Update()
    {
      if (Input.anyKeyDown && m_MyEvent != null)
      {
         // 调用事件
            m_MyEvent.Invoke(5, 6, 7, 8);
      }
    }

    void Ping(int i, int j, int k, int l)
    {
      Debug.Log("Ping" + i + j + k + l);
    }
}
然后再说说Unity中的事件监听,Unity中“事件”并不只是上文中提到的Event这个狭义的类对象,而是一类可以像观察者模式那样被调用的函数接口,这些的函数的官方名称叫“事件函数”。Unity 中的脚本与传统的程序概念不同。在传统程序中,代码在循环中连续运行,直到完成任务。相反,Unity 通过调用在脚本中声明的某些函数来间歇地将控制权交给脚本。函数执行完毕后,控制权将交回 Unity。这些函数由 Unity 激活以响应游戏中发生的事件,因此称为事件函数。Unity 使用一种命名方案来标识要对特定事件调用的函数,那就是OnXxxx()。下面举几个典型的例子:
1.脚本生命周期中的事件函数
哪怕是第一天学习Unity的程序员,也应该知道下面这张图吧


脚本生命周期中,各个以“On”开头的函数都是事件函数。
2.非UI游戏对象响应UI事件的实现
这里就要使用Unity.EventSystem中的接口来实现非UI游戏对象响应UI事件,这里举一个立方体响应鼠标点击的例子。
// 继承IPonterClickhandler接口
public class ClickListener : MonoBehaviour,IPointerClickHandler
{
    // 实现接口中的响应鼠标点击的函数
    public void OnPointerClick(PointerEventData eventData)
    {
      Debug.Log(gameObject.name + "被点击了! 屏幕位置是:x = "
            + eventData.position.x + " y = " + eventData.position.y);
    }
}将该脚本Add到场景中的任意非UI对象上。
然后,还有两件事情要做,第一,要给主摄像机加上射线组件(Physics Raycaster),这个组件顾名思义就是从摄像机发射一条指向视椎体近平面(屏幕平面)上鼠标点击位置的射线,检测该射线与场景内各个物体的相交情况。如果没有该组件,则非UI游戏对象无法接收到屏幕点击或者拖拽相关的UI信息。



第二,要在场景中加入EventSystem对象。EventSystem对象中有两个组件,一个是Input Module,该组件决定了怎么识别输入信号,及控制响应输入的频率,另一个是EventSystem,该组件负责接收UI输入的信息和分发,如果场景中缺少必要的EventSystem组件,那么UI输入的信息就无法下发至等待监听UI事件的游戏对象,那么游戏UI系统就无法起作用。


运行游戏以后点击立方体


如果能明白上面这个过程,那么就可以搞明白为什么平时我们创建UI游戏对象的时候会自动创建一个Canvas对象和一个EventSystem对象了,Canvas对象就起到了上面带物理射线的摄像机的作用,只不过2D的UI只需要更加简单的射线检测机制,它的方式比3D的简单许多,具体的可以参考图形学相关的书籍。


到这里,应该能完全理解什么是“事件”了,就是个回调而已。。。

-------------------------------------更新分割线--------------------------------------
Ps:只有拥有Collider组件的非UI游戏对象才能被Raycaster投射出的射线所检测到,前文中拖入场景的Cube上已经有了Box Collider,所以才能被鼠标点击事件触发。
页: [1]
查看完整版本: unity怎么抛出事件,监听事件?