将Cocos2dX渲染到MFC窗口上
引用:http://www.cnblogs.com/windeer/archive/2012/11/18/2767750.html
引言
现在智能手机已经慢慢进入大众化,移动类应用开始火爆起来,游戏类应用更是占据了手机用户的大部分碎片时间。
现在手机开发游戏也逐渐流行开来,手机的平台目前主打是 Andoird、IOS和WindowPhone。Cocos2DX跨平台开发成为吸引手机开发商和独立游戏制作人的一大亮点。
Cocos2dX脱胎于Cocos2D,有优良的血统,成熟的框架,加上不错的效率,成为跨平台手机游戏开发的首选。
在游戏开发过程中,各种辅助工具的开发是难免的。下面的文章 http://www.cocos2dres.com/view.asp?id=55 中有 介绍网络上可以找到一些工具,其中一些需要收费。
如果您是制作一些小游戏,网上找到的那些工具,也许可以解决制作中的问题,但是如果您在制作大型游戏,您就不得不自己动手制作工具了。
本文介绍将Cocos2dX渲染到MFC窗口,是制作游戏工具所需的一些知识。
COCOS2DX开发环境
本文的Cocos2DX的版本是 cocos2d-2.0-x-2.0.3, 点击进入下载页面 www.cocos2d-x.org/projects/cocos2d-x/wiki/Download
Cocos2DX下载后以及windows下配置这里就不赘述了 相关教程链接 http://www.cocos2dres.com/post/7.html win7下 VS2010教程
在理解了 helloCPP后,我们就可以进行下一步了。
MFC工程
第一步,创建MFC工程,修改工程属性
打开 cocos2d-win32.vc2010.sln 解决方案,添加一个MFC单文档程序。不会添加MFC工程的同学,到网上找找教程。
以下是工程(工程名叫MFCTest)截图:
其中 Cocos2DApp是自己添加的。
在工程属性面板,
图中红圈处设置成现HelloCPP一样就可以了。调试的工作路径,需要设置成自己的路径。
第二步,分析HelloCpp下的Windows窗口
查看main.cpp的代码:
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { UNREFERENCED_PARAMETER(hPrevInstance); UNREFERENCED_PARAMETER(lpCmdLine); // create the application instance AppDelegate app; CCEGLView* eglView = CCEGLView::sharedOpenGLView(); eglView->setFrameSize( 960, 640 ); return CCApplication::sharedApplication()->run(); }
这里首先创建一个app,然后设置窗口大小,然后就是run()了。Windows的窗口在哪里创建的呢?
eglView应当是与窗口相关的,我们看看setFrameSize的相关实现:
void CCEGLView::setFrameSize(float width, float height) { Create((LPCTSTR)m_szViewName, (int)width, (int)height ); CCEGLViewProtocol::setFrameSize(width, height); }
首先映入眼帘就是Create方法,看来windows窗口的创建就在这个方法里面:
bool CCEGLView::Create(LPCTSTR pTitle, int w, int h) { bool bRet = false; if( hWnd == NULL ) { do { CC_BREAK_IF(m_hWnd); HINSTANCE hInstance = GetModuleHandle( NULL ); WNDCLASS wc; // Windows Class Structure // Redraw On Size, And Own DC For Window. wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; wc.lpfnWndProc = _WindowProc; // WndProc Handles Messages wc.cbClsExtra = 0; // No Extra Window Data wc.cbWndExtra = 0; // No Extra Window Data wc.hInstance = hInstance; // Set The Instance wc.hIcon = LoadIcon( NULL, IDI_WINLOGO ); // Load The Default Icon wc.hCursor = LoadCursor( NULL, IDC_ARROW ); // Load The Arrow Pointer wc.hbrBackground = NULL; // No Background Required For GL wc.lpszMenuName = NULL; // We Don't Want A Menu wc.lpszClassName = kWindowClassName; // Set The Class Name CC_BREAK_IF(! RegisterClass(&wc) && 1410 != GetLastError()); // center window position RECT rcDesktop; GetWindowRect(GetDesktopWindow(), &rcDesktop); WCHAR wszBuf[50] = {0}; MultiByteToWideChar(CP_UTF8, 0, m_szViewName, -1, wszBuf, sizeof(wszBuf)); // create window m_hWnd = CreateWindowEx( WS_EX_APPWINDOW | WS_EX_WINDOWEDGE, // Extended Style For The Window kWindowClassName, // Class Name wszBuf, // Window Title WS_CAPTION | WS_POPUPWINDOW | WS_MINIMIZEBOX, // Defined Window Style 0, 0, // Window Position 0, // Window Width 0, // Window Height NULL, // No Parent Window NULL, // No Menu hInstance, // Instance NULL ); CC_BREAK_IF(! m_hWnd); resize(w, h); bRet = initGL(); CC_BREAK_IF(!bRet); s_pMainWindow = this; bRet = true; } while (0); }return bRet; }
果不出我所料,我们看到了熟悉的CreateWindowEx,然后调用initGL()初始化OpenGL相关。
我们再看看 initGL的实现:
bool CCEGLView::initGL() { m_hDC = GetDC(m_hWnd); // 拿到窗口的DC SetupPixelFormat(m_hDC); // 设置像素格式 //SetupPalette(); m_hRC = wglCreateContext(m_hDC); /// 创建OpenGLES渲染环境 wglMakeCurrent(m_hDC, m_hRC); /// 设置当前渲染环境 GLenum GlewInitResult = glewInit(); if (GLEW_OK != GlewInitResult) { fprintf(stderr,"ERROR: %s\n",glewGetErrorString(GlewInitResult)); return false; } if (GLEW_ARB_vertex_shader && GLEW_ARB_fragment_shader) { CCLog("Ready for GLSL\n"); } else { CCLog("Not totally ready :( \n"); } if (glewIsSupported("GL_VERSION_2_0")) { CCLog("Ready for OpenGL 2.0\n"); } else { CCLog("OpenGL 2.0 not supported\n"); } return true; }
这样 Windows窗口就和OpenGLES的渲染环境关联起来了。
我们如何将MFC的窗口和OpenGLES的渲染环境关联起来呢?
MFC窗口是预先已经创建好的,我们只要在创建的时候,用已经创建好的窗口来做 initGL应当就可以实现。于是我将CCEGLView的Create和SetFrameSize做了调整,可以 传入窗口句柄。新的函数代码如下:
void CCEGLView::setFrameSize(float width, float height,HWND hWnd)/// 将窗口句柄通过参数传进来 { Create((LPCTSTR)m_szViewName, (int)width, (int)height, hWnd); CCEGLViewProtocol::setFrameSize(width, height); } bool CCEGLView::Create(LPCTSTR pTitle, int w, int h, HWND hWnd) { bool bRet = false; if( hWnd == NULL ) /// 如果等于 NULL 则按以前的方式走 { do { CC_BREAK_IF(m_hWnd); HINSTANCE hInstance = GetModuleHandle( NULL ); WNDCLASS wc; // Windows Class Structure // Redraw On Size, And Own DC For Window. wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; wc.lpfnWndProc = _WindowProc; // WndProc Handles Messages wc.cbClsExtra = 0; // No Extra Window Data wc.cbWndExtra = 0; // No Extra Window Data wc.hInstance = hInstance; // Set The Instance wc.hIcon = LoadIcon( NULL, IDI_WINLOGO ); // Load The Default Icon wc.hCursor = LoadCursor( NULL, IDC_ARROW ); // Load The Arrow Pointer wc.hbrBackground = NULL; // No Background Required For GL wc.lpszMenuName = NULL; // We Don't Want A Menu wc.lpszClassName = kWindowClassName; // Set The Class Name CC_BREAK_IF(! RegisterClass(&wc) && 1410 != GetLastError()); // center window position RECT rcDesktop; GetWindowRect(GetDesktopWindow(), &rcDesktop); WCHAR wszBuf[50] = {0}; MultiByteToWideChar(CP_UTF8, 0, m_szViewName, -1, wszBuf, sizeof(wszBuf)); // create window m_hWnd = CreateWindowEx( WS_EX_APPWINDOW | WS_EX_WINDOWEDGE, // Extended Style For The Window kWindowClassName, // Class Name wszBuf, // Window Title WS_CAPTION | WS_POPUPWINDOW | WS_MINIMIZEBOX, // Defined Window Style 0, 0, // Window Position 0, // Window Width 0, // Window Height NULL, // No Parent Window NULL, // No Menu hInstance, // Instance NULL ); CC_BREAK_IF(! m_hWnd); resize(w, h); bRet = initGL(); CC_BREAK_IF(!bRet); s_pMainWindow = this; bRet = true; } while (0); } else { m_hWnd = hWnd; /// 直接将外部窗口当做当前窗口 bRet = initGL(); /// 初始化OpenGLES相关 } return bRet; }
底层已经支持将外部窗口传入作为渲染窗口了,那我们就只要做相应的调整就可以了。
第三步,添加Cocos2DApp
在工程中添加 类 CCocos2DApp继承自 cocos2d::CCApplication, 这就相当于HelloCPP中的 AppDelegate.之所以在这里自己实现,是想将MFC的窗口句柄交给
Cocos2D的渲染层。
CCocos2DApp需要在AppDelegate的基础上,添加两个方法,代码如下:
1 /** 2 @brief 系统初始化 3 @param uWnd 窗口句柄 4 @param nWidth 窗口宽 5 @param nHeight 窗口高 6 */ 7 void CCocos2DApp::init( uint32 uWnd, int32 nWidth, int32 nHeight )/// uint32 int32 是自定义数据类型 就是int 和 unsinged int 8 { 9 m_uWnd = uWnd; 10 11 cocos2d::CCEGLView* eglView = cocos2d::CCEGLView::sharedOpenGLView(); 12 if( eglView == NULL ) 13 { 14 return; 15 } 16 17 eglView->setFrameSize(nWidth, nHeight, (HWND)m_uWnd); /// 这里调用已经修改的cocos2d::CCEGLView方法 18 19 // Initialize instance and cocos2d. 20 if (!applicationDidFinishLaunching()) 21 { 22 return; 23 } 24 25 n_bCocos2DInit = true; 26 } 27 //------------------------------------------------------------------------------ 28 /** 29 @brief 系统运行 重载基类的run方法 30 @param 31 */ 32 int CCocos2DApp::run() 33 { 34 if( m_uWnd == 0 ) 35 { 36 return cocos2d::CCApplication::run(); /// 如果是引擎底层创建窗口,在消息循环在这里执行,在消息循环中执行mainLoop 37 } 38 else 39 { 40 if( n_bCocos2DInit) 41 { 42 cocos2d::CCDirector::sharedDirector()->mainLoop(); 43 } 44 45 return 1; 46 } 47 }
再把HelloWorldScene添加到工程。运行HelloCPP需要的图片资源,也需要复制到工作目录下。
第四步 将CCocos2DApp添加到CMFCTestView中
在CMFCTestView.h中添加
CCocos2DApp m_Cocos2DApp;
我们希望在窗口初始后,调用CCocos2DApp的init方法,实现渲染环境初始化。
切换到类视图,选择属性,选择重写(Overide)标签,找到OnInitialUpdate,添加重写CMFCTestView::InitUpdate方法
在其中添加代码,调用CCocos2DApp::init,代码如下:
1 void CMFCTestView::OnInitialUpdate() 2 { 3 CView::OnInitialUpdate(); 4 5 // TODO: 在此添加专用代码和/或调用基类 6 7 RECT rc; 8 ::GetClientRect( m_hWnd, &rc ); 9 10 m_Cocos2DApp.init( (uint32)m_hWnd, rc.right - rc.left, rc.bottom - rc.top ); /// 传入窗口句柄和宽高 11 SetTimer( 1, 30, NULL ); /// 见下面的分析 12 }
在之前分析中已经提到,HelloCpp的Render是在消息循环中执行的,MFC窗口接管消息循环,我们Render放在哪里比较好呢?因为我们是用来制作工具,对渲染效率要求不是很高,所以我想用定时器来驱动我们的Render。所以在初始化的时候启动了一个定时器。
在CMFCTestView中添加定时器处理方法:
1 // CMFCTestView 消息处理程序 2 void CMFCTestView::OnTimer(UINT_PTR nIDEvent) 3 { 4 // TODO: 在此添加消息处理程序代码和/或调用默认值 5 m_Cocos2DApp.run(); /// 这里执行mainLoop,在mainLoop中执行Render 6 CView::OnTimer(nIDEvent); 7 }
编译运行应当就可以看到可爱的Cocoser了,如下图
画面是渲染出来了,可是却收不到鼠标消息。原来引擎实现了CCEGLView::WindowProc用来处理Windows消息,系统在这里模拟了触屏的一些操作。那我们只要将MFC的窗口消息传递给CCEGLView::WindowProc就可以实现了。
在CMFCTestView中添加重写(Ovreide)函数WindProc,代码如下:
/** @brief 窗口回调函数 @param */ LRESULT CCocos2DApp::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) { cocos2d::CCEGLView* eglView = cocos2d::CCEGLView::sharedOpenGLView(); if( eglView ) { return eglView->WindowProc( message, wParam, lParam ); } return 0; }
这样就可以正常收到windows窗口消息了。
还有一个需要注意的问题,当窗口大小发生变化,我们也需要通知到引擎底层,所以我们需要再添加一个针对WM_SIZE的处理函数,函数代码如下:
1 void CMFCTestView::OnSize(UINT nType, int cx, int cy) 2 { 3 CView::OnSize(nType, cx, cy); 4 5 // TODO: 在此处添加消息处理程序代码 6 m_Cocos2DApp.OnSize( cx, cy ); 7 }
在CCocos2DApp::OnSize中再对底层进行相关的设置,我们看下代码:
/** @brief 重置大小 @param */ void CCocos2DApp::OnSize( int nWidth, int nHeight ) { // TODO: 在此处添加消息处理程序代码 cocos2d::CCEGLView* eglView = cocos2d::CCEGLView::sharedOpenGLView(); if( eglView ) {if( n_bCocos2DInit ) { //重新设置窗口大小及投影矩阵 cocos2d::CCEGLView::sharedOpenGLView()->resize(nWidth,nHeight); cocos2d::CCDirector::sharedDirector()->reshapeProjection(cocos2d::CCSizeMake(nWidth,nHeight)); } } }
至此,你已经看到怎样将Cocos2DX渲染到MFC窗口的全过程,相信聪明的你也一定可以制作你自己想要的游戏工具,最后希望大家Coding happy!!!