作为一个还没进入游戏行业的菜鸟,进入游戏行业一直是我的梦想,

有一天,我深深被天龙八部那漂亮的场景所吸引,决定自己实现一下.也算是对得起我自己的梦想,

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

一步一步来吧...

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

作为一个还没进入游戏行业的菜鸟,进入游戏行业一直是我的梦想,

有一天,我深深被天龙八部那漂亮的场景所吸引,决定自己实现一下.也算是对得起我自己的梦想,

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

一步一步来吧...

折磨了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 .

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

  1. Ogre 编辑器二(用Ogre的地形组件加载天龙八部地形)

    主界面如上文设计完成后,场景刚开始添加了是Ogre例子里的,发现场景里实物太少,于是想到直接把天龙的场景拿下来,天龙网上有源码,参考了下,把天龙的地形用Ogre的地形组件完成了下,如下是效果图: 因为 ...

  2. Spark菜鸟学习营Day5 分布式程序开发

    Spark菜鸟学习营Day5 分布式程序开发 这一章会和我们前面进行的需求分析进行呼应,完成程序的开发. 开发步骤 分布式系统开发是一个复杂的过程,对于复杂过程,我们需要分解为简单步骤的组合. 针对每 ...

  3. oracle菜鸟学习之 分析函数-排序

    oracle菜鸟学习之 分析函数-排序 排序函数 1.row_number:返回连续的排序,无论值是否相等 2.rank:具有相等值得行排序相同,序数值随后跳跃 3.dense_rank:具有相等值得 ...

  4. MFC 学习笔记(一):MFC单文档程序运行流程梳理与总结

    MFC 学习笔记(一):MFC单文档程序运行流程梳理与总结 1.MFC单文档程序运行流程 1.首先利用全局变量对象 theApp 启动应用程序 (这是因为这个全局对象,基类CWinApp中 this ...

  5. oracle菜鸟学习之 复杂的更新语句使用

    oracle菜鸟学习之 复杂的更新语句使用 实例与答案 问题:表T1里有a,b,c...N个字段,表T2里有a,b,c三个字段,然后想在T1中"c"与表T2中"c&quo ...

  6. oracle创建自身连接,oracle菜鸟学习之 自连接查询实验

    oracle菜鸟学习之 自连接查询实验 实验表的创建 表字段说明: id:员工编号 name:员工名字 ano:管理人员编号 create table admin(id varchar2(4),nam ...

  7. 菜鸟学习笔记:Java提升篇12(Java动态性2——动态编译、javassist字节码操作)

    菜鸟学习笔记:Java提升篇12(Java动态性2--动态编译.javassist字节码操作) Java的动态编译 通过脚本引擎执行代码 Java字节码操作 JAVAssist的简单使用 常用API ...

  8. 菜鸟学习笔记:Java提升篇11(Java动态性1——注解与反射)

    Java提升篇11(Java其它高级特性--注解与反射) 注解(Annotation) JDK内置注解 自定义注解 元注解(meta-annotation) 反射(reflection) 动态语言 反 ...

  9. 菜鸟学习笔记:Java提升篇10(网络2——UDP编程、TCPSocket通信、聊天室案例)

    菜鸟学习笔记:Java提升篇10(网络2--UDP编程.TCPSocket通信) UDP编程 TCP编程(Socket通信) 单个客户端的连接 多个客户端的连接(聊天室案例) UDP编程 在上一篇中讲 ...

最新文章

  1. linux中的while命令
  2. redis主从复制如何保证数据一致性_面试官:Redis 主从复制时网络开小差了怎么整?...
  3. 机器学习资料升级版来了!!!
  4. SQL2000数据库中删除“坏表”的方法
  5. YouTube怎么判断影片内含侵权内容? 解析Content ID内容识别系统的原理及功能
  6. 游戏开发过程中需求变化那些事
  7. 防止对SQL Server的蛮力攻击
  8. 恐怖logo效果展示AE模板
  9. 读懂才会用 : 瞅瞅Redis的epoll模型
  10. VS Code Material Icon Theme插件设置自定义文件夹图标关联
  11. python怎么变白-Python将png透明变为白色并保存为jpg图片
  12. 在新加坡旅游过一个令你心跳加速的情人节
  13. 转自【MDCC技术大咖秀】Android内存优化之OOM
  14. pid是滞后超前校正_如何理解超前补偿、滞后补偿、超前滞后补偿?
  15. linux arm xenomai,Wiki - Xenomai
  16. @OneToMany---ManyToOne
  17. 未来一年西藏旅行时间表,此生必去一次。
  18. 索引的使用以及常见索引类型,组合索引的具体使用方法。
  19. windows桌面图标或状态栏图标显示空白或无法正常显示
  20. #Reading Paper# 【序列推荐综述】IJCAI‘19:Sequential Recommender Systems: Challenges, Progress and Prospects

热门文章

  1. 个人微信开发协议sdk接口API分享
  2. 设置Ajax为同步请求
  3. 背包问题之多重背包基础写法
  4. virt-viewer的简单使用
  5. STATA闪退,CLDS数据无法转码
  6. RFID技术如何让图书馆实现自助借还,自助盘点
  7. 校园网限速引起网络知识:网络配置ipconfig /release ipconfig /renew
  8. 报错:The path is not a valid path to the xxx kernel headers.
  9. 轩小陌的Python笔记-day28 索引、函数及存储过程
  10. mac下idea选中多个相同内容的快捷键