Irrlicht 0.1引擎源码分析与研究(三)

IEventReceiver

源代码总共有两个例子,第一个例子没有操作任何3D对象或者摄像机,所以没有用到事件接收器。第二个例子“Quake3DMap”中涉及到摄像机的操作,所以添加一事件处理部分的代码。下面是Quake3DMap.cpp文件中的MyEventReceiver类的定义,当然,作为一种好的编程习惯。类的定义应该尽量放到.h文件中,但此例只是一个简单的示例,内容很少,所以只用了一个CPP文件。

scene::ICameraSceneNode* camera = 0;

class MyEventReceiver : public IEventReceiver

{

public:

         virtual bool OnEvent(SEvent event)

         {

                   if (camera)

                            return camera->OnEvent(event);

                   return false;

         }

};

接口IEventReceiver的定义在IEventReceiver.h文件中,内容如下,只有一个方法OnEvent()

class IEventReceiver

{

public:

 

         //! called if an event happened. returns true if event was processed

         virtual bool OnEvent(SEvent event) = 0;

};

在本例中,camera所指的对象的类型为CCameraMayaSceneNode,因为添加相机的语句如下:

//添加摄像机

camera = pSManager->addCameraSceneNodeMaya();

CCameraMayaSceneNode又派生自CCameraSceneNode类,而CCameraSceneNode类又派生自ICameraSceneNode ICameraSceneNode又派生自ISceneNode, IEventReceiver 。由此可以看出,CCameraMayaSceneNodeOnEvent 是从IEventReceiver 继承来的,但奇怪的是的CCameraSceneNode中也定义了一个虚函数OnEvent。看来这是重复定义,是没有意义的。

下面是CCameraMayaSceneNode类中OnEvent函数的实现。

bool CCameraMayaSceneNode::OnEvent(SEvent event)

{

         if (event.EventType != EET_MOUSE_INPUT_EVENT)

                   return false;

 

         switch(event.MouseInput.Event)

         {

         case EMIE_LMOUSE_PRESSED_DOWN:

                   MouseKeys[0] = true;

                   break;

         case EMIE_RMOUSE_PRESSED_DOWN:

                   MouseKeys[2] = true;

                   break;

         case EMIE_MMOUSE_PRESSED_DOWN:

                   MouseKeys[1] = true;

                   break;

         case EMIE_LMOUSE_LEFT_UP:

                   MouseKeys[0] = false;

                  break;

         case EMIE_RMOUSE_LEFT_UP:

                   MouseKeys[2] = false;

                   break;

         case EMIE_MMOUSE_LEFT_UP:

                   MouseKeys[1] = false;

                   break;

         case EMIE_MOUSE_MOVED:

                   {

                            video::IVideoDriver* driver = SceneManager->getVideoDriver();

                            if (driver)

                            {

                                     core::dimension2d<s32> ssize = SceneManager->getVideoDriver()->getScreenSize();

                                     MousePos.X = event.MouseInput.X / (f32)ssize.Width;

//存储鼠标点击点x方向的相对位置。

                                     MousePos.Y = event.MouseInput.Y / (f32)ssize.Height;

//存储鼠标点击点y方向的相对位置。

                            }

                   }

                   break;

         }

         return true;

}

可以看出,这个事件响应函数响应了鼠标事件,鼠标键按下时,记下状态。当鼠标移动时,记下光标在在窗口的相对位置。

再回到CIrrDeviceWin32函数中关于IEventReceiver* receiver参数的处理,看这个参数传到了什么地方,是谁调用了MyEventReceiver类中定义的OnEvent虚函数。其中有一句:

GUIEnvironment = gui::createGUIEnvironment(FileSystem, VideoDriver, receiver);

CGUIEnvironment.cpp文件中,能找到该函数定义如下:

IGUIEnvironment* createGUIEnvironment(io::IFileSystem* fs, video::IVideoDriver* Driver, IEventReceiver* userReceiver)

{

         return new CGUIEnvironment(fs, Driver, userReceiver);

}

里面又调用了类CGUIEnvironment的构造函数。该类的继承关系为:

class CGUIEnvironment : public IGUIEnvironment, IGUIElement

下面来看它的构造函数:

CGUIEnvironment::CGUIEnvironment(io::IFileSystem* fs, video::IVideoDriver* driver, IEventReceiver* userReceiver)

: IGUIElement(0, 0, 0, core::rectEx<s32>(core::position2d<s32>(0,0), driver ? driver->getScreenSize() : core::dimension2d<s32>(0,0))),

         UserReceiver(userReceiver), Hovered(0), CurrentSkin(0), Driver(driver),

         MouseFocus(0), KeyFocus(0), FileSystem(fs)

{

         if (Driver)

                   Driver->grab();

         if (FileSystem)

                   FileSystem->grab();

         #ifdef _DEBUG

         IGUIEnvironment::setDebugName("CGUIEnvironment IGUIEnvironment");

         IGUIElement::setDebugName("CGUIEnvironment IGUIElement");

         #endif

 

         loadBuidInFont();

 

         CurrentSkin = createSkin(EGST_WINDOWS_STANDARD);

         CurrentSkin->setFont(getBuildInFont());

}

