阅读 134

Unity 开放项目之事件系统

笔者语

翻译自开放项目的 Wiki ,翻译时版本是 ad3e009,诸君若看到较新的版本,或笔者文章有何疏漏,可留言或电邮 zhangqrr@qq.com / ricey54560@gmail.com


如何建立一个稳固的、能让 Objects 相互通信并且避免使用单例模式的事件系统?我们的解决方案是使用 ScriptableObject。

我们不用单例模式的原因有很多。单例模式在两个系统中建立死板的连接,导致他们不能单独存在,必须依赖于对方。显而易见,这会让项目变得难以维护、模块难以复用,如果你想单独测试一个系统也是很困难的,必须要测试整个游戏才可以。

工作原理

在事件系统底层我们创建了一系列的 Scriptable Object 叫做“事件频道”(Event Channels),他们就像是一个收音机频道一样,在这些频道上广播其他脚本(图中Action/Trigger)想要广播的事件。另一些脚本(图中Event Listener)可以反过来收听一个自己关心的频道,并将自己的回调方法(图中Call response(s))注册进频道所广播的事件中。上面的图描绘了这一运作过程。

发送事件和监听事件的都是 Monobehaviours。Scriptobject Event Channels 是一种资源,自从我们使用它们去连接两个系统之后,这些 Monobehaviours 就可以以完全独立的方式存在于两个不同的场景中了。

举一个例子,当按下某个按钮的时候会发送一个事件,并且广播在一个叫做 “Button_X_Pressed” 的 Event Channels Scriptobject 上。这时,我们可以让一个或者多个物体监听这个事件,并且当事件发生的时候做出不同的反映:其中一个孵化出很多粒子,其中一个播放了声音,另一个开始播放过场动画。

如何使用

项目中的 Event Channel

Events 可以有参数或者没有参数。下面是一些我们在项目中用到的 Event Channels 的例子。

  • Void Events 是没有参数的事件。一个很好地应用是需要广播退出游戏的时候。

  • Int Events 是在广播时携带一个 int 参数的事件。当我们解锁了一个成就时,我们可以使用这种事件进行广播,参数为成就 ID。

  • Load Events 是要传递两个参数的事件,一个是 GameScene 的数组,包含了我们想要加载的场景的所有数据,另一个是一个 bool 值,表明我们是否想要显示 ”加载中......“ 的界面。

会有更多类型的 Channels 被添加进项目。你可以在 /Scripts/Events/Scriptableobjects/ 文件夹中找到我们定义的所有 Event Channels ScriptableObjects。

创建一个 Event Channel ScriptableObject

在项目窗口右键,在 “Game Event” 中选择一种最适合你需求的 Event Channel,并给它取一个形象的名字。这样一个 Event Channel 就创建好了,可以随时使用。

使用 Event Channel 进行广播

要先持有作为 Channel 的 SO 的引用,然后只需要一行代码就可以在代码的任何地方发送一个事件。下面是一个例子,当一个物体进入当前物体的碰撞体时发送一个事件。

public VoidEventChannelSO OnTriggerEnterEventChannel;private void OnTriggerEnter(Collider other){
    OnTriggerEnterEventChannel.RaiseEvent();}

设置一个事件监听者

有很多方式可以监听一个 Event Channel 上的事件。负责监听的物体可以是一个单独的 Monobehaiour,它只用来做这一件事,或者把监听部分包含在一个脚本中。

在项目中,一个混合监听者的例子就是 LocationLoader 脚本。在这个例子中,负责监听的部分包含在了脚本内,也就是说这个脚本不仅是场景加载方法的容器也是一个监听者。当我们在项目中使用某个监听者不超过一次时,这种方式非常有用。(它在 Scripts/SceneManagement/LocationLoader.cs)(笔者:?)

你也可以找一个通用监听者的例子在 Scripts/Events/VoidEventsListener.cs 。这个脚本可以被挂载到任何 GameObject 并且监听一个 VoidEventChannelSO,它有一个无参的 UnityEvent,你可以将游戏中的任何行为与该 UnityEvent 关联,当事件发生时,UnityEvent 关联的所有行为都会回应。

创建一个新类型的 Event Channel

如果你需要在调用 Raise 方法的时候传递特定数量/类型的参数,那么你可以创建新类型的 Event Channel。下面是 IntEventChannelSO 的例子:

[CreateAssetMenu(menuName = "Events/Int Event Channel")]public class IntEventChannelSO : ScriptableObject{
    public UnityAction<int> OnEventRaised;
    public void RaiseEvent(int value)
    {
        OnEventRaised.Invoke(value);
    }}

将 int 变量换成任何你想要传递的参数类型。你也可以添加不止一个参数。

创建一个新类型的监听者

你可以根据事件触发时传递的参数数量来新建一个类型的监听者。下面是 Int Event Listener 的例子:

[System.Serializable]public class IntEvent : UnityEvent<int>{}public class IntEventListener : MonoBehaviour{
    public IntEventChannelSO IntGameEvent;
    public IntEvent OnEventRaised;

    private void OnEnable()
    {
        //Check if the event exists to avoid errors
        if (IntGameEvent == null)
        {
            return;
        }
        IntGameEvent.eventRaised += Respond;
    }

    private void OnDisable()
    {
        if (IntGameEvent == null)
        {
            return;
        }
        IntGameEvent.eventRaised -= Respond;
    }

    public void Respond(int value)
    {
        if (OnEventRaised == null)
        {
            return;
        }
        OnEventRaised.Invoke(value);
    }}

更多信息

  • 可以通过 2nd Devlog Video 来快速回顾我们是如何使用 ScriptableObjects 来驱动 Event Channels。

  • 我们最初介绍事件系统是在 Episode 2 of the Livestream (37.55)。注意,有些细节在那之后已经改变了。在 Wiki 里的是较新的。

  • 如果你想知道更多关于 UnityEvent 的多参数版本,你可以看 examples in the documentation ,这里它使用了四个参数。



作者:烂醉花间dlitf
链接:https://www.jianshu.com/p/0631ca0abc89
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