事件总线(Event Bus)作为中心枢纽管理着一系列可以订阅或发布的事件。它是一种集中式事件处理机制,允许不同的组件之间进行彼此通信而又不需要相互依赖,达到一种解耦的目的。
在计算机术语中,总线指计算机各种组件之间传送信息的公共通信干线。在事件总线模式中,这些组件就是时间的发布者(Publisher)和监听者(Listener)。因此,事件总线是一种使用发布-订阅模式(publish-subscribe)通过事件连接对象的方法。
事件总线(Even Bus)模式本质上是发布订阅模式(Publish-Subscribe)。当对象即发布者(Publisher)引发事件时,它给其他对象发送信号。只有订阅该事件的对象即订阅者(Subscriber)才会被通知,并处理该事件。这就好比突然发出的无线电信号,只有调频到特定频率的天线才能检测到。
在Unity中使用事件总线模式可以简化订阅者和发布者之间的关系,让它们完全不知道彼此。并且仅用一行代码就可以实现订阅或发布。
事件总线作为订阅者和发布者之间消息传递的媒介
从上图可以看到事件总线模式中有三种角色:
- 发布者(Publisher):可以发布由事件总线定义的特定类型的事件。
- 事件总线(Event Bus):该对象负责协调发布者和订阅者之间的事件传输。
- 订阅者(Subscriber):这些对象通过事件总线将自己注册为特定事件的订阅者。
事件总线模式的优点:1.可以解耦各个对象,对象之间通过事件总线交流,取代直接交流。2.抽象了发布订阅的机制,让其应用更简单。
事件总线模式的缺点:1.在任何事件系统的底层,都有一个管理对象之间消息传递的低级机制。 因此,使用事件系统时可能会有轻微的性能成本,但取决于目标平台,它可能是微不足道的。 2.与单例模式类似,因为事件总线可以全局访问,所以很难进行单元测试(Unit test)。
接下里在Unity中实践一下事件总线模式。
假设我们正在开发一款赛车竞速类游戏。在竞速类比赛中通常有以下几种状态:1.比赛开始前的倒计时(Countdown) 2. 比赛开始(RaceStart) 3.比赛结束(RaceFinish) 4.比赛暂停(RacePause) 5.比赛重新开始(RaceRestart) 6.比赛停止(RaceStop)
基本事件类型已经明确,接下来就在Unity中使用枚举的方法定义事件类型
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace EventBus
{
public enum RaceEventType
{
COUNTDOWN, START, RESTART, PAUSE, STOP, FINISH, QUIT
}
}
接下来我们来实现该模式中最重要的部分——事件总线。
using System.Collections;
using System.Collections.Generic;
using UnityEngine.Events;
namespace EventBus
{
public class RaceEventBus
{
private static readonly IDictionary<RaceEventType, UnityEvent>
Events = new Dictionary<RaceEventType, UnityEvent>();
public static void Subscribe(RaceEventType eventType, UnityAction listener)
{
UnityEvent thisEvent;
if(Events.TryGetValue(eventType, out thisEvent))
{
thisEvent.AddListener(listener);
}
else
{
thisEvent = new UnityEvent();
thisEvent.AddListener(listener);
Events.Add(eventType, thisEvent);
}
}
public static void UnSubscribe(RaceEventType eventType, UnityAction listener)
{
UnityEvent thisEvent;
if(Events.TryGetValue(eventType, out thisEvent))
{
thisEvent.RemoveListener(listener);
}
}
public static void Publish(RaceEventType eventType)
{
UnityEvent thisEvent;
if(Events.TryGetValue(eventType, out thisEvent))
{
thisEvent.Invoke();
}
}
}
}
在这里我们使用词典Dictionary来对各种类型的事件进行管理。通过Suscribe()方法可以对事件的进行订阅,通过Unsubscribe()方法可以取消订阅,通过Publish()方法发布指定类型的事件。
接下来我们写一些脚本来测试刚才写的EventBus
首先实现一个倒计时的脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace EventBus
{
public class CountdownTimer : MonoBehaviour
{
private float currentTime;
private float duration = 3.0f;
private void OnEnable()
{
RaceEventBus.Subscribe(RaceEventType.COUNTDOWN, StartTimer);
}
private void OnDisable()
{
RaceEventBus.UnSubscribe(RaceEventType.COUNTDOWN, StartTimer);
}
private void StartTimer()
{
StartCoroutine(CountDown());
}
private IEnumerator CountDown()
{
currentTime = duration;
while(currentTime > 0)
{
yield return new WaitForSecondsRealtime(1f);
currentTime--;
}
RaceEventBus.Publish(RaceEventType.START);
}
private void OnGUI()
{
GUI.color = Color.blue;
GUI.Label(new Rect(125, 0, 100, 20), "COUNTDOWN " + currentTime);
}
}
}
然后我们实现一个BikeController脚本,用来测试Start和Stop类型的事件。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace EventBus
{
public class BikeController : MonoBehaviour
{
private string status;
private void OnEnable()
{
RaceEventBus.Subscribe(RaceEventType.START, StartBike);
RaceEventBus.Subscribe(RaceEventType.STOP, StopBike);
}
private void OnDisable()
{
RaceEventBus.UnSubscribe(RaceEventType.START, StartBike);
RaceEventBus.UnSubscribe(RaceEventType.STOP, StopBike);
}
private void StartBike()
{
status = "Start";
}
private void StopBike()
{
status = "Stop";
}
private void OnGUI()
{
GUI.color = Color.green;
GUI.Label(new Rect(10, 60, 200, 20), "BIKE STATUS :" + status);
}
}
}
而后我们再写一个HUDController的脚本,可以控制UI的显示(当游戏未开始时,没有StopGame按钮,开始后才有)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace EventBus
{
public class HUDController : MonoBehaviour
{
private bool isDisplayOn;
private void OnEnable()
{
RaceEventBus.Subscribe(RaceEventType.START, DisplayHud);
}
private void OnDisable()
{
RaceEventBus.UnSubscribe(RaceEventType.START, DisplayHud);
}
private void DisplayHud()
{
isDisplayOn = true;
}
private void OnGUI()
{
if(isDisplayOn)
{
if(GUILayout.Button("Stop Game"))
{
isDisplayOn = false;
RaceEventBus.Publish(RaceEventType.STOP);
}
}
}
}
}
最后我们编写一个CilentEventBus来对上面写的几种控制脚本进行管理。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace EventBus
{
public class CilentEventBus : MonoBehaviour
{
private bool isButtonEnabled;
private void OnEnable()
{
RaceEventBus.Subscribe(RaceEventType.RESTART, ReStart);
}
private void OnDisable()
{
RaceEventBus.UnSubscribe(RaceEventType.RESTART, ReStart);
}
// Start is called before the first frame update
void Start()
{
gameObject.AddComponent<HUDController>();
gameObject.AddComponent<BikeController>();
gameObject.AddComponent<CountdownTimer>();
isButtonEnabled = true;
}
private void ReStart()
{
isButtonEnabled = true;
}
private void OnGUI()
{
if(isButtonEnabled)
{
if(GUILayout.Button("Start Countdown"))
{
isButtonEnabled = false;
RaceEventBus.Publish(RaceEventType.COUNTDOWN);
}
}
}
}
}
运行效果
游戏开始前
游戏开始前的倒计时
游戏运行中
(水平有限,只是想记录学习过的知识,如果有错请指正)