在这里,可以看到引用计数机制的使用。因为CGUIEnvironment要使用DriverFileSystem所指的对象,要保证在CGUIEnvironment的生命周期内,他所需要的对象不会在别人地方被删除,可以在CGUIEnvironment类的构造函数中调用grap()函数,就可以保证对象不会在调用drop函数前被删除,在析构函数中调用drop函数。

if (Driver)

                   Driver->drop();

if (FileSystem)

                   FileSystem->drop();

 

回到追踪事件接收器上来,CGUIEnvironment类的构造函数中,事件接收器传给了它的成员变量UserReceiver,至此暂时无法追踪。因为此处只是传给了CGUIEnvironment类的成员变量,并没有进行下一步的操作。构造函数中的

loadBuidInFont();

         CurrentSkin = createSkin(EGST_WINDOWS_STANDARD);

         CurrentSkin->setFont(getBuildInFont());

这几个操作也与事件处理没有直接的关系,看来只能回到例子的main函数中找线索了。createDevice函数后面的几个操作事件处理都无关,直到找到pDevice->run()语句,如下:

//循环

         while (pDevice->run())

         {

                   pVDriver->beginScene(true,true,Color(0,100,100,100));//0.1版与新版有区别,新版有默认参数

                   pSManager->drawAll();

                   pGuienv->drawAll();

                   pVDriver->endScene();//绘图完成后,完全前后缓存的交换。

         }

         //释放设备

         pDevice->drop();

         return 0;

前面已经分析过了,pDevice->run()只不过执行了消息的分发的任务,同时判断如果消息为WM_QUIT,结束循环:

if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))

         {

                   TranslateMessage(&msg);

                   DispatchMessage(&msg);

         }

         return (msg.message != WM_QUIT);

真正对消息进行响应的函数应该在窗口的回调函数中,看回调函数的定义:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

{

         irr::gui::IGUIEnvironment* Env = 0;

         irr::SEvent event;

         switch (message)

         {

         case WM_PAINT://

                   {

                            PAINTSTRUCT ps;

                            HDC hdc = BeginPaint(hWnd, &ps);

                            EndPaint(hWnd, &ps);

                   }

                   return 0;

         case WM_ERASEBKGND://

                   return 0;

         case WM_LBUTTONDOWN://处理键按下消息

        event.EventType = irr::EET_MOUSE_INPUT_EVENT;

                   event.MouseInput.Event = irr::EMIE_LMOUSE_PRESSED_DOWN;

                   event.MouseInput.X = LOWORD(lParam);

                   event.MouseInput.Y = HIWORD(lParam);

    //获取与窗口hWnd关联的的GUI环境指针

                   Env = getEnvironmentFromHWnd(hWnd);

    //投递消息

                   if (Env)

                            Env->postEventFromUser(event);

//GUI环境来处理事件,此处这才是事件Irr事件处理的入口。

                   return 0;

         ……(此处省略N行,语句内容和WM_LBUTTONDOWN消息差不多,处理鼠标和键盘的消息。)

         case WM_DESTROY:

                   PostQuitMessage(0);//退出消息

                   return 0;

         case WM_SYSCOMMAND://系统消息

                   // prevent screensaver or monitor powersave mode from starting

                   if (wParam == SC_SCREENSAVE ||

                            wParam == SC_MONITORPOWER)//屏保或者显示器关闭

                            return 0;

                   break;

         }

 

         return DefWindowProc(hWnd, message, wParam, lParam);

}

好了,至此我们追踪到了在回调函数中调用了与窗口关联的GUI环境的postEventFromUser函数来处理事件。再看CGUIEnvironment类中postEventFromUser函数的定义:

void CGUIEnvironment::postEventFromUser(SEvent event)

{

         switch(event.EventType)

         {

         case EET_GUI_EVENT://GUI事件

                   // hey, why is the user sending gui events..?

                   break;

         case EET_MOUSE_INPUT_EVENT://鼠标输入事件

                   if (MouseFocus)//如果有GUI元素获得鼠标焦点,则让GUI对象处理。

                            MouseFocus->OnEvent(event);

                   else//如果没有GUI元素获得鼠标焦点

                   {

                            bool absorbed = false;

                            updateHoveredElement(core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y));

 

                   if (Hovered && Hovered != this)//好像是说如果鼠标悬停在某个GUI元素上面。

                                     absorbed = Hovered->OnEvent(event);//则由该GUI元素来处理事件。

                   if (!absorbed && UserReceiver)//传给用户事件处理器

                                     UserReceiver->OnEvent(event);

//调用用户定义的事件处理器处理,此处为Irrlicht事件处理机制的一个关键点。

                   }

                   break;

         case EET_KEY_INPUT_EVENT://处理键盘输入事件

                   {

                            bool absorbed = false;

                            if (KeyFocus && KeyFocus != this)

                                     absorbed = KeyFocus->OnEvent(event);

 

                            if (!absorbed && UserReceiver)

                                      UserReceiver->OnEvent(event);

                            break;

                   }

         default:

                   return;

         }

}

