天天看点

菜鸟学习OGRE和天龙八部之一:OGRE+MFC+OIS

折磨了2天,终于成功把OGRE嵌入到MFC里面,并实现了OIS缓冲输入和鼠标显示,看图:

菜鸟学习OGRE和天龙八部之一:OGRE+MFC+OIS

具体实现方法:

一 最好抛弃示例框架,实现一个自己的框架,因为有些东西需要修改,有些东西要自己实现,我自己写了一个框架demo来嵌入MFC,看简洁不少

// 我的帧监听类

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

{

public:

 MyFrameListener(OgreDemo* app, HWND hMainWnd);

 ~MyFrameListener();

 bool frameStarted(const FrameEvent &evt);

 bool mouseMoved(const OIS::MouseEvent &e);

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

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

 bool keyPressed(const OIS::KeyEvent &e);

 bool keyReleased(const OIS::KeyEvent &e);

protected:

 SceneManager *mSceneMgr;

 SceneNode* mCamNode;

 OIS::Keyboard* mKeyboard;        

 OIS::Mouse* mMouse;              

 OIS::InputManager* mInputManager;

 Real mRotate;          // 旋转常量

 Real mMove;            // 运动常量

 bool mContinue;        // 是否要继续渲染

 Vector3 mDirection;    // 指向正确的移动方向

};

class OgreDemo

{

public:

 OgreDemo():mRoot(0), mWindow(0), mListener(0), mCamera(0), mSceneMgr(0),mCamNode(0)

 {

 }

 ~OgreDemo();

 void setup(HWND m_hWnd, int width, int height, HWND hMainWnd);

 // 获得成员变量

 Root* getRoot(void) const {return mRoot;}

 Camera* getCamera(void) const {return mCamera;}

 SceneNode* getCamNode(void) const {return mCamNode;}

 SceneManager* getSceneManager(void) const {return mSceneMgr;}

 RenderWindow* getRenderWindow(void) const {return mWindow;}

private:

 Root* mRoot;

 RenderWindow* mWindow;           

 SceneManager* mSceneMgr;

 Camera* mCamera;

 SceneNode* mCamNode;

 MyFrameListener* mListener;

 void createRoot();

 void defineResources();

 void setupRenderSystem();

 void createRenderWindow(HWND m_hWnd, int width, int height);

 void initializeResourceGroups();

 void setupScene();

 void createFrameListener(HWND hMainWnd);

};

二, 具体过程分析

首先,因为用MFC打开,我们要去掉弹出的配置框,自己把渲染系统参数设置好:

void OgreDemo::setupRenderSystem()

{

 // 自己设置

 RenderSystem *rs = mRoot->getRenderSystemByName("Direct3D9 Rendering Subsystem");

 mRoot->setRenderSystem(rs);

 rs->setConfigOption("Full Screen", "No");

 rs->setConfigOption("Video Mode", "800 x 600 @ 32-bit colour");

}

其次,要嵌入到MFC,那么就要用view视图来作为OGRE的窗口,那么就要在生成OGRE窗口的时候把vire窗口句柄传入,幸好,OGRE不但支持自动产生窗口,还支持外部窗口,具体生产方法:

// 传入外部窗口句柄,这里是view类的句柄

void OgreDemo::createRenderWindow(HWND m_hWnd, int width, int height)

{

 // root初始化的时候,我们可以传入一个false值来告知Root不用给我们自动创建渲染窗口

 // 这样我们可以用外部窗口来作为MFC窗口

 mRoot->initialise(false);

 NameValuePairList miscParams; // 参数列表, 作为createRenderWindow函数的最后一个参数

 miscParams["externalWindowHandle"] = StringConverter::toString((long)m_hWnd);

 mWindow = mRoot->createRenderWindow("OgreRenderWindow", width, height, false, &miscParams);

}

现在我们就是用外部程序的窗口来作为OGRE的窗口了.

接着的问题是渲染循环!

当你使用startRendering()的时候,因为渲染循环交给了系统,所以没有办法把Ogre结合到窗口系统的消息循环中去.那么WINDOWS的消息循环和startRendering()的这2个独立的循环就会产生冲突,比如WM_PAINT和OGRE一起渲染...

解决办法就是做到一个循环中来,正好OGRE有提供只渲染一帧的方法renderOneFrame();

我把循环做到view视图类的OnDraw函数里面,用时间函数来控制循环

void CTLBBView::OnDraw(CDC* )

{

 CTLBBDoc* pDoc = GetDocument();

 ASSERT_VALID(pDoc);

 if (!pDoc)

  return;

 // TODO: 在此处为本机数据添加绘制代码

 // 创建OGRE程序

// 程序只初始化一次

 if(m_isFirstDraw)

 {

  m_isFirstDraw = false;

  CRect   rect;

  GetClientRect(&rect);

  app.setup(m_hWnd,rect.Width(),rect.Height(),AfxGetApp()->GetMainWnd()->GetSafeHwnd());

  // setup后root才被new出来,这时候才可以获得root

  root = app.getRoot();

  // 10ms触发一次

  SetTimer(1, 10, NULL);

 }

 if(m_isStart){

  root->renderOneFrame();

 }else{

//

 }

}

void CTLBBView::OnTimer(UINT nIDEvent)

