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 ,这里它使用了四个参数。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容