菜鳥學習OGRE和天龍八部之一:OGRE+MFC+OIS

來源:互聯網
上載者:User

作為一個還沒進入遊戲行業的菜鳥,進入遊戲行業一直是我的夢想,

有一天,我深深被天龍八部那漂亮的情境所吸引,決定自己實現一下.也算是對得起我自己的夢想,

但是貌似很難,前路漫漫兮, 吾將上下而求索...

 

一步一步來吧...

 

折磨了2天,終於成功把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* /*pDC*/)
{
 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 .

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.