{

 if(m_isStart){

  root ->renderOneFrame();

 }

}

这里有个setTimer,不设置回调函数以后(NULL),就会调OnTimer这个函数,每隔10ms OnTimer收到WM_TIMER消息就会绘制一次

10ms fps就是100啊,这里不要递归OnTimer,小心栈溢出哦

既然OnTimer要收到WM_TIMER消息,就要代码中加上消息映射

BEGIN_MESSAGE_MAP(CTLBBView, CView)

 ON_WM_TIMER()

END_MESSAGE_MAP()

这样,循环渲染的问题我们用2个timer就解决了,

这下应该OK了吧,但是还有个键盘鼠标的问题,

我们用OIS,

但是OIS在创建输入系统的时候,他要控制的窗口必须是顶层窗口,就是说你传view视图的窗口句柄给他不行,必须要MFC程序的主窗口,

所以又要多传一个参数了哇,算起来我们传了2个窗口参数了,一个MFC主窗口给OIS,一个view视图窗口作为渲染窗口

看看我传的参数:

app.setup(m_hWnd,rect.Width(),rect.Height(),AfxGetApp()->GetMainWnd()->GetSafeHwnd());

第4个参数就是MFC主窗口

参数有了,我们就需要把主窗口句柄传给OIS的OIS::InputManager

 // 获得输入系统

 size_t windowHnd = 0;

 std::ostringstream windowHndStr;

 OIS::ParamList pl;

 app->getRenderWindow()->getCustomAttribute("WINDOW", &windowHnd);

 windowHnd = (size_t )hMainWnd; // 这里这个窗口句柄就是我们传入的MFC主窗口

 windowHndStr << windowHnd;

 // OIS的窗口必须要顶层窗口,所以只有传MFC的主窗口给他,传view就不行

 pl.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str()));

 // 设置鼠标显示和非游戏独占,这样鼠标可以显示在屏幕上并可以移动到窗口外

 pl.insert(std::make_pair(std::string("w32_mouse"), std::string("DISCL_FOREGROUND" )));

 pl.insert(std::make_pair(std::string("w32_mouse"), std::string("DISCL_NONEXCLUSIVE")));

 // 键盘非游戏独占

 //pl.insert(std::make_pair(std::string("w32_keyboard"), std::string("DISCL_FOREGROUND")));

 //pl.insert(std::make_pair(std::string("w32_keyboard"), std::string("DISCL_NONEXCLUSIVE")));

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

 // 这样InputManager就建好了,但为了从键盘、鼠标、或是手柄中获得输入,你还必须创建这些对象:

 try

 {

  mKeyboard = static_cast<OIS::Keyboard*>(mInputManager->createInputObject(OIS::OISKeyboard, true));

  mMouse = static_cast<OIS::Mouse*>(mInputManager->createInputObject(OIS::OISMouse, true));

  //mJoy = static_cast<OIS::JoyStick*>(mInputManager->createInputObject(OIS::OISJoyStick, false));

 }

 catch (const OIS::Exception &e)

 {

  throw Exception(42, e.eText, "OgreDemo::setupInputSystem");

 }

 上面代码还有一个重要的地方,就是

 // 设置鼠标显示和非游戏独占,这样鼠标可以显示在屏幕上并可以移动到窗口外

 pl.insert(std::make_pair(std::string("w32_mouse"), std::string("DISCL_FOREGROUND" )));

 pl.insert(std::make_pair(std::string("w32_mouse"), std::string("DISCL_NONEXCLUSIVE")));

这样的话,你就可以看到鼠标了,还可以移出窗口外面任何地方,就像地图编辑器一样!

还可以用来关闭MFC,这样你退出按键都不用写了,

如果你还要用鼠标控制视角,可以这样写:

bool MyFrameListener::mouseMoved(const OIS::MouseEvent &e)

{

 if (e.state.buttonDown(OIS::MB_Left))

 {

  mCamNode->yaw(Degree(-mRotate * e.state.X.rel), Node::TS_PARENT);

  mCamNode->pitch(Degree(-mRotate * e.state.Y.rel), Node::TS_LOCAL);

 }

 return true;

}

这样你左键按下,就可以控制视角,不按下的话,就可以随意移动

至于MFC和 OGRE1.6的内存泄露问题,网上有些资料,

第一步,是卸载dll先后顺序的问题,让OgreMain_d.dll在mfc80d.dll之前析构,老外早就有分析了:

 i) in the General tab, switch "Use MFC in a shared DLL" to "Use Standard Windows Libraries"

 ii) in the C/C++/Preprocessor tab, add _AFXDLL to the preprocessor definitions

 iii) in the Linker/Input tab, add mfc80d.lib anywhere before OgreMain_d.lib

第二步, Ogre嵌入到MFC里面, 将会导致NEW等操作符的重载冲突, 你必须选择让Ogre或者MFC进行内存管理. 使用Ogre自己的MemoryManager,并且禁止调用MFC的DEBUG_NEW,这需要先 找到cpp中的以下行      #ifdef _DEBUG

     #define new DEBUG_NEW

     #endif 并用  #define OGRE_DEBUG_MEMORY_MANAGER 1代替 这样Ogre中会使用自己的new/delete,而不是调用vccrt中的_heap_alloc_debug .