


当需要 触发A ► 激活B 这种功能时(尤其是N个触发M个这种多对多的情况),一般会使用事件(Event)来实现。在这里A被称作发布者(Publisher),B被称作订阅者(Subscriber),所以这种模式也叫发布/订阅模式。
在Unity里主要是通过UnityEvent和UnityAction来实现。

例如这种踩陷阱 + 动画 + 通知其他建筑
在上面这个Demo里敌人移动踩中踏板,激活踏板动画并点亮远处信号灯,就是用了一系列的事件来实现的。
完整版Demo视频:

动机(+)和困难(-)
+ 解耦发布者、订阅者。也就是说“信号灯”可以轻松接收各种机关的触发信号,同时“踏板”也可以发布给各类型的信号灯。双方可以存在于完全不相关的系统里。
+ 高扩展性。加入新的发布、订阅者对原功能基本不会造成影响。
- 监听过多会降低游戏运行速度,所以不能同时存在过多的事件(不过经测试每帧万次以内问题都不大)。
总的来说是绝对的利大于弊,尤其在后期会很方便加入新元素。

..
概念
Publisher - 发布者 (UnityEvent)
主动发送信息的一方,例如Demo中的踏板。每次同时发送相同信息给所有的订阅者。
Subscriber - 订阅者(Listener / UnityAction)
接收信息的一方,例如Demo中的信号灯。时刻监听着发布者是否有发送新的消息,并且付诸行动。
在我的项目中,Event目前主要被用在两个方面:
达成触发条件时,播放对应的动画(例如踏板激活时下降、变色)
达成触发条件时,通知所有与其连接的信号接收者。
所以目标里包括了两个完全不相关的事务(动画、信号接收者)。
在Unity中,我们可以使用自带的UnityEvent和UnityAction来完成目标,相比C#自带的更方便,可以在编辑器直接加入需要订阅的函数。
UnityEvent

Grid_SensorHolder.cs
例如在我的“传感器”类里,有两个UnityEvent,分别在敌人“走入传感器”、“走出传感器”时触发。
UnityEvent.Invoke()

Grid_SensorHolder.cs
这两个Event通过Invoke()来激活。也就是说每次Invoke(),这个Event的所有的Listener(监听者)都会执行一次。
UnityEvent.AddListener()
给一个事件增加订阅者有两个方法:
1. 编辑器

在编辑器里,同样生成了out_enterTile和out_exitTile。为了方便我在后面添加了换音效+播放的函数。
2. 代码

LinkAnimatorToEvent.cs

LinkAnimatorToEvent.cs
这个代码将“动画”与“传感器类”结合。每次敌人进入传感器范围时,动画的"bool Activated"(用来使踏板下降变绿)参数被设为True,反之亦然。在这里,只要把所有需要执行的函数传入AddListener()即可。如果需要移除可以通过RemoveListener()。
UnityAction
UnityAction是一个很方便的函数打包器,或是叫 Zero-argument delegate “零参数委托”,专门用来和UnityEvent配合。
比如一个UnityAction可以通过 += func_1(); += func_2() 来打包让Event同时执行这两个函数。或者类似:

直接赋予lambda函数
第三第四行等于直接把输入输出首尾相连。
与命令模式结合
游戏内设计逻辑时,信号输出与输入需要通过“建造模式”进行链接。我的方法是在“逻辑建造”模式下,依次点击两下,产生连线时表示连接成功。
这里有两处比较难搞的地方:
建造时如何选择输入信号与输出信号,因为存在不止一个输入与输出。
如何撤销。
我的解决方法是:

InteractiveObject.cs
通过Dictionary来索引用于发布的事件,以及用于执行的UnityAction。
有了这两个写Execute和Redo就非常简单了,分别为:
output.AddListener(input);
output.RemoveListener(input);

很方便实现1连N或N连1