至此已经找出了用户定义的事件接收器是如果被调用的。

在前面分析过的CCameraMayaSceneNode::OnEvent(SEvent event)函数中可以看出,此函数只不过是记录了鼠标三个键的状态以及移动到了什么位置。但并没有对摄像机在世界坐标中的参数,说明在某个地方检测到了这些改变之后,摄像机的位置或方向被变换。接下来的工作是找到到底是谁,在什么地方完成了这些操作?接着pDevice->run()往后看,pDevice->isWindowActive()没有,pVDriver->beginScene()有嫌疑,那么追踪一下这个函数,发现它有几个版本的定义,有OpenGL,DX8,软件版等等。其实到这里已经可以猜到,改变摄像机的位置和方向的操作应该不在这些版本中,因为这并不是哪一个版本的特殊操作,是所有的版本中都要进行了。还是简单地看看这个操作吧,以DX8版本为例:

bool CVideoDirectX8::beginScene(bool backBuffer, bool zBuffer, Color color)

{

         CVideoNull::beginScene(backBuffer, zBuffer, color);

         DWORD flags = 0;

         if (backBuffer)

                   flags |= D3DCLEAR_TARGET;

         if (zBuffer)

                   flags |= D3DCLEAR_ZBUFFER;

         pID3DDevice->Clear( 0, NULL, flags, color.color, 1.0, 0);

         HRESULT hr = pID3DDevice->BeginScene();

         return !FAILED(hr);

}

可以看出,它只是进行了一些设置,并调用了DXClearBeginScene函数。回到例子的main函数中,接下来有嫌疑的就是pSManager->drawAll()了,而且它的嫌疑最大。从字面意义上看,它应该要画所有的场景了。要画场景,肯定要用到摄像机的方向和位置,此时不变换摄像机,更待何时?

void CSceneManager::drawAll()

{

         if (!Driver)

                   return;

         // calculate camera pos.

         camTransPos.set(0,0,0);

         if (ActiveCamera)

                   camTransPos = ActiveCamera->getAbsolutePosition();

//获取摄像机的世界坐标系位置

         // let all nodes register themselfes

         OnPreRender();

         //render lights and cameras

         Driver->deleteAllDynamicLights();

         for (u32 i=0; i<LightAndCameraList.size(); ++i)

                   LightAndCameraList[i]->render();

         LightAndCameraList.clear();

         // render default objects

         DefaultNodeList.sort(); // sort by textures

         for (i=0; i<DefaultNodeList.size(); ++i)

                   DefaultNodeList[i].node->render();

         DefaultNodeList.clear();

         // render transparent objects.

         TransparentNodeList.sort(); // sort by distance from camera

         for (i=0; i<TransparentNodeList.size(); ++i)

                   TransparentNodeList[i].node->render();

         TransparentNodeList.clear();

         // do animations and other stuff.

         OnPostRender(os::Timer::getTime());

}

 

virtual void OnPostRender(u32 timeMs)

                   {

                            AnimatedRelativeTransformation = RelativeTransformation;

 

                            if (IsVisible)

                            {

                                     // animate this node with all animators

 

                                     core::list<ISceneNodeAnimator*>::Iterator ait = Animators.begin();

                                     for (; ait != Animators.end(); ++ait)

                                               (*ait)->animateNode(this, timeMs);

 

                                     // update absolute position

                                     updateAbsolutePosition();

 

                                     // perform the post render process on all children

                                    

                                     core::list<ISceneNode*>::Iterator it = Children.begin();

                                     for (; it != Children.end(); ++it)

                                               (*it)->OnPostRender(timeMs);

                            }

                   }

 

void CCameraMayaSceneNode::OnPostRender(u32 timeMs)

{

         animate();

         AnimatedRelativeTransformation = RelativeTransformation;

         AnimatedRelativeTransformation.setTranslation(Pos);

         updateAbsolutePosition();

         // This scene node cannot be animated by scene node animators.

}

 

void CCameraMayaSceneNode::animate()

{       

         const SViewFrustrum* va = getViewFrustrum();

         f32 nRotX = rotX;

         f32 nRotY = rotY;

         f32 nZoom = currentZoom;

  //前后移动-------------------------

         if (isMouseKeyDown(0) && isMouseKeyDown(2))//如果左键和右键都按下

         ……(后面是一大堆噼里啪啦控制相机移动的代码,还没有深入研究)

}

整理参考:

hellphenix的专栏:http://blog.csdn.net/hellphenix/archive/2008/03/19/2198226.aspx

小时候可靓了:http://blog.csdn.net/wqjqepr/archive/2010/04/26/5528528.aspx

游戏的行者:http://blog.csdn.net/n5/archive/2009/07/08/4329603.aspx

游戏的行者:http://blog.csdn.net/n5/archive/2008/12/15/3516219.aspx

游戏的行者:http://blog.csdn.net/n5/archive/2009/07/05/4323332.aspx

游戏的行者:http://blog.csdn.net/n5/archive/2009/07/12/4342758.aspx

Flyflyking:http://blog.csdn.net/flyflyking/archive/2011/03/30/6289698.aspx

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值