现在的位置: 首页 > 综合 > 正文

Ogre监听器原理详解

2013年01月10日 ⁄ 综合 ⁄ 共 8870字 ⁄ 字号 评论关闭

TutorialFrameListener是示例教程给的监听器,从下面可以看出它继承哪些监听器

class TutorialFrameListener : public ExampleFrameListener, public OIS::MouseListener, public OIS::KeyListener

下面一个个分析:

(1)ExampleFrameListener

这个监听器声明如下:

class ExampleFrameListener: public FrameListener, public WindowEventListener 

可以看出它继承与FrameListener帧监听器和WindowEventListener 窗体事件监听器

ExampleFrameListener类中定义了几个跟键盘和鼠标以及游戏手柄有关的成员变量如下:

//OIS Input devices
 OIS::InputManager* mInputManager;
 OIS::Mouse*    mMouse;
 OIS::Keyboard* mKeyboard;
 OIS::JoyStick* mJoy;

类的构造函数原型如下:

ExampleFrameListener(RenderWindow* win, Camera* cam, bool bufferedKeys = false, bool bufferedMouse = false,bool bufferedJoy = false ) ;

其中,bufferedXXX由客户继承此类决定是否使用缓冲输入对应的设备。

在类的构造函数中实例化上面几个成员变量:

win->getCustomAttribute("WINDOW", &windowHnd);
  windowHndStr << windowHnd;
  pl.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str()));

  mInputManager = OIS::InputManager::createInputSystem( pl );   //输入管理器需要要一个窗口相关的参数

  //Create all devices (We only catch joystick exceptions here, as, most people have Key/Mouse)
  mKeyboard = static_cast<OIS::Keyboard*>(mInputManager->createInputObject( OIS::OISKeyboard, bufferedKeys ));  

  mMouse = static_cast<OIS::Mouse*>(mInputManager->createInputObject( OIS::OISMouse, bufferedMouse ));
  try {
   mJoy = static_cast<OIS::JoyStick*>(mInputManager->createInputObject( OIS::OISJoyStick, bufferedJoy ));
  }
  catch(...) {mJoy = 0; }

 ExampleFrameListener类剩下的东西就是实现部分他的父类FrameListener和 WindowEventListener 的virtual方法。

 

FrameListener帧监听器:

下面是FrameListener类的截图

可以看出,监听器就是一个简单的显示器,这个类里只有三个简单的被root对象在它的renderOneFrame函数里依次调用的三个函数。

WindowEventListener监听器:

这个类在示例教程中没用到,它也很简单,就只有5个关于窗体状态变化的函数,截图如下:

另外在这个窗体监听器类的头文件中还定义了一个WindowEventUtilities类,好像是Ogre封装了windows消息循环,以配合WindowEventListener使用。

 

(2)MouseListener鼠标监听器

这个监听器所在头文件截图如下:

从图中可以看出,MousListener类很简单,就是三个关于鼠标状态的函数。

关于MoueButtonID是个OIS命名空间下的枚举类型:

enum MouseButtonID
 {
  MB_Left = 0, MB_Right, MB_Middle,
  MB_Button3, MB_Button4, MB_Button5, MB_Button6, MB_Button7
 };

关于MouseEvent类:

class _OISExport MouseEvent : public EventArg
 {
 public:
  MouseEvent( Object *obj, const MouseState &ms ) : EventArg(obj), state(ms) {}
  virtual ~MouseEvent() {}

  //! The state of the mouse - including buttons and axes
  const MouseState &state;
 };

可以看出MouseEvent类中主要就是有一个MouseState类型的描述鼠标当前状态的变量。

MouseState类主要内容如下:

class _OISExport MouseState
 {
 public:
    mutable int width, height;    //mutable是c++关键字表示易变的,与const相反之意

  Axis X;      Axis Y;    Axis   Z;      //X、Y、Z坐标

  ................其他内容省略

 };

有了上面各种类的定义,我门就可以分析MouseListener类的方法了:

关于无缓冲输入和带缓冲输入,总的来说:

对于无缓冲的输入,在每一帧里我们查询OIS::Keyboard和OIS::Mouse实例的状态,以判断它们是否被按下。而缓冲输入使用了一个listener接口,以便在事件发生时通知你的程序。【即缓冲输入才有监听器。】

