本文歡迎轉載,但是必須得保住原文地址 http://www.cocos2dres.com/view.asp?id=63 否則將追究責任。
引言
現在智能手機已經慢慢進入大眾化,移動類應用開始火爆起來,游戲類應用更是占據了手機用戶的大部分碎片時間。
現在手機開發游戲也逐漸流行開來,手機的平台目前主打是 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!!!