强行在MFC窗体中渲染Cocos2d-x 3.6
【前言】
把Cocos2dx渲染到另一個應用程序框架中的方法,在2.x時代有很多大神已經實現了,而3.x的做法網上幾乎找不著。這兩天抽空強行折騰了一下,不敢獨享,貼出來供大家參考。
?
【已知存在的問題】
程序退出時會發生非常嚴重的內存泄漏,博主檢查了很久,但技術不夠暫時無法解決。如果有大神能搞定,求告知一下做法,謝謝!
在程序從開始運行到關閉期間,有且僅有一個cocos2dx窗體存在時可以選擇性無視內存泄漏。如果非常在意這一點,建議使用cocos2d-x 2.2.6這個版本,放在MFC中的內存泄漏很小。
*使用VLD檢查泄漏會報錯
?
【為什么要這么做】
在進行游戲開發途中,多多少少會用到一些輔助工具,比如CocosStudio。但是在更多的時候,CocosStudio并不能以不變應萬變(比如在博文《我用Cocos2d-x制作〈Love Live!學院偶像祭〉的Live場景》中提到的譜面編輯器的功能,CocoStudio無法做到)。在這種情況下,開發人員就需要一款針對當前項目而設計的工具。
如果輔助工具需要提供豐富的界面和控件,純用Cocos2d-x來制作就會十分雞肋。比如這個打開文件的控件:
當然,一定要做的話用cocos2dx也是可以做的,但是相當麻煩。如果有興趣可以自己嘗試寫一下,提高自己的姿勢水平。
所以這個時候應當把cocos2dx層放在一個提供了各種控件的應用程序框架里面,cocos2dx僅用于做顯示,其余的數據操作交由框架完成。
目前博主比較熟悉的框架是MFC和C# Winform。說實話C# Winform做窗體比MFC方便快捷太多。但是如果使用C# Winform就得去做C#調用C++,同時對于某些特定參數(比如string到const char*的轉換)必須做特殊處理,比較麻煩,否則DLL堆棧會出錯。而MFC不存在這個問題。
?
【核心思想】
Cocos2dx在Windows上運行起來是一個窗口,那么在其內部一定調用了CreateWindowEx這個API。那么只要我們找到這個API,把參數設為子窗口,并把父窗口的句柄傳進去,就可以達到要求。創建出來的窗體就是父窗體中的子窗體了。
還要注意一點是cocos2dx原生程序有一個自己的消息循環,如果直接調用Application::run會導致MFC層卡死,我們需要把消息循環交給框架的主線程來操作。
流程圖如下:
【需要的工具】
1、??? 安裝了MFC組件的Visual Studio 2013
2、??? Cocos2d-x 3.6
3、??? GLFW (下載地址:點我)
4、??? CMake(下載地址:點我)
?
【操作步驟】
1、 ? ?創建項目
創建一個MFC項目(我使用的對話框型)。注意在向導中“MFC的使用”這一項要選擇“在共享DLL中使用MFC”:
?
2、 ? ?拷貝必要文件
把cocos2dx的源碼和模板項目中的Classes和Resources文件夾拷貝到項目目錄下(項目模板位于引擎目錄\templates\cpp-template-default下),一定要使用這個結構:
?
3、 ? ?修改項目屬性
打開MFC項目解決方案,在屬性管理器(視圖——屬性管理器)中為項目添加cocos2dx的兩個屬性表。屬性表位于解決方案目錄\cocos2d\cocos\2d:
然后將libcocos2d,libbox2d,libspine加入解決方案中,并把libcocos2d設為MFC項目的依賴項:
再在MFC項目的附加包含目錄中加入:
$(EngineRoot)cocos\audio\include
$(EngineRoot)external
$(EngineRoot)external\chipmunk\include\chipmunk
$(EngineRoot)extensions
..\Classes
..
%(AdditionalIncludeDirectories)
$(_COCOS_HEADER_WIN32_BEGIN)
$(_COCOS_HEADER_WIN32_END)
預處理器定義中加入:
COCOS2D_DEBUG=1
?
附加庫目錄中加入:
$(_COCOS_LIB_PATH_WIN32_BEGIN)
$(_COCOS_LIB_PATH_WIN32_END)
附加依賴項加入:
$(_COCOS_LIB_WIN32_BEGIN)
$(_COCOS_LIB_WIN32_END)
libcocos2d.lib
?
再修改項目屬性——工作目錄,以及生成目錄:
再將Classes下的所有文件加入MFC項目:
最后設置不使用預編譯頭,不然每加入一個類都得加上#include “stdafx.h”,麻煩:
?
4、 修改GLFW ??
Cocos2dx 2.x中創建窗口在CCEGLView類中完成,直接修改它就行。到3.x后使用glfw管理窗口,CreateWindowEx被封裝進去了。而cocos2dx并沒有附帶glfw的源碼,只有頭文件和lib文件。所以我們需要下載glfw的源碼進行修改。
用CMakeGUI打開GLFW,source code處選擇下下來的glfw解壓的文件夾,build the binaries選擇生成解決方案的文件夾,然后生成對應VS版本的解決方案(glfw解壓的文件夾不要刪除):
然后打開生成的sln,查找CreateWindowEx,修改它所在的函數(win32_window.c,633行):
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | static?int?createWindow(_GLFWwindow* window, ????????????????????????const?_GLFWwndconfig* wndconfig, ????????????????????????const?_GLFWctxconfig* ctxconfig, ????????????????????????const?_GLFWfbconfig* fbconfig, ????????????????????????HWND?parent)?// 父窗體句柄 { ????int?xpos, ypos, fullWidth, fullHeight; ????WCHAR* wideTitle; ????window->win32.dwStyle = WS_CHILDWINDOW | WS_VISIBLE;?// 子窗體樣式 ????window->win32.dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; ????? ????xpos = 0; ????ypos = 0; ????fullWidth = wndconfig->width; ????fullHeight = wndconfig->height;??? ????wideTitle = _glfwCreateWideStringFromUTF8(wndconfig->title); ????if?(!wideTitle) ????{ ????????_glfwInputError(GLFW_PLATFORM_ERROR, ????????????????????????"Win32: Failed to convert window title to UTF-16"); ????????return?GL_FALSE; ????} ????window->win32.handle = CreateWindowExW(window->win32.dwExStyle, ???????????????????????????????????????????_GLFW_WNDCLASSNAME, ???????????????????????????????????????????wideTitle, ???????????????????????????????????????????window->win32.dwStyle, ???????????????????????????????????????????xpos, ypos, ???????????????????????????????????????????fullWidth, fullHeight, ???????????????????????????????????????????parent,?// 傳入父窗體句柄 ???????????????????????????????????????????NULL,?// No window menu ???????????????????????????????????????????GetModuleHandleW(NULL), ???????????????????????????????????????????window);?// Pass object to WM_CREATE ????// ????// ... } |
然后從內向外依次修改調用它的地方:
win32_window.c,769行
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | int?_glfwPlatformCreateWindow(_GLFWwindow* window, ??????????????????????????????const?_GLFWwndconfig* wndconfig, ??????????????????????????????const?_GLFWctxconfig* ctxconfig, ??????????????????????????????const?_GLFWfbconfig* fbconfig, ??????????????????????????????HWND?parent) { ????// ... ????// ????if?(!createWindow(window, wndconfig, ctxconfig, fbconfig, parent)) ????????return?GL_FALSE; ????// ... ????// ????????if?(!createWindow(window, wndconfig, ctxconfig, fbconfig, parent)) ????????????return?GL_FALSE; ????// ????// ... } |
internal.h,524行
| 1 2 3 4 5 | int?_glfwPlatformCreateWindow(_GLFWwindow* window, ??????????????????????????????const?_GLFWwndconfig* wndconfig, ??????????????????????????????const?_GLFWctxconfig* ctxconfig, ??????????????????????????????const?_GLFWfbconfig* fbconfig, ??????????????????????????????HWND?parent); |
window.c,116行
| 1 2 3 4 5 6 7 8 9 10 11 12 | GLFWAPI GLFWwindow* glfwCreateWindow(int?width,?int?height, ?????????????????????????????????????const?char* title, ?????????????????????????????????????GLFWmonitor* monitor, ?????????????????????????????????????GLFWwindow* share, ?????????????????????????????????????int?parent) { ????// ... ????//? ????if?(!_glfwPlatformCreateWindow(window, &wndconfig, &ctxconfig, &fbconfig, (HWND)parent))? ????// ????// ... } |
glfw3.h,1645行:
| 1 | GLFWAPI GLFWwindow* glfwCreateWindow(int?width,?int?height,?const?char* title, GLFWmonitor* monitor, GLFWwindow* share,?int?parent); |
改好后使用MinSizeRel選項進行編譯,編譯好后在GLFW解決方案目錄\src\MinSizeRel下找到glfw3.lib文件,連同glfw3.h(在glfw解壓目錄\include\GLFW)一起,分別放入MFC項目解決方案目錄\cocos2d\external\glfw3\prebuilt\win32 和 MFC項目解決方案目錄\cocos2d\external\glfw3\include\win32下覆蓋原文件。
?
5、 ? ?修改Cocos層
在GLViewImpl類(3.2中是GLView類)的頭文件中加入一個方法和成員:
| 1 2 3 4 5 | public: ????static?void?SetParent(HWND?parent){ m_sParent = parent; } private: ????static?HWND?m_sParent; |
別忘了在cpp中加入
| 1 | HWND?GLViewImpl::m_sParent = NULL; |
然后修改GLViewImpl::initWithRect方法,修改調用glfwCreateWindow的地方:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | bool?GLViewImpl::initWithRect(const?std::string& viewName, Rect rect,?float?frameZoomFactor) { ????// ... ????// ????_mainWindow = glfwCreateWindow(rect.size.width * _frameZoomFactor, ???????????????????????????????????rect.size.height * _frameZoomFactor, ???????????????????????????????????_viewName.c_str(), ???????????????????????????????????_monitor, ???????????????????????????????????nullptr, ???????????????????????????????????(int)m_sParent);?// 傳入父窗口句柄 ????// ????// ... } |
修改Application類的run方法,去掉里面的消息循環:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | int?Application::run() { ????PVRFrameEnableControlWindow(false); ????initGLContextAttrs(); ????// Initialize instance and cocos2d. ????if?(!applicationDidFinishLaunching()) ????{ ????????return?1; ????} ????// Retain glview to avoid glview being released in the while loop ????Director::getInstance()->getOpenGLView()->retain(); ????return?0; } |
6、 ? 編輯MFC窗體
接下來在MFC窗體中添加一個Picture Control控件,控件ID設為IDC_RENDERWND,然后選中控件(非常蛋疼的是只能在控件邊框處點擊才能選中)點右鍵——“添加變量”:
?
7、添加渲染類
在解決方案資源管理器中的MFC項目上點右鍵——“添加”——“類…”,添加一個MFC類:
然后修改類:
+ View Code實現:
+ View Code然后將剛才綁定的控件m_RenderWnd的類型由CStatic改為CRenderWnd,并在主窗體的OnInitDialog方法中加入一行:
| 1 2 3 4 5 6 7 8 9 10 | BOOL?CCocos2dxMFCDlg::OnInitDialog() { ????????// ... ????????// ????// TODO:? 在此添加額外的初始化代碼 ????????this->m_RenderWnd.Initialize();?? ????return?TRUE;??// 除非將焦點設置到控件,否則返回 TRUE } |
?
8、運行起來
理論上要做的操作已經做完了,現在只需要編譯就能運行起來。然而觸控會這么好心地做好事不留坑嘛?
當然不會了~傳說cocos系列的坑連起來可以繞地球多少圈來著,這里噗通一下就入坑了,不信你F5一下:
這什么鬼?!其實是ApplicationProtocol中Platform枚舉中的一個值和MFC的某個宏同名了。解決方法是在stdafx.h中加入這樣一句:
| 1 | #undef OS_WINDOWS |
然后繼續編譯。當然是坑不單行,又報錯:
不過這個簡單,根據報錯內容,在項目的預處理器定義中加入_CRT_SECURE_NO_WARNINGS。
按理說最后是不是應該出現一個BOSS級深坑來著?BOSS來了:此時編譯可以通過了,但是一運行必然報錯。看看輸出窗口:
嗷,原來是找不到文件。但是我們之前已經設置了工作目錄,Resources下面也有文件啊(這個坑在2.2.6中并沒有)。
從Label::createWithTTF一路追蹤下去,最后發現cocos2dx搜索文件的目錄是在這里設置的(CCFileUtils-win32.cpp 59行):
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | static?void?_checkPath() { ????if?(0 == s_resourcePath.length()) ????{ ????????WCHAR?*pUtf16ExePath =?nullptr; ????????_get_wpgmptr(&pUtf16ExePath); ????????// We need only directory part without exe ????????WCHAR?*pUtf16DirEnd = wcsrchr(pUtf16ExePath, L'\\'); ????????char?utf8ExeDir[CC_MAX_PATH] = { 0 }; ????????int?nNum = WideCharToMultiByte(CP_UTF8, 0, pUtf16ExePath, pUtf16DirEnd-pUtf16ExePath+1, utf8ExeDir,?sizeof(utf8ExeDir),?nullptr,?nullptr); ????????s_resourcePath = convertPathFormatToUnixStyle(utf8ExeDir); ????} } |
_get_wpgmptr是個嘛玩意?查一下可以知道,這個函數用于取得進程exe所在的目錄。
我們再看看cocos2dx 2.2.6中對應的部分(CCFileUtilsWin32.cpp 34行):
| 1 2 3 4 5 6 7 8 9 10 11 | static?void?_checkPath() { ????if?(! s_pszResourcePath[0]) ????{ ????????WCHAR??wszPath[MAX_PATH] = {0}; ????????int?nNum = WideCharToMultiByte(CP_ACP, 0, wszPath, ????????????GetCurrentDirectoryW(sizeof(wszPath), wszPath), ????????????s_pszResourcePath, MAX_PATH, NULL, NULL); ????????s_pszResourcePath[nNum] =?'\\'; ????} } |
很明顯,2.2.6中使用GetCurrentDirectoryW獲取當前目錄的,使用這個函數就能獲取正確的工作目錄了。為什么用cocos new出來的3.6項目沒這個問題?因為new出來的項目的預鏈接事件中最后有這么一句:
這個命令會把Resources下的所有文件拷貝到輸出目錄(也就是進程exe所在的目錄)下,自然不會出現找不到文件的問題了。
不知道這么做的意義和目的是什么?但是此時我想說:
我還想說:
修改的方法很簡單,參考2.2.6把_checkPath中_get_wpgmptr函數改為GetCurrentDirectoryW:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | static?void?_checkPath() { ????if?(0 == s_resourcePath.length()) ????{ ????????char?pathBuffer[MAX_PATH] = { 0 }; ????????WCHAR??wszPath[MAX_PATH] = { 0 }; ????????int?nNum = WideCharToMultiByte(CP_ACP, 0, wszPath, ????????????GetCurrentDirectory(sizeof(wszPath), wszPath), ????????????pathBuffer, MAX_PATH, NULL, NULL); ????????pathBuffer[nNum] =?'\\'; ????????s_resourcePath = pathBuffer; ????} } |
⑨、最后的小修改
如果你用的MFC窗體是一個Dialog類型的,運行后會發現按回車或Esc后窗體直接關閉了。所以還需要屏蔽掉回車和Esc鍵的響應。在MFC對話框類中添加一個方法重寫PreTranslateMessage:
| 1 2 | private: ????virtual?BOOL?PreTranslateMessage(MSG* pMsg); |
實現:
| 1 2 3 4 5 6 7 8 9 10 11 | BOOL?CCocos2dxMFCDlg::PreTranslateMessage(MSG* pMsg) { ????if?(pMsg->message == WM_KEYDOWN) ????{ ????????if?(pMsg->wParam == VK_ESCAPE || pMsg->wParam == VK_RETURN) ????????{ ????????????return?TRUE; ????????} ????} ????return?CDialogEx::PreTranslateMessage(pMsg); } |
【運行起來】
如果編譯沒有出錯的話,運行起來會看到這個樣子:
只要將接口留出來,就可以很方便地通過MFC層的控件來控制cocos層了。至于要做成一個什么樣的工具,全靠大家發揮咯~
?
【后記】
采用這套思路理論上可以把cocos渲染到任何一個支持調用C++層代碼的框架中。
需要渲染在C# Winform中的童鞋請看這篇博客,里面有講處理方法及string到const char*的轉換。
文章轉載自http://www.cnblogs.com/GuyaWeiren/p/4600937.html,感謝博主的分享。
總結
以上是生活随笔為你收集整理的强行在MFC窗体中渲染Cocos2d-x 3.6的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ashx需要everyone权限
- 下一篇: html标签和css参数