对于缓冲的理解,就好像有一个带锁的缓存区,每次press时存入缓存区并立即锁上缓存区并保持数据在缓存区,对应的鼠标或键盘接口对象会一直监视这个缓存区直到对应的鼠标或按键弹起时才解锁缓存区并清空缓存区存的上一次的数据。所以带缓冲的输入感觉上每个动作都是开关型的动作,即每次只能执行一下。

(3)KeyListener键盘监听器

这个类如下:

class _OISExport KeyListener
 {
 public:
  virtual ~KeyListener() {}
  virtual bool keyPressed(const KeyEvent &arg) = 0;
  virtual bool keyReleased(const KeyEvent &arg) = 0;  
 };

KeyEvent类里主要就是下面一个成员

const KeyCode key; //其中KeyCode是个直接定义在OIS命名空间下的枚举类型,其成员都是int型的东西。

具体使用如:case OIS::KC_1:  break;   //注意,枚举类型定义时就给所有成员分配静态内存空间了(类似static const),可以直接使用成员了,不需要进行实例化。

 

有了上面各个类的分析,下面讲具体使用:

一般定义自己的监听器类如class TutorialFrameListener : public ExampleFrameListener, public OIS::MouseListener, public OIS::KeyListener;

首先实现ExampleFrameListener提供的虚方法,由于它又是继承与FrameListener和 WindowEventListener,本身基本上没什么东西,所以只需要实现FrameListener和 WindowEventListener提供的虚方法,另外FrameListener在Ogre示例中只是为了帮读者写好一些常用的固定功能,所以它只是个中间层,完全可以不用FrameListener,使TutorialFrameListener直接继承于和 WindowEventListener,并自己写一些常用的固定东西。

由TutorialFrameListener的定义可以看出TutorialFrameListener类的实例是FrameListener、WindowEventListener、MouseListener和KeyListener类型的东西。那么既然是这些个监听器的实例,就要添加到这些监听器对应的被监听对象的列表中(监听器或访问者模式)。此时就要分清有哪些被监听的对象了:

首先,Root(或它内部的子元素)是被监听的对象,对应FrameListener,需要调用root->addListener(mTutorialFrameListener)方法。

第二,当前渲染窗口(Ogre::RenderWindow)是被监听的对象,对应WindowEventListener,使用Ogre::WindowEventUtilities::addWindowEventListener(mRenderWindow, mTutorialFrameListener)方法。

第三,鼠标和键盘接口的实例是被监听的对象,对应MouseListener和KeyListener,使用mMouse->setEventCallback(this)和mKeyboard->setEventCallback(this)方法,其中this就代表mTutorialFrameListener。

最后要说的是,这些被监听器都不是想象中的在自己独立的线程中捕获事件(WindowEventListener还不是很肯定)。对于FrameListener,是在Root主渲染线程中的RenderOneFrame函数中通知FrameListener该改变状态了;对于MouseListener和KeyListener,被监听的对象鼠标和键盘接口实例不会主动由系统实时判断鼠标和键盘的状态变化,而是虚程序员手工使用鼠标或键盘实例的capture()方法获取一次鼠标或键盘的当前一次状态。而在capture()方法内部调用通知MouseListener和KeyListener的方法,也即capture()内部调用鼠标或键盘监听器的诸如mousePressed或keyPressed方法。一般在Ogre中,对鼠标和键盘状态的变化都是在每一帧渲染的时候判断,也即写在主渲染线程中,具体做法是在FrameListener的frameStarted()方法中调用capture()方法。【具体capture内部是怎么实现的,估计使用了操作系统的函数的帮助捕获了当前鼠标和键盘的状态,就不深究了】。

 

