游戏编程模式之观察者模式

版权声明:未经作者允许不可转载 https://blog.csdn.net/qq_38134452/article/details/88682894

观察者模式中的观察者列表

“在对象间定义一种一对多的依赖关系,以便当某对象的状态改变时,与它存在依赖关系的所有对象都能收到通知并自动进行更新”(摘自《游戏编程模式》)

  • MVC的底层就是观察者模式
  • 观察者模式理解图

one

  • 两者是m : n的关系。被观察者可以被多个观察监视,一个观察者可以观察多个被观察者
  • 观察者模式的意义是:把游戏逻辑从分散在各个主干代码上聚起来
  • 实现方法主要思想是:利用关键字作为事件标志建立一个消息通知系统,当特定的事件发生时,调用被观察者自身通知观察者的方法,(通知观察者),然后在观察者执行游戏逻辑判断。
  • 发送消息的手段即调用方法
  • 只要理解为如下:

two

成就系统

  • 很多游戏都存在成就系统。成就系统关系到很多行为 —— 因为它本应存在于游戏中的各个角落,涉及到多方面的判断。成就系统中可能包含以下成就:
    • 杀死100个野怪 (累计在角色身上的杀敌数)
    • 从桥上坠落 (物理系统的碰撞检测)
    • 飞行最高记录:1000米 (依旧是物理)
  • 我们不可以将成就判定代码写在上述中的角色类、物理引擎上。 这就到了观察者模式大显身手的时候了。

    其实这是可行的,但这种设计方法是糟糕的,会照成代码的不可维护

  • 观察者:
    class Observer
    {
        public:
            virtual ~Observer();
            virtual void noNotify(const Entity& entity,Event event)=0;
    }
    
    //成就系统
    class Achievements : public Observer
    {
        public:
            virtual void noNotify(const Entity& entity,Event event)
            {
                switch(event)
                {
                    case EVENT_ENTITY_FELL:
                        if(entity.isHero()&&heroIsOnBridge_)
                          unLock(ACHIEVEMENT_FELL_OFF_BRIDGE);
                        break;
                }
            }
            
        private:
            void unLock(Achievement ach)
            {
                //Unlock
            }
            
        bool heroIsOnBridge_;
    }
    
  • 被观察者:(以从桥上坠落为例)
    class Subject 
    {
        private:
            Observer* observers[MAX_OBSERVER];
            int numObservers;
            
        public:
            void addObserver()     {}
            void removeObserver()  {}
            void notityAll()       {}  //遍历并发送
    }
    
    //从桥上坠落涉及到物理系统
    class Physics : public Subject
    {
        public:
            void updateEntity(Entity& entity);
    }
    

存在问题

  • 太快的后果
    • 消息系统会存在消息堵塞问题。观察者模式是同步的,被观察者只有从观察者获得上一次消息的反馈才会继续工作,这是从其他地方会有新的消息传入,引发了堵塞。
    • 远离UI线程:UI消息是同步的,在处理UI消息时,必须要马上完成响应并尽可能快的返回UI代码,这样才不会照成卡顿。
  • 太多的动态内存分配
    • 在上面的代码中,为了简便使用了固定长度的数组,但在实际开发中,更多人需要的是可变长度的数组,这时设计到动态分配内存
    • 解决方案是可以将数组改为链表

链式Observer代替数组Observer

  • 实现如下所示:(不给出解释了,链表的实现可以参考数据结构知识点)
    class Subject 
    {
        public:
            Subject():head_(NULL){}
            
            void addObserver()     {}
            void removeObserver()  {}
            void notityAll()       {}  //遍历并发送
            
        private:
            Observer* head_;
    }
    
    class Observer
    {
        friend class Subject;
        public:
            Observer() : next_(NULL)  {}
            
        private:
            Observer* next_;    
    }
    
    void Subject::addObserver(Observer* observer)
    {
        observer->next_=head_;
        head_=observer;
    }
    
    void Subject::removeObserver(Observer* observer)
    {
        if(head_==observer)
        {
            head_=observer->next_
            observer->next_=NULL;
        }
        
        Observer* current=head_;
        //链表遍历
        while(current!=NULL)
        {
            if(current->next==observer)
            {
                current->next_=observer->next_;
                observer->next_=NULL;
                return;
            }
            current=current->next;
        }
    }
    
    void Subject::notify(const Entity& entity,Event event)
    {
        Observer* observer=head_;
        while(observer != NULL)
        {
            observer->onNotify(entity,event);
            observer=observer->next_;
        }
    }
    

余下的问题

  • 销毁观察者or被观察者:因为我们都在运用指针。指针比较关键的注意点是——误删。由于指针指向的是地址,当我们不小心delete时,其他引用该地址的指针便会**指向一个空地址!! **

总结

  • 观察者模式适用于一些不相关模块之间的通信问题。不适合用于单个紧凑的模块内部之间的通信
  • 观察者模式中Subject中的观察者列表具有灵活性,你可以选择合适的数据结构进行设计。

知识点全部源于《Game Programming Patterns》,中文名《游戏编程模式》

猜你喜欢

转载自blog.csdn.net/qq_38134452/article/details/88682894