Unity下落式音游实现——(3)实现观察者模式

Unity下落式音游实现——(3)实现观察者模式

前言

本来这一部分是计划放在后面的,但在整理鼓盘敲击判定时优化了原来的部分代码(删掉了一个不必要地函数),顺理成章地出了bug。最后发现是这个函数原会在另一个脚本中用unity自带地消息系统传递(SendMessage),麻烦在于SendMessage传递函数名参数用的是字符串,所以当我回滚代码,查找那个函数的引用时什么也没找到,很麻烦。于是就先将观察者模式提前…

前期准备

观察者模式

监听者被监听者两类对象,当某事件出现时,被监听者会对所有监听者进行广播一段信息,当监听者收到信息,发现这信息是自己感兴趣的内容则执行某些操作,若不是则忽略该信息。我们需要实现一个Messenger对象中心,给监听者提供接口函数AddListener以监听某段信息,给被监听者提供Boradcast以广播某段信息。信息以字符串形式传递,在广播时可以传递参数(意味着我们需要使用模板)

C#委托和事件

类似C++中函数对象,能实现一对多调用函数。监听者使用AddListener在Messenger中注册对应委托事件,被监听者使用Boradcast在Messenger中调用对应委托事件

实现过程

原理

class Observer
{
    public:
     virtual ~Observer() {}
    //	收到消息后做点什么
     virtual void onNotify(const Entity& entity, Event event) = 0;
};

class Subject
{
    private:
    //	需要通知的观察者列表
     Observer* observers[MAX_OBSERVERS];
     int numObservers_;
    
    public:
     void addObserver(Observer* observer);
     void removeObserver(Observer* observer);
    
    protected:
    //	发通知
     void notify(const Entity& entity, Event event)
     {
         for (int i = 0; i < numObservers_; i++)
            observers_[i]->onNotify(entity, event);
     }
};

优化

为了避免动态分配,可以预先在对象池中分配一系列Observer和Subject节点,

C#实现(基于消息

using System;
using System.Collections.Generic;
using System.Linq;

static public class Messenger
{
    //	存储已注册的委托事件
    readonly public static Dictionary<string, Delegate> eventTable = new Dictionary<string, Delegate>();
    
    static public void AddListener(string eventType, Delegate callback)
    {
        //	检查委托表中是否含有对应key
         if (!eventTable.ContainsKey(eventType))
		{
			eventTable.Add(eventType, null);
		}
        //	添加新委托
        eventTable[eventType] = Delegate.Combine(eventTable[eventType], callback);
    }
    
    	static public void RemoveListener(string eventType, Delegate handler)
	{
		if (!eventTable.ContainsKey(eventType))
            return;
		eventTable[eventType] = Delegate.Remove(eventTable[eventType], handler);
            //	若该信息无人感兴趣,就移除
		if(eventTable[eventType] == null)
            	eventTable.Remove(eventType);
	}
    
    	static public void Broadcast(string eventType)
        {
         Delegate d;
            //	获得对应委托
		if (!eventTable.TryGetValue(eventType, out d))
            return ;
            //	转化为委托数组
		Action[] invocationList = d.GetInvocationList().Cast<T>().ToArray();

		foreach (var callback in invocationList)
			callback.Invoke();
        }
}

参考了一个外国社区上的实现,我只实现了基本功能,没有适配多参数的模板,异常检测啥的也没做,这里贴一下大佬的异常检测

//	简单举例
static public BroadcastException CreateBroadcastSignatureException(string eventType)
{
	return new BroadcastException(string.Format("Broadcasting message {0} but listeners have a different signature than the broadcaster.", eventType));
}

public class BroadcastException : Exception
{
	public BroadcastException(string msg)
		: base(msg)
	{
	}
}

我们可以在GameEvent里记录事件信息

//  处理广播事件
public static class GameEvent 
{
    public const string DRUM_HIT = "DRUM_HIT";
    public const string DIFFICULTY_CHANGED = "DIFFICULTY_CHANGED";
    public const string STATUS_CHANGED = "STATUS_CHANGED";
}

使用

    void Awake()
    {
        Messenger<float>.AddListener(GameEvent.DIFFICULTY_CHANGED, changeSpeed);
    }
    
    void OnDestroy()
    {
        Messenger<float>.RemoveListener(GameEvent.DIFFICULTY_CHANGED, changeSpeed);
    }

总结

我们基于C#委托实现了观察者模式,在后续的UI和输入中会频繁地用到。另外监听者调用的函数由于类型是委托,不是字符串,可以正常地找到引用,不用再担心发生前言中的问题

另外Unity自带的SendMessage这么拉,是不是应该完全不用呢?个人感觉也不是,比如下面这段代码中需要调用三个参数不同的函数,就需要挂三个Listener,很麻烦(当然如果参数一样当然还是可以挂Listener,将三个函数封装一下就好)

    //  将对应信息传给slider
    slider.SendMessage("setStatus", status);
    slider.SendMessage("setTarget", tmp);
    slider.SendMessage("setMovingTime", movingTime);

如果迫不得已要用SendMessage,请务必要在调用处和被调用的函数处都写上注释表明来源或去处

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值