另外,Ogre中还有一个重要的SdkTray的东西,这个东西意思就是带sdk的托盘,也就是功能复杂的能触发事件的托盘,用于取代cegui。在SdkTray.h头文件中定义了SdkTrayListener、Wiget、button等控件以及SdkTrayManager这些类。其中,button等控件继承于wiget类,每个wiget类内部都包含有一个SdkTrayListener的实例。以监听器(观察者)模式来看,Wiget(button是Wiget的一种)是被观察对象的接口(这里没那么严格,不一定是纯接口,总之是被观察的对象就行了),而SdkTrayListener是监听器(观察者),所以所有的事件都应该是被观察的对象Wiget主动发出的,自己可以想一下,每个被观察对象内部就应该含有监听器的列表(只不过这里Wiget内部只含有一个SdkTrayListener罢了)。所有需要处理Wiget的事件处理函数所在的类都应该是监听器(观察者),并且应该实例化具体的监听器并调用被观察对象的AddListener()函数将这个监听器添加到Wiget内部。以上就是分析,但Ogre把上面的东西都封装起来了(本人感觉看起来更累),靠的就是SdkManager这个类,这个管理器内部存储有所有创建的Wiget(如button等),并且创建具体的button之类的东西都是靠这个管理器内部的函数创建。SdkTrayManager类的构造函数需要一个SdkTrayListener的参数,然后把这个参数传给每个创建的Wiget(被观察对象)。上面说了这么多,还是没解决Wiget这个被观察对象是怎么主动捕获诸如鼠标按下之类的动作的,本来我以为Wiget既然作为被观察的对象就和上面讲到的鼠标和键盘接口一样,他们的实例都应该具有captrue()方法,因为只有具有这个方法才能主动去捕获鼠标和键盘动作,这样猜测完全没错,但Ogre采用了更好的办法,他让SdkTrayManager管理所有的Wiget,并且在管理器内部实现了类似captrue()的方法,这些方法名字叫做m_pTrayMgr->injectMouseMove(evt);、injectMouseUp(evt)等函数(参数evt是OIS::MouseEvent
&evt类型的,含有鼠标位置等信息),分别用于捕获鼠标按下、弹起等动作,Ogre这样做的目的就是为了完全隐藏Wiget,让用户只需要看到Manager就可以使用了。实际上一个SdkTrayManager就管理它内部的所有Wiget(具体就是Button等),在injectMouseDown(evt)函数内部遍历所有的Wiget,调用所有Wiget的_cursorPressed(cursorPos)之类的函数,注意cursorPos是鼠标位置,由evt参数中获取,在具体wiget的_crusorPressed()函数内部判断当前单击位置是不是自己,如果是就执行对应事件,如果不是直接返回。对于客户来说,只需要知道实现SdkTrayListener监听器,并写好对应的事件处理函数,然后把这个监听器实例通过SdkTrayManager的injectMouseDown之类的函数添加到被监听对象中就行了,至于injectMouseDown(evt)函数可以在每帧事件FrameStart之类的函数内部调用。

 

