您的位置:首页 > 其它

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

2011-05-14 20:53 369 查看

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 。由此可以看出,CCameraMayaSceneNode的OnEvent 是从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类要使用Driver和FileSystem所指的对象,要保证在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);
}
可以看出,它只是进行了一些设置,并调用了DX的Clear和BeginScene函数。回到例子的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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: