之前写过一篇将cocos2dx-2.x版本集成到MFC的文章,现在到了cocos2dx-3.x版本,之前的方法已经行不通了,最近有点时间,研究了一下,算是做出来点样子,下面写一下步骤,算是做个笔记。
我采用的方案步骤很多,改动量也比较大, 对于那些期望只修改几行就能达到目的的人来说,让你们失望了-_-,但本着学习的目的,改的越多不就意味着你学的越多么:)
首先要改的不是cocos2dx,而是GLFW。原因是因为cocos2dx-3.x底层渲染是基于GLFW的,但GLFW是不支持渲染到控件的,所以要改一下它。
下载GLFW源码,地址:http://www.glfw.org/download.html
解压后用CMake生成win32工程,然后用vs打开,整个工程是这样的:
我们要修改的就是glfw这个项目。
GLFWAPI GLFWwindow* glfwCreateWindowEx(void* winHwnd,int width,int height,const char* title,GLFWmonitor* monitor,GLFWwindow* share);
然后在window.c中实现此函数
GLFWAPI GLFWwindow* glfwCreateWindowEx(void* winHwnd,GLFWwindow* share) { _GLFWfbconfig fbconfig; _GLFWctxconfig ctxconfig; _GLFWwndconfig wndconfig; _GLFWwindow* window; _GLFWwindow* prevIoUs; _GLFW_REQUIRE_INIT_OR_RETURN(NULL); if (width <= 0 || height <= 0) { _glfwInputError(GLFW_INVALID_VALUE,"Invalid window size"); return NULL; } fbconfig = _glfw.hints.framebuffer; ctxconfig = _glfw.hints.context; wndconfig = _glfw.hints.window; wndconfig.width = width; wndconfig.height = height; wndconfig.title = title; wndconfig.monitor = (_GLFWmonitor*)monitor; ctxconfig.share = (_GLFWwindow*)share; if (wndconfig.monitor) { wndconfig.resizable = GL_FALSE; wndconfig.visible = GL_TRUE; wndconfig.focused = GL_TRUE; } // Check the OpenGL bits of the window config if (!_glfwIsValidContextConfig(&ctxconfig)) return NULL; window = calloc(1,sizeof(_GLFWwindow)); window->next = _glfw.windowListHead; _glfw.windowListHead = window; window->videoMode.width = width; window->videoMode.height = height; window->videoMode.redBits = fbconfig.redBits; window->videoMode.greenBits = fbconfig.greenBits; window->videoMode.blueBits = fbconfig.blueBits; window->videoMode.refreshRate = _glfw.hints.refreshRate; window->monitor = wndconfig.monitor; window->resizable = wndconfig.resizable; window->decorated = wndconfig.decorated; window->autoIconify = wndconfig.autoIconify; window->floating = wndconfig.floating; window->cursorMode = GLFW_CURSOR_NORMAL; // Save the currently current context so it can be restored later prevIoUs = _glfwPlatformGetCurrentContext(); // Open the actual window and create its context if (!_glfwPlatformCreateWindowEx(winHwnd,window,&wndconfig,&ctxconfig,&fbconfig)) { glfwDestroyWindow((GLFWwindow*)window); _glfwPlatformMakeContextCurrent(prevIoUs); return NULL; } _glfwPlatformMakeContextCurrent(window); // Retrieve the actual (as opposed to requested) context attributes if (!_glfwRefreshContextAttribs(&ctxconfig)) { glfwDestroyWindow((GLFWwindow*)window); _glfwPlatformMakeContextCurrent(prevIoUs); return NULL; } // Verify the context against the requested parameters if (!_glfwIsValidContext(&ctxconfig)) { glfwDestroyWindow((GLFWwindow*)window); _glfwPlatformMakeContextCurrent(prevIoUs); return NULL; } // Clearing the front buffer to black to avoid garbage pixels left over // from prevIoUs uses of our bit of VRAM glClear(GL_COLOR_BUFFER_BIT); _glfwPlatformSwapBuffers(window); // Restore the prevIoUsly current context (or NULL) _glfwPlatformMakeContextCurrent(prevIoUs); if (wndconfig.monitor) { int width,height; _glfwPlatformGetWindowSize(window,&width,&height); window->cursorPosX = width / 2; window->cursorPosY = height / 2; _glfwPlatformSetCursorPos(window,window->cursorPosX,window->cursorPosY); } else { if (wndconfig.visible) { if (wndconfig.focused) _glfwPlatformShowWindow(window); else _glfwPlatformUnhideWindow(window); } } return (GLFWwindow*)window; return NULL; }
在internal.h中增加函数声明
int _glfwPlatformCreateWindowEx(void* handle,_GLFWwindow* window,const _GLFWwndconfig* wndconfig,const _GLFWctxconfig* ctxconfig,const _GLFWfbconfig* fbconfig);
在win32_window.c文件中实现此函数
int _glfwPlatformCreateWindowEx(void* handle,const _GLFWfbconfig* fbconfig) { int status; if (!createWindowEx(handle,wndconfig,ctxconfig,fbconfig)) return GL_FALSE; status = _glfwAnalyzeContext(window,fbconfig); if (status == _GLFW_RECREATION_IMPOSSIBLE) return GL_FALSE; if (window->monitor) { _glfwPlatformShowWindow(window); //if (!enterFullscreenMode(window)) // return GL_FALSE; } return GL_TRUE; }
static int createWindowEx( void* phandle,const _GLFWfbconfig* fbconfig) { window->win32.handle = (HWND)(phandle); if (!window->win32.handle) { _glfwInputError(GLFW_PLATFORM_ERROR,"Win32: Failed to create window"); return GL_FALSE; } if (_glfw_ChangeWindowMessageFilterEx) { _glfw_ChangeWindowMessageFilterEx(window->win32.handle,WM_DROPFILES,MSGFLT_ALLOW,NULL); _glfw_ChangeWindowMessageFilterEx(window->win32.handle,WM_COPYDATA,WM_COPYGLOBALDATA,NULL); } if (wndconfig->floating && !wndconfig->monitor) { SetWindowPos(window->win32.handle,HWND_TOPMOST,SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); } DragAcceptFiles(window->win32.handle,TRUE); if (!_glfwCreateContext(window,fbconfig)) return GL_FALSE; return GL_TRUE; }
编译,如果没有错误的话会生成新的库文件。这样glfw就修改完毕了,我们做的就是让glfw支持传入窗口句柄,并在句柄对应的窗体上渲染。
其实在这步做完后,就可以创建个MFC工程,在工程上添加个Picture控件,然后使用我们刚添加的函数渲染到控件中,但我们的目的是使用cocos2dx,这个就不做了,免得跑题:)
创建一个cocos2dx工程,名字随意,我起的名字是MFCTest。
将glfw的include\GLFW目录下的.h文件复制到MFCTest\cocos2d\external\glfw3\include\win32下。
将glfw的src\Debug中的glfw3.lib文件复制到MFCTest\cocos2d\external\glfw3\prebuilt\win32下。
接着在MFCTest下创建一个MFC工程,我起名叫MFCDialog
在这步选择对话框,简单方便:)
添加一个Picture控件
并将其ID改为IDC_PIC_COCOS2DX
给Picture控件添加变量,我起的名字是m_nPicCocos2dx
点击完成后,在MFCDialogDlg.h文件中会自动添加m_nPicCocos2dx的声明。
新生成的变量类型是CStatic,这个类无法完成我们的需求,所以我们要创建自己的类型。
右键工程=》添加=》类,选择MFC类,如图:点击添加,类名随意,我写的是CCocos2dxWin。点击完成。
将m_nPicCocos2dx这个变量的类型改为我们新添加的类。
接下来我们要实现CCocos2DXWin类,那么首先,我们要将cocos2dx的文件包含到工程中,
首先添加头文件,选择项目属性=》配置属性=》C/C++ =》常规 =》 附加包含目录,配置如图:
加了一大堆,也没管有用没用-_-!。
接下来设置链接库文件与路径,首先设置库搜索路径,选择项目属性=》配置属性=》链接器 =》常规 =》 附加库目录,配置如图:
然后设置需要链接的库,选择项目属性=》配置属性=》链接器 =》输入 =》 附加依赖项,如图:
最后要设置一下dll搜索路径,配置项目属性=》配置属性=》调试=》环境,如图:
将cocos2dx工程下的AppDelegate.cpp,HelloWorldScene.cpp,AppDelegate.h,HelloWorldScene.h复制并关联到工程下,将图片资源也都复制到工程下。
下面开始修改CCocos2dxWin类,打开Cocos2dxWin.h文件,修改成如下所示:
#pragma once #include "AppDelegate.h" #include "glfw3.h" // CCocos2dxWin class CCocos2dxWin : public CWnd { DECLARE_DYNAMIC(CCocos2dxWin) public: CCocos2dxWin(); virtual ~CCocos2dxWin(); BOOL createGLWin(); protected: AppDelegate app; GLFWmousebuttonfun _fnMouseFunc; GLFWcursorposfun _fnCursorFunc; protected: DECLARE_MESSAGE_MAP() public: afx_msg void OnTimer(UINT_PTR nIDEvent); afx_msg void OnLButtonDown(UINT nFlags,CPoint point); afx_msg void OnLButtonUp(UINT nFlags,CPoint point); afx_msg void OnMouseMove(UINT nFlags,CPoint point); };
其中createGLWin函数功能为初始化cocos2dx。
AppDelegate app为cocos2dx使用
GLFWmousebuttonfun _fnMouseFunc是GLFW鼠标点击事件的回调函数
GLFWcursorposfun_fnCursorFunc是GLFW鼠标移动事件回调函数下面四个函数是Dialog的消息处理函数,分别对应WM_TIMER,WM_LBUTTONDOWN,
WM_LBUTTONUP,WM_MOUSEMOVE消息。
首先实现createGLWin函数,别忘了添加cocos2dx相关的头文件
// Cocos2dxWin.cpp : 实现文件 // #include "stdafx.h" #include "MFCDialog.h" #include "Cocos2dxWin.h" #include "cocos2d.h" USING_NS_CC; #include "platform/desktop/CCGLViewImpl-desktop.h" // CCocos2dxWin IMPLEMENT_DYNAMIC(CCocos2dxWin,CWnd) CCocos2dxWin::CCocos2dxWin() { AllocConsole(); freopen("CONOUT$","w",stdout); } CCocos2dxWin::~CCocos2dxWin() { } BOOL CCocos2dxWin::createGLWin() { CRect rc; GetClientRect(&rc); cocos2d::Application::getInstance()->initInstance(GetSafeHwnd(),"",rc.right-rc.left,rc.bottom-rc.top); cocos2d::Application::getInstance()->run(GetSafeHwnd()); auto director = Director::getInstance(); auto glview = director->getOpenGLView(); _fnMouseFunc = static_cast<GLViewImpl*>(glview)->getMouseBtnFunc(); _fnCursorFunc = static_cast<GLViewImpl*>(glview)->getCursorFunc(); SetTimer(1,1,NULL); this->MoveWindow(rc.left,rc.top,rc.right,rc.bottom); return TRUE; }
此时可以看到有几个函数cocos2dx没有提供,这就需要我们修改cocos2dx,添加上这几个函数。
在cocos2dx工程中找到文件CCApplication-win32.h,在其中的Application类中添加如下几个函数:
int run(HWND hWnd); bool initInstance(HWND hWnd,LPCSTR szTitle,UINT width,UINT height); void renderWorld();
然后在类中添加变量:
LARGE_INTEGERm_nLast;
接下来在CCApplication-win32.cpp中实现这些函数,首先在文件最上端引入头文件
#include"../desktop//CCGLViewImpl-desktop.h"实现run函数
int Application::run(HWND hWnd) { PVRFrameEnableControlWindow(false); QueryPerformanceCounter(&m_nLast); initGLContextAttrs(); // Initialize instance and cocos2d. if (!applicationDidFinishLaunching()) { return 1; } auto director = Director::getInstance(); auto glview = director->getOpenGLView(); // Retain glview to avoid glview being released in the while loop glview->retain(); }
然后是initInstance函数
bool Application::initInstance(HWND hWnd,UINT height) { auto director = Director::getInstance(); auto glview = director->getOpenGLView(); if (!glview) { cocos2d::Rect rect; rect.origin = cocos2d::Vec2::ZERO; rect.size = cocos2d::Size(width,height); glview = GLViewImpl::create(hWnd,rect); director->setOpenGLView(glview); } return true; }
最后是renderWorld函数
void Application::renderWorld() { LARGE_INTEGER nNow; QueryPerformanceCounter(&nNow); if (nNow.QuadPart - m_nLast.QuadPart > _animationInterval.QuadPart) { m_nLast.QuadPart = nNow.QuadPart; auto director = Director::getInstance(); auto glview = director->getOpenGLView(); director->mainLoop(); glview->pollEvents(); } }
Application类修改完毕,现在可以看到initInstance函数中GLViewImpl类没有我们需要的create函数,这个需要我们修改GLViewImpl类。
定位到CCGLViewImpl-desktop.h,在GLViewImpl类中增加如下几个函数static GLViewImpl* create(HWND hWnd,Rect rect); bool initWithHWND(HWND hWnd,Rect rect,float frameZoomFactor); static GLFWmousebuttonfun getMouseBtnFunc(); static GLFWcursorposfun getCursorFunc();
在CCGLViewImpl-desktop.cpp中实现它们
首先实现create函数GLViewImpl* GLViewImpl::create(HWND hWnd,Rect rect) { auto ret = new (std::nothrow) GLViewImpl; if (ret && ret->initWithHWND(hWnd,rect,1.0)) { ret->autorelease(); return ret; } return nullptr; }
initWithHWND函数
bool GLViewImpl::initWithHWND(HWND hWnd,float frameZoomFactor) { setViewName("Custom HWND View"); _frameZoomFactor = frameZoomFactor; if (!glfwInit()) { return FALSE; } glfwWindowHint(GLFW_RESIZABLE,GL_FALSE); glfwWindowHint(GLFW_RED_BITS,_glContextAttrs.redBits); glfwWindowHint(GLFW_GREEN_BITS,_glContextAttrs.greenBits); glfwWindowHint(GLFW_BLUE_BITS,_glContextAttrs.blueBits); glfwWindowHint(GLFW_ALPHA_BITS,_glContextAttrs.alphaBits); glfwWindowHint(GLFW_DEPTH_BITS,_glContextAttrs.depthBits); glfwWindowHint(GLFW_STENCIL_BITS,_glContextAttrs.stencilBits); _mainWindow = glfwCreateWindowEx((void*)hWnd,rect.size.width*_frameZoomFactor,rect.size.height*_frameZoomFactor,_viewName.c_str(),nullptr,nullptr); glfwMakeContextCurrent(_mainWindow); setFrameSize(rect.size.width,rect.size.height); // check OpenGL version at first const GLubyte* glVersion = glGetString(GL_VERSION); if (utils::atof((const char*)glVersion) < 1.5) { char strComplain[256] = { 0 }; sprintf(strComplain,"OpenGL 1.5 or higher is required (your version is %s). Please upgrade the driver of your video card.",glVersion); MessageBox(strComplain,"OpenGL version too old"); return false; } initGlew(); // Enable point size by default. glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); return true; }getMouseBtnFunc和getCursorFunc函数
GLFWmousebuttonfun GLViewImpl::getMouseBtnFunc() { return GLFWEventHandler::onGLFWMouseCallBack; } GLFWcursorposfun GLViewImpl::getCursorFunc() { return GLFWEventHandler::onGLFWMouseMoveCallBack; }
接着定位到CCFileUtils-win32.cpp文件
修改_checkPath函数static void _checkPath() { if (0 == s_resourcePath.length()) { WCHAR pUtf16ExePath[512]; GetModuleFileName(NULL,pUtf16ExePath,512); // We need only directory part without exe WCHAR *pUtf16DirEnd = wcsrchr(pUtf16ExePath,L'\\'); char utf8ExeDir[CC_MAX_PATH] = { 0 }; int nNum = WideCharToMultiByte(CP_UTF8,pUtf16DirEnd-pUtf16ExePath+1,utf8ExeDir,sizeof(utf8ExeDir),nullptr); s_resourcePath = convertPathFormatToUnixStyle(utf8ExeDir); } }现在可以编译一下libcocos2d工程,如果没提示错误,恭喜你!
接着编译一下我们的MFC工程,很不幸,会得到几百个错误,分析原因,根源是指向CCApplicationProtocol.h文件中
ApplicationProtocol:: Platform:: OS_WINDOWS这个枚举,原因是OS_WINDOWS这个名字与windows某个头文件中的定义重复了,我们把cocos2dx中的OS_WINDOWS改为CC_OS_WINDOWS,并将其他使用此枚举的地方一并修改。
再次编译libcocos2d。
然后编译MFC工程,如果没有错误,那么离成功不远了。
还记得在CCocos2dxWin类中我们增加了四个消息处理函数么,实现它们的时候到了。
void CCocos2dxWin::OnTimer(UINT_PTR nIDEvent) { // TODO: 在此添加消息处理程序代码和/或调用默认值 cocos2d::Application::getInstance()->renderWorld(); CWnd::OnTimer(nIDEvent); } void CCocos2dxWin::OnLButtonDown(UINT nFlags,CPoint point) { // TODO: 在此添加消息处理程序代码和/或调用默认值 auto director = Director::getInstance(); auto glview = director->getOpenGLView(); _fnMouseFunc(static_cast<GLViewImpl*>(glview)->getWindow(),GLFW_MOUSE_BUTTON_LEFT,GLFW_PRESS,0); CWnd::OnLButtonDown(nFlags,point); } void CCocos2dxWin::OnLButtonUp(UINT nFlags,GLFW_RELEASE,0); CWnd::OnLButtonUp(nFlags,point); } void CCocos2dxWin::OnMouseMove(UINT nFlags,CPoint point) { // TODO: 在此添加消息处理程序代码和/或调用默认值 auto director = Director::getInstance(); auto glview = director->getOpenGLView(); _fnCursorFunc(static_cast<GLViewImpl*>(glview)->getWindow(),point.x,point.y); CWnd::OnMouseMove(nFlags,point); }
首先是两个用于判断坐标位置的函数
bool isPointInPictureWin(CPoint& pt); CPoint getPicturePoint(CPoint& pt);然后是几个消息处理函数
afx_msg void OnLButtonDown(UINT nFlags,CPoint point); afx_msg void OnLButtonUp(UINT nFlags,CPoint point); afx_msg void OnMouseMove(UINT nFlags,CPoint point);这几个函数分别对应WM_LBUTTONDOWN,WM_LBUTTONUP,WM_MOUSEMOVE消息。
bool CMFCDialogDlg::isPointInPictureWin(CPoint& pt) { CRect rc; m_nPicCocos2dx.GetWindowRect(&rc); ScreenToClient(&rc); if (pt.x >= rc.left && pt.x <= rc.right && pt.y >= rc.top && pt.y <= rc.bottom) return true; return false; } CPoint CMFCDialogDlg::getPicturePoint(CPoint& pt) { CRect rc; m_nPicCocos2dx.GetWindowRect(&rc); ScreenToClient(&rc); CPoint picPt; picPt.x = pt.x - rc.left; picPt.y = pt.y - rc.top; return picPt; } void CMFCDialogDlg::OnLButtonDown(UINT nFlags,CPoint point) { // TODO: 在此添加消息处理程序代码和/或调用默认值 if (isPointInPictureWin(point)) { CPoint pt = getPicturePoint(point); SendMessageA(m_nPicCocos2dx.GetSafeHwnd(),MK_LBUTTON,pt.y << 16 | pt.x); } CDialogEx::OnLButtonDown(nFlags,point); } void CMFCDialogDlg::OnLButtonUp(UINT nFlags,pt.y << 16 | pt.x); } CDialogEx::OnLButtonUp(nFlags,point); } void CMFCDialogDlg::OnMouseMove(UINT nFlags,WM_MOUSEMOVE,pt.y << 16 | pt.x); } CDialogEx::OnMouseMove(nFlags,point); }
这些函数的主要功能是判断点击区是否在控件范围内,是的话就将消息发送给控件。
现在,我们的MFC Dialog已经可以运行起来,并可以接收鼠标点击事件,如图
功能基本完成了,希望能对有此需求的人有所帮助。
本文使用开发环境为cocos2dx 3.6,VS2013,win8.1。