总之,对于用户来说,只需要给用户被监听对象和监听器的两个类,用户使用时不需要深究内部东西,只需要实现监听器函数(事件处理函数)和添加监听器到被监听对象(类似c#的添加委托事件)即可。最多可能需要自己在循环中调用被监听对象的captrue或injectMuseDown之类的方法而已。

 

 

最后补充,ogre示例课程使用能够OIS的方法,下面内容都是转自网页:

在Ogre中使用OIS的两种模式

转自:http://blog.sina.com.cn/s/blog_68f6e8a90100xz0z.html

关于OIS的输入

要开始考虑游戏输入的问题了,以及开始加入CEGUI也要考虑加入输入的问题。先把OIS的输入简单回忆一下。

OIS有两种输入模式:非缓冲输入以及缓冲输入。

无论用哪种输入方式,都应该有个输入设备管理器先,例如:(这代码仅仅是举例而已)

    在Ogre中的一个例子。。。。

    OIS::InputManager*                            m_pInputManager;

    size_t windowHnd = 0;
    std::ostringstream windowHndStr;
    OIS::ParamList pl;
    RenderWindow *win = m_pRoot->getAutoCreatedWindow();
    win->getCustomAttribute("WINDOW", &windowHnd);
    windowHndStr << windowHnd;

    m_pInputManager = OIS::InputManager::createInputSystem(pl);

非缓冲输入:

1、创建方式:其中 false 参数代表使用非缓冲模式

OIS::Keyboard* mKeyboard;

OIS::Mouse*     mMouse ;

mKeyboard = static_cast<OIS::Keyboard*>

(mInputManager->createInputObject(OIS::OISKeyboard, false));

mMouse = static_cast<OIS::Mouse*>

(mInputManager->createInputObject(OIS::OISMouse, false));

2、在监听帧中的用法:

bool frameStarted(const FrameEvent& evt)

{

mKeyboard->capture();

mMouse->capture();

if(mKeyboard->isKeyDown(OIS::XXXX))

{

}

}

使用方法比较简单,对帧监听器没有特殊要求(相对于缓冲方式)

缓冲输入:

1创建方式:其中 true参数代表使用非缓冲模式

mKeyboard = static_cast<OIS::Keyboard*>

(mInputManager->createInputObject(OIS::OISKeyboard, true));

mMouse = static_cast<OIS::Mouse*>

(mInputManager->createInputObject(OIS::OISMouse, true));

2、在监听帧中的用法:

首先,监听帧必须拥有回调的方法,就是说监听器应该这样:继承KeyListener,MouseListener

class ExitListener : public FrameListener,public OIS::KeyListener,public OIS::MouseListener

其次,监听帧必须实现以下方法:

bool mouseMoved(const OIS::MouseEvent &arg)

bool mousePressed(const OIS::MouseEvent &arg, OIS::MouseButtonID id)

bool mouseReleased(const OIS::MouseEvent &arg, OIS::MouseButtonID id)

bool keyPressed(const OIS::KeyEvent &arg)

bool keyReleased(const OIS::KeyEvent &arg)

这些方法都是父类的纯虚接口,不得不实现他们。。正常运行时,发生事件后讲执行对应的函数。

要让这些函数起作用,还要做的事情是:

在 ExitListener 的构造函数(随便)中注册该类为回调类

mMouse->setEventCallback(this);

mKeyboard->setEventCallback(this);

最后,还要在每一帧更新输入

bool frameStarted(const FrameEvent& evt)

{

mKeyboard->capture();

mMouse->capture();

}

非缓冲输入以及缓冲输入使用效果的最大区别:

非缓冲能处理(立即模式) 键盘或者鼠标按住的事件

缓冲不能处理 键盘或者鼠标按住的事件

即:使用带缓冲输入是,当鼠标键盘的事件发生时,我们会立即处理它,不会像非缓冲那样每一帧只处理一次。

使用CEGUI时需要自己注入输入消息,使用缓冲模式条理比较清晰,处理比较简单。

在同一个游戏中是能同时存在缓冲以及非缓冲输入的!

另外:

典型输入系统在PC上有键盘 鼠标 和 摇杆. OGRE目前是采用了OIS这套库。OIS封装了DX INPUT,能够和OGRE无缝的融合在一起. OIS的优点是短小精悍,使用简单,缺点是封装过于单一化,只支持DX,对其它的输入系统没有有效的接口支持(或者说不能够自定义一些输入设备);另外,OIS不支持单独线程处理输入设备,需要使用者自己实现,也增加了复杂度.

目前的OGRE把输入系统和渲染系统放在了同一个线程中来做(其实大部分游戏也都是这样做的),但这样做有个很严重的问题,就是游戏的FPS下降时,鼠标或其他输入系统的响应时间也会变慢。解决这个问题就两种方法,但都需要修改OGRE源代码,具体方法如下:
1. 使用Windows的鼠标系统,Windows的鼠标系统其实是跑在了一个优先级相当高的线程当中。直接利用它可以解决这类问题。但鼠标的图片就不是那么灵活了,必须要用光标格式(.cur .ani)等。另外,还得在回调函数里向我们的输入系统转发鼠标消息
2. 自己建立线程,单独处理鼠标的输入,并及时的在屏幕上渲染自己。这个机制恐怕得修改OGRE源代码。
综合考虑,第一种方案实现比较简单,第二种方案必须对OGRE的渲染机制有很深的了解。

其他补充:OIS的输入系统都分为两类,缓冲模式和立即模式,这其实是由DX决定的.
那么这两种模式在使用时有什么区别吗? 是有的。对于缓冲模式,每一个输入都漏不掉,OIS会原封不动的把输入的信息发送到我们上层。这种模式比较适合对输入连续性要求比较严格的场所,(比如文字输入系统等)。
这两种模式在获取数据时也是有所不同的,只有在缓冲模式下,上面列出的一大堆回调函数(比如keyPressed mouseReleased等才会被调用,具体调用的位置是在capture这个函数里面),而在立即模式(非缓冲)下,上面那几个回调函数都不会被调用,我们要通过其它手段来获取信息。这里是通过isKeyDown(键盘),或者getMouseState(鼠标)来获取的。

 

抱歉!评论已关闭.