游戏开发设计模式(三):事件总线(Event Bus)

        事件总线(Event Bus)作为中心枢纽管理着一系列可以订阅或发布的事件。它是一种集中式事件处理机制,允许不同的组件之间进行彼此通信而又不需要相互依赖,达到一种解耦的目的。

        在计算机术语中,总线指计算机各种组件之间传送信息的公共通信干线。在事件总线模式中,这些组件就是时间的发布者(Publisher)和监听者(Listener)。因此,事件总线是一种使用发布-订阅模式(publish-subscribe)通过事件连接对象的方法。

        事件总线(Even Bus)模式本质上是发布订阅模式(Publish-Subscribe)。当对象即发布者(Publisher)引发事件时,它给其他对象发送信号。只有订阅该事件的对象即订阅者(Subscriber)才会被通知,并处理该事件。这就好比突然发出的无线电信号,只有调频到特定频率的天线才能检测到。

        在Unity中使用事件总线模式可以简化订阅者和发布者之间的关系,让它们完全不知道彼此。并且仅用一行代码就可以实现订阅或发布。

        

 事件总线作为订阅者和发布者之间消息传递的媒介

        

        从上图可以看到事件总线模式中有三种角色:

  1. 发布者(Publisher):可以发布由事件总线定义的特定类型的事件。
  2. 事件总线(Event Bus):该对象负责协调发布者和订阅者之间的事件传输。
  3. 订阅者(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);
                }
            }
        }
    }
}

运行效果

游戏开始前 

游戏开始前的倒计时

       游戏运行中 

(水平有限,只是想记录学习过的知识,如果有错请指正)

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值