消息机制的设计一般总要设计到三个部分:消息的产生、消息的传递和消息的处理。
OGRE中的消息处理者的抽象类主要是listener类,而listener必须是对应特定的target的,所以可以认为是由listener和target两个抽象类组成。而消息传递这是有:Dispatcher和Processor组成。那么消息的产生有那些呢?暂时只分析出Input Reader。下面介绍大概的结构。
下面来分析一下从消息的产生到分配到处理的过程。
1.消息(事件)的产生
消息是由InputReader类产生的,这个类有几个重要的方法:
virtual void capture() = 0;
这个方法用于捕获所有的输入设备的状态。
void mouseMoved();
void triggerMouseButton(int nMouseCode, bool mousePressed);
void keyChanged(int key, bool down);
这几个方法根据鼠标或键盘的状态来调用以下函数来产生鼠标或键盘事件。
void createMouseEvent(int id, int button);
void createKeyEvent(int id, int key);
所有产生的事件的基类为:InputEvent,它们的继承结构图如下:
上面两个函数把产生的事件插进消息队列。这个消息队列是Processor传递过来的,这在等下会分析。
获得事件以后接下来就是分发事件给不同的处理者。
2.消息(事件)的分配和处理
处理消息分配的类主要是EventProcessor和EventDispatcher。EventProcessor是一个框架类,它调用InputReader并把自己的消息队列传递过去。把获得的事件保存在这个消息队列中然后再分发它们。 分发的过程是在
void processEvent(InputEvent* e);
中完成。这个函数先遍历mDispatcherList来处理这个事件,如何处理等下会说明,如果EventDispatcher不能处理这个事件。即e->isConsumed()为false的话,它就遍历mEventTargetList来处理这个事件。如果e->isConsumed()仍然为false话就根据事件的类型来调用相应的继承来的Target中的processXXXEvent函数来处理。
mDispatcherList是EvnetDispacher的队列。它通过下面这个函数来增加元素。
void EventProcessor::addTargetManager(TargetManager* targetManager)
{
//一个TargetManager 对应一个 EventDispatcher
EventDispatcher* pDispatcher = new EventDispatcher(targetManager);
mDispatcherList.push_back(pDispatcher);
}
从这个函数我们可以看出,一个EventDispatcher就对应一个TargetManager。那么什么是TargetManager呢?观察TargetManager发现这个类提供了一个抽象的接口:
virtual PositionTarget* getPositionTargetAt(Real x, Real y) = 0;
它就是返回x,y位置的PositionTarget。不同类型的target需要不同的实现。比如这个类被OverlayManager继承,它实现这个函数就返回一个x,y位置的GuiElement。GuiElement在游戏菜单、按钮等一些控件。
由此我们得出结论如果我们产生的事件不是在Overlay上或者其他继承了TargetManager的管理者所管理的元素上发生的,就没有相应的EventDispatcher来处理它!
ExampleFrameListener类中调用这个类的顺序是:
mEventProcessor = new EventProcessor();
mEventProcessor->initialise(win);
mEventProcessor->startProcessingEvents();
mEventProcessor->addKeyListener(this);
mInputDevice = mEventProcessor->getInputReader();
EventProcessor继承于几个类,它们分别是FrameListener, MouseTarget, MouseMotionTarget, KeyTarget,Singleton<EventProcessor>。之所以要继承FrameListener是因为在每帧开始的时候它都要先要通过调用InputReader的capture()来捕获所有设备的状态。然后依次处理消息队列中的所有消息并把处理完的消息删除;另外要继承MouseTarget, MouseMotionTarget, KeyTarget是因为这个类可以增加这几种Target对应的EventListener来处理各个消息。EventProcessor的行为就像一个默认的EventTarget,所以它可以处理那些没有dispatcher处理的事件。例如:
ExampleFrameListener中的mEventProcessor->addKeyListener(this);其中的addKeyListener(this)就是覆写了KeyTarget中的addKeyListener函数,this是KeyListener的子类,它可以定义处理各个事件(比如按键,释放键.ect)的方法。从而可以处理那些不由Dispatcher处理的那些事件。
下面来看看,当Processor把一个事件发送给EvnetDispacher,EventDispacher是如何处理这个事件的。
EvnetDispacher用于把传递给它的单个事件分发事件到相应的EventTarget中。这个类中主要的函数就是dispatchEvnet(InputEvent* e),它通过调用其它成员函数实现了整个类的主要作用。它通过判断消息属于鼠标消息还是键盘消息来处理:
if (e->isEventBetween(MouseEvent::ME_FIRST_EVENT, MouseEvent::ME_LAST_EVENT))
{
向下转型为鼠标消息
processMouseEvent(me);
…..
}
else if (e->isEventBetween(KeyEvent::KE_FIRST_EVENT, KeyEvent::KE_LAST_EVENT))
{
向下转型为键盘消息
processKeyEvent(me);
……
}
下面我们来看是如何处理鼠标消息ProcessMouseEvent和键盘消息ProcessKeyEvent的.
bool EventDispatcher::processMouseEvent(MouseEvent* e)
这个函数根据事件的ID来区分不同的事件,并把它们发送给各自得接受者.比如:
switch (e->getID())
{
case MouseEvent::ME_MOUSE_PRESSED:
retargetMouseEvent(…)
break;
case MouseEvent::ME_MOUSE_RELEASED:
retargetMouseEvent(…)
break;
case MouseEvent::ME_MOUSE_MOVED:
case MouseEvent::ME_MOUSE_DRAGGED:
retargetMouseEvent(…)
break
}
retargetMouseEvent调用事件的Target进而调用Target所关联的接收者来处理事件。
在Oger中InputEvent,EventTarget,Listener构成命令模式InputEvent是请求类,EventTarget是命令类,Listener是接收者类。我们知道命令模式中InputEvent中保存了一个EventTarget对象,哪个InputEvent保存哪个EventTarget对象就由dispatcher来指定。
Event类的class view 如下所示:
InputEvent 是基类,主要包括了一些辅助键(如:ctrl、alt、shift等)。这些辅助键同鼠标和键盘结合就可以产生一些复合操作。而子类则根据具体的对象定义一些特有的属性,例如:鼠标则具有屏幕坐标属性一些各个按键的状态;而键盘则包括按键的ascoll码。其它则包括了各类GUI所特有的属性。
同时注意Event类中都包含了事件所对应的目标对象指针――EventTarget。
下面我们就介绍EventTarget类的Class:
其中EventTarget类是InputEvent要使用的类的基类。这个类也是处理缓冲输入的类之一。KeyTarget用于处理增加和移出KeyListeners和处理Key事件。PositionTarget类用于处理和xy位置有关的事件,它支持嵌套操作,所以它的left和top是相对于parent来说的,它里面的函数全是全虚函数 ,所以必须要有类来继承。
现在分析一下它们是在程序中如何组织以及如何产生和传递的。
Input Event主要是一Queue的形式保存,如下所示:
而Target则以Target Manager保存,注意这个Target Manager是一个抽象类,不同属性的target需要实现不同的Target Manager;例如:Overlay 就有 Overlay Manager(继承了Target Manager),如果还有一些3D的物体可以作为Target的话,就需要定义一个3D Object Manager 了。