zoukankan      html  css  js  c++  java
  • Ogre设计模式分析观察者模式

    观察者模式是游戏开发中十分常用的模式,Ogre也大量运用了此模式来监听,比如FrameListener.ResourceListener

    这种方式比常见的回调函数更好用,因为观察者模式是基于接口的,任何类只要继承这个接口,注册后就可以监听我们需要观察的对象。不想监听,取消注册就行了,

    具体实现原理,我们以为FrameListener为例子,然后再举一反三在自己的游戏中使用它,比如场景编辑器,我拖动了一个entity,改变了他的位置,那么显示对象属性的控件应该更新这个对象的位置,我们就可以设计一个监听类来监听任何对对象的操作,有变化就通知给显示面板。

    Ogre的帧监听怎么实现的呢

    1,先设计了一个基类,作为接口,啥东西都没,要自己override

        class _OgreExport FrameListener
        {
        public:

            virtual bool frameStarted(const FrameEvent& evt) { return true; }
            virtual bool frameEnded(const FrameEvent& evt) { return true; }

            virtual ~FrameListener() {}
        };

    2,然后在renderOneFrame(void)里面就会每帧调用这个基类的函数

        bool Root::renderOneFrame(void)
        {
            if(!_fireFrameStarted())   // 调用帧开始
                return false;

            _updateAllRenderTargets();

            return _fireFrameEnded(); // 调用帧结束
        }

    3, 看看具体实现

        bool Root::_fireFrameStarted(FrameEvent& evt)
        {
            // Increment frame number
            ++mCurrentFrame;

            // Remove all marked listeners
            std::set<FrameListener*>::iterator i;
            for (i = mRemovedFrameListeners.begin();
                i != mRemovedFrameListeners.end(); i++)
            {
                mFrameListeners.erase(*i);
            }
            mRemovedFrameListeners.clear();

            // Tell all listeners

           // 通知所有注册了的帧监听类
            for (i= mFrameListeners.begin(); i != mFrameListeners.end(); ++i)
            {
                if (!(*i)->frameStarted(evt))
                    return false;
            }

            return true;

        }

    看了源码,就明白,root里面保存有一个容器mFrameListeners,存储了所有注册了的帧监听

    只要加入到这个容器,就可以被遍历.然后通过C++的动态多态,实现frameStarted的调用

    4, 怎么加入这个容器呢?就是addFrameListener函数

        void Root::addFrameListener(FrameListener* newListener)
        {
      // Check if the specified listener is scheduled for removal
      std::set<FrameListener *>::iterator i = mRemovedFrameListeners.find(newListener);

      // If yes, cancel the removal. Otherwise add it to other listeners.
      if (i != mRemovedFrameListeners.end())
       mRemovedFrameListeners.erase(*i);
      else
       mFrameListeners.insert(newListener); // Insert, unique only (set)
        }

    5, 所有要使用一个帧监听,实例一个,再addFrameListener就OK,否则void Root::removeFrameListener(FrameListener* oldListener)

    ,而且我们可以创建N个帧监加入容器,就可以在任何类里面调用到frameStarted函数.

    --------------------------------------------------------------------------------------------------------------------------

    其作用,其实和用函数指针实现的回调函数差不多,但是明显比常见的回调的方式更好

    由于是基于接口的,那么扩展性非常好,比如Ogre的代码是封装成一个dll的,如果用回调,可能要写明回调到那个函数,可视Ogre并不知道用户写的函数,所以Ogre用接口,只操作接口,而用户继承这个接口,用C++动态多态,就可以实现对应函数的调用.

    这种方式很适合库和库之间,比如我Ogre的渲染模块用了一个库,客户端可以用我,场景编辑器也可以用我,Ogre的渲染模块就应该设计很多接口

    比如场景编辑器,我拖动了一个entity,改变了他的位置,那么显示对象属性的控件应该更新这个对象的位置,我们就可以设计一个监听类来监听任何对对象的操作,有变化就通知给显示面板。

    依葫芦画瓢

    1,先设计一个SceneListener基类

    2, 设计一个管理者SceneListenerManager来管理这些类,Ogre里面就是用root管理的

    1. // 用boost库的智能指针管理的场景对象而已  
    2. class Object;  
    3. typedef boost::shared_ptr<Object> ObjectPtr;  
    4. typedef boost::weak_ptr<Object> ObjectWeakPtr;  
    5.   
    6. // 场景监听基类  
    7. class SceneListener  
    8. {  
    9. public:  
    10.     SceneListener(void) {}  
    11.     virtual ~SceneListener(void) {}  
    12.   
    13.     virtual void onObjectPropertyChanged(const ObjectPtr& object, const String& name) {};  
    14. };  
    15.   
    16. // 场景监听管理器  
    17. class SceneListenerManager  
    18. {  
    19. protected:  
    20.     typedef std::list<SceneListener*> Listeners;  
    21.     Listeners mListeners;  
    22.   
    23. public:  
    24.     SceneListenerManager(void);  
    25.     ~SceneListenerManager(void);  
    26.   
    27.     void addSceneListener(SceneListener* listener)  
    28.     {  
    29.         mListeners.push_back(listener);  
    30.     }  
    31.   
    32.     void removeSceneListener(SceneListener* listener)  
    33.     {  
    34.         mListeners.remove(listener);  
    35.     }  
    36.   
    37.     // SceneListener* exclude 表示不用通知自己,因为自己也可能是一个SceneListener  
    38.     void _fireObjectPropertyChanged(const ObjectPtr& object, const String& name, SceneListener* exclude = 0)  
    39.     {  
    40.         for (Listeners::const_iterator it = mListeners.begin(); it != mListeners.end(); ++ it)  
    41.         {  
    42.             SceneListener* listener = *it;  
    43.             if (listener != exclude)  
    44.             {  
    45.                 listener->onObjectPropertyChanged(object, name);  
    46.             }     
    47.         }  
    48.     }  
    49. };  

    那么只要我们在更改对象属性的地方调用此函数通知所有监听者

    1. object->setProperty("position", position);  
    2. getSceneListenerManager()->_fireObjectPropertyChanged(object, "position"this);  

    所有监听者都会被通知,那么只要我们注册成一个监听者就可以收到这个消息

    假设显示面板要收到这个消息

    1. // 编辑器的属性面板  
    2. class PropertyPlane : public SceneListener  
    3. {  
    4. public:  
    5.   
    6.     // 初始化的时候加入监听  
    7.     PropertyPlane(void)  
    8.     {  
    9.         getSceneListenerManager()->_addSceneListener(this);  
    10.     }  
    11.   
    12.     // 重写  
    13.     void onObjectPropertyChanged(const ObjectPtr& object, const String& name)  
    14.     {  
    15.         // 显示这个对象的新的属性,对象指针和属性名字已经传过来了  
    16.     }  
    17. };  

     ---转自maomaoxx ID:qq18052887

  • 相关阅读:
    isinstance函数
    Django之ORM那些相关操作
    Django ~ 2
    Django ~ 1
    Django详解之models操作
    Django模板语言相关内容
    livevent的几个问题
    客户端,服务器发包走向
    关闭客户端连接的两种情况
    std::vector<Channel2*> m_allChannels;容器,以及如何根据channelid的意义
  • 原文地址:https://www.cnblogs.com/minggoddess/p/1959443.html
Copyright © 2011-2022 走看看