成就系统,基本上是每个游戏里都会涉及到的系统,但如果直接使用其它系统的函数接口,你将会发现整个项目中,每个系统都要和成就系统耦合在一起。
这是一件极其糟糕的事情。
观察者模式可以解决这件事情。
首先,我们先定义两个类,分别是观察者和被观察者。
观察者:
class Observer { friend class Subject; public: Observer() :next(nullptr), previous(nullptr) {} void OnNotify(const Unit unit, Event event) {} private: std::shared_ptr<Observer> next; std::shared_ptr<Observer> previous; };
被观察者:
class Subject { public: Subject() :head(nullptr) {} void AddObserver(std::shared_ptr<Observer> observer) { observer->next = head; head = observer; } void RemoveObserver(std::shared_ptr<Observer> observer) { if (observer->previous == nullptr) head = observer->next; else observer->previous = observer->next; } void Notify(const Unit unit,Event event) { //相应事件 //... auto observer = head; while (observer != nullptr) { observer->OnNotify(unit, event); observer = observer->next; } } private: std::shared_ptr<Observer> head; };
如何去使用它:
假如游戏中存在着一个邮件系统,当玩家发送第一封邮件时,解锁一个成就:
发信人:成功发送一封邮件给其他玩家
那我们只需要在邮件系统中定义:
class MailMgr
{
public:
void OnRoleSendMail(Unit& unit)
{
subject.Notify(unit, Event::EVENT_SENDMAIL);
//...
}
private:
Subject subject;
};
观察者模式本身只是发送了一个通知,它的逻辑是遍历一个观察者列表,然后调用函数,相对于一开始的直接函数调用,它并没有速度上的劣势,反而让两个游戏系统解耦,邮件系统开发者并不需要关注成就系统开发者做了什么事情。
如果非要说它的缺点:那就是多了一些内存的开销
进阶:
链表节点池:
我们可以对被观察者进行改造,在上面的代码中,被观察者存储的是一个观察者列表,现在改为存储一个指针列表,列表中的指针指向真正的观察者。
这样,多个指针可以指向同一个观察者,节省空间。
(当然,上面的代码使用的是智能指针,变向地支持了这个功能)
对象池:
由于所有的结点都是一样的大小和类型,可以预分配一个内存对象池子,根据需要去重用而不是不断地进行内存分配
注册函数,而不是注册类:
在上面的代码中,我们是使用类来实现观察者(列表中存储的是对象指针),其实也可以改造成函数指针。