[Cocos2d-x相关教程来源于红孩儿的游戏编程之路 CSDN博客地址:http://blog.csdn.net/honghaier]
本节所用Cocos2d-x版本:cocos2d-1.0.1-x-0.12.0
我在之前的“如何利用Cocos2d-x开发一个游戏?”。在实际的项目开发过程中,工具是支撑开发流程规范的必要条件。一套好的工具可以使工作流程顺畅。从而极大的提高效率。我们知道Unity3d场景编辑器就是一个很好的例子。它将游戏开发中的各个流程节点的工作都集成到编辑器中,这是一个很COOL的想法。对于游戏开发团队来说,只需要掌握好这个流程,使用编辑器做好每一步的编辑工作就可以快速开发出高品质的游戏。
但对于Cocos2d-x来说。虽然网络上有一些开放的工具,但零零散散的不成系统。并不方便使用。这一章来简要介绍一下如何使用MFC来进行Cocos2d-x相关工具的开发。请对MFC不熟悉的朋友先学习一下基本的对话框程序再进行本章内容的学习。
第一节:将Cocos2d-x嵌入MFC的子窗体中
[注:学习这一章之前请先学习Cocos2d-x 的“HelloWorld” 深入分析一文]
首先,我们用VC++在Cocos2d-x的目录里建立了个Unicode字符集MFC对话框程序。这里命名为Cocos2dXEditor。按照HelloWorld工程设置把包含头文件目录,库文件目录,包含库都设置好。并且画好对话框界面。
如图:
我把界面设计为三部分,左边和右边用来创建对话框面板,至于要具体显示什么根据我们的工具开发需求而定。暂时先不管。而中间放一个Picture 控件,做为Cocos2d-x的显示窗口。为了生成相应的窗口控件变量,我们需要将此Picture控件的ID改成自定义的一个ID,这里我们改成IDC_COCOS2DXWIN保存。
我们画好界面后,选中Picture控件后右键单击,在弹出菜单中找到“添加变量”。为Picture控件生成一个控件变量。这里命名为m_Cocos2dXWin,并点击完成。
好,现在主对话框类里有了这么一句:
- public:
- CStatic m_Cocos2dXWin;
这个变量是CStatic类型的,它只是一个最简单的CWnd派生类。并不能显示Cocos2d-x。我们需要做点工作来创建一个新的类来替代它完成显示Cocos2d-x的工作。
在工程右键弹出菜单里找到“添加”一项,在其子菜单中点击“类”。
在弹出的“添加类”对话框中“MFC”项中的“MFC类”。
点击“添加”。这时会弹出“MFC类向导”对话框。我们在类名里输入“CCocos2dXWin”,并选择基类CWnd。然后点击“完成”。
向导会为我们的工程自动加入两个文件“Cocos2dXWin.h”和“Cocos2dXWin.cpp”。这才是我们要进行Cocos2d-x显示的窗体类,它的基类是CWnd,与Picture控件有相同的基类。
打开Cocos2dXWin.h,在CCocos2dXWin类中增加一个public成员函数声明:
-
- BOOL CreateCocos2dXWindow();
我们另外增加一个private变量用来标记窗口是否已经进行了Cocos2d-x的OpenGL窗口创建。
- private:
-
- BOOL m_bInitCocos2dX;
在CPP文件中加入需要用到的头文件
- #include "CCEGLView_win32.h"
- #include "../Classes/AppDelegate.h"
- #include "cocos2d.h"
下面来手动增加函数定义
- /创建Cocos2dX窗口
- BOOL CCocos2DXWin::CreateCocos2dXWindow()
- {
-
- CRect tClientRect;
- GetClientRect(&tClientRect);
-
- cocos2d::CCApplication::sharedApplication().run(GetSafeHwnd(),TEXT("第一个Cocos2d-x程序"),tClientRect.Width(),tClientRect.Height());
-
- m_bInitCocos2dX = TRUE;
- return TRUE;
- }
现在还不能运行。因为CCApplication的run函数跟本就没有HWND参数,我们必须对CCApplication动个小手术。使它能够接收到窗体的句柄并使用这个句柄来创建OpenGL。
找到CCApplication_win32.cpp中的run函数做以下修改:
- [Cocos2d-x相关教程来源于红孩儿的游戏编程之路 CSDN博客地址:http:
-
- int CCApplication::run(HWND hWnd,LPCTSTR szTitle,UINT wWidth,UINT wHeight)
- {
- PVRFrameEnableControlWindow(false);
-
-
- MSG msg;
- LARGE_INTEGER nFreq;
- LARGE_INTEGER nNow;
-
- QueryPerformanceFrequency(&nFreq);
-
- QueryPerformanceCounter(&m_nLast);
-
-
- if (! initInstance(hWnd,szTitle,wWidth,wHeight) || ! applicationDidFinishLaunching())
- {
- return 0;
- }
-
- CCEGLView& mainWnd = CCEGLView::sharedOpenGLView();
-
- if(NULL == hWnd)
- {
- mainWnd.centerWindow();
- ShowWindow(mainWnd.getHWnd(), SW_SHOW);
-
- while (1)
- {
- if (! PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
- {
-
- QueryPerformanceCounter(&nNow);
-
-
- if (nNow.QuadPart - m_nLast.QuadPart > m_nAnimationInterval.QuadPart)
- {
- m_nLast.QuadPart = nNow.QuadPart;
- CCDirector::sharedDirector()->mainLoop();
- }
- else
- {
- Sleep(0);
- }
- continue;
- }
-
- if (WM_QUIT == msg.message)
- {
-
- break;
- }
-
-
- if (! m_hAccelTable || ! TranslateAccelerator(msg.hwnd, m_hAccelTable, &msg))
- {
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
- }
-
- return (int) msg.wParam;
- }
- return 0;
- }
修改
.h
中的函数声明与cpp一致并保存,然后打开
CCApplication
的派生类
AppDelegate
的
initInstance
函数。做同样的修改工作。
- #if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)
- CCEGLView * pMainWnd = new CCEGLView();
- CC_BREAK_IF(! pMainWnd
- || ! pMainWnd->Create(hWnd,szTitle, wWidth, wHeight));
-
- #endif // CC_PLATFORM_WIN32
修改.h中
函数声明与
cpp
保持一致
,再打开CCEGL_View_win32.cpp,找到Create函数,继续手术:
- bool CCEGLView::Create(HWND hWnd,LPCTSTR pTitle, int w, int h)
- {
- bool bRet = false;
- do
- {
-
- if(hWnd)
- {
- m_hWnd = hWnd ;
- }
- else
- {
- CC_BREAK_IF(m_hWnd);
-
- HINSTANCE hInstance = GetModuleHandle( NULL );
- WNDCLASS wc;
-
-
- wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
- wc.lpfnWndProc = _WindowProc;
- wc.cbClsExtra = 0;
- wc.cbWndExtra = 0;
- wc.hInstance = hInstance;
- wc.hIcon = LoadIcon( NULL, IDI_WINLOGO );
- wc.hCursor = LoadCursor( NULL, IDC_ARROW );
- wc.hbrBackground = NULL;
- wc.lpszMenuName = NULL;
- wc.lpszClassName = kWindowClassName;
-
- CC_BREAK_IF(! RegisterClass(&wc) && 1410 != GetLastError());
-
-
- RECT rcDesktop;
- GetWindowRect(GetDesktopWindow(), &rcDesktop);
-
-
- m_hWnd = CreateWindowEx(
- WS_EX_APPWINDOW | WS_EX_WINDOWEDGE,
- kWindowClassName,
- pTitle,
- WS_CAPTION | WS_POPUPWINDOW | WS_MINIMIZEBOX,
- 0, 0,
- 0,
- 0,
- NULL,
- NULL,
- hInstance,
- NULL );
-
- CC_BREAK_IF(! m_hWnd);
- }
- m_eInitOrientation = CCDirector::sharedDirector()->getDeviceOrientation();
- m_bOrientationInitVertical = (CCDeviceOrientationPortrait == m_eInitOrientation
- || kCCDeviceOrientationPortraitUpsideDown == m_eInitOrientation) ? true : false;
- m_tSizeInPoints.cx = w;
- m_tSizeInPoints.cy = h;
- resize(w, h);
-
-
- m_pEGL = CCEGL::create(this);
-
- if (! m_pEGL)
- {
- DestroyWindow(m_hWnd);
- m_hWnd = NULL;
- break;
- }
-
- s_pMainWindow = this;
- bRet = true;
- } while (0);
-
- return bRet;
- }
这样我们就完成了对于创建窗口函数的修改。但是因为屏蔽了原窗口的创建,所以也同时屏蔽了Cocos2d-x对于窗口消息的处理。我们在类视图中找到CCocos2dXWin,在右键弹出菜单中点击“属性”。打开类属性编辑框。
在WindowProc一栏中增加函数WindorProc,完成后进入CCocos2dXWin的WindorProc函数,因为我们在CCEGLView的Create函数中可以找到在注册窗口类时,其设定的窗口消息处理回调函数是WindowProc.而其实例对象是单件。故可以这样修改:
- LRESULT CCocos2DXWin::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
- {
-
- if(m_bInitCocos2dX)
- {
- CCEGLView::sharedOpenGLView().WindowProc(message, wParam, lParam);
- }
- return CWnd::WindowProc(message, wParam, lParam);
- }
这样Cocos2dXWin所实例化的窗口就可以接收到鼠标等对于窗口的消息了。但是!我们屏幕了Cocos2d-x的消息循环,而Cocos2d-x的渲染处理函数是在消息循环中调用的。我们就算现在运行程序,也看不到帅气的小怪物。所以我们必须想办法实时的调用Cocos2d-x的渲染处理函数。对于工具来说,我们并不需要考虑太高的FPS刷新帧数,所以我们只需要使用一个定时器,并在定时触发函数中进行Cocos2d-x的渲染处理函数的调用就可以了。
在CCocos2DXWin::CreateCocos2dXWindow()函数尾部加入一句:
加入一个ID为1的定时器,设置它每1毫秒响应一次,其处理函数为默认,则使用CCocos2DXWin的WINDOWS消息处理函数对于WM_TIMER进行响应就可以了。
按之前增加WindorProc函数的方式找到CCocos2DXWin的消息WM_TIMER一项,增加函数OnTimer.如图:
在其生成的函数OnTimer中我们新写一个函数调用,则每次定时响应调用
- void CCocos2DXWin::OnTimer(UINT_PTR nIDEvent)
- {
-
- cocos2d::CCApplication::sharedApplication().renderWorld();
- CWnd::OnTimer(nIDEvent);
- }
我们打开CCApplication_win32.h,为CCApplication类增加一个public的renderWorld函数。
在cpp中我们将消息循环中的相关处理移入进来
- bool CCApplication::renderWorld()
- {
- LARGE_INTEGER nNow;
-
- QueryPerformanceCounter(&nNow);
-
-
- if (nNow.QuadPart - m_nLast.QuadPart > m_nAnimationInterval.QuadPart)
- {
- m_nLast.QuadPart = nNow.QuadPart;
- CCDirector::sharedDirector()->mainLoop();
- return true;
- }
- return false;
- }
OK,这样我们就可以在外部来调用Cocos2d-x的显示设备进行渲染处理了。当然,我们也不能忘了在CCocos2dXWin窗口销毁时把定时器资源进行一下释放。找到类消息WM_DESTROY新增函数OnDestroy。
- void CCocos2DXWin::OnDestroy()
- {
-
- if(TRUE == m_bInitCocos2dX)
- {
-
- m_bInitCocos2dX = FALSE;
-
- KillTimer(1);
- CWnd::OnDestroy();
- }
- }
我们在“HelloWorld”的代码分析中可以知道在Cocos2d-x程序退出时先后响应WM_CLOSE和WM_DESTROY。在CCEGLView::WindowProc 可以看到
- case WM_CLOSE:
- CCDirector::sharedDirector()->end();
- break;
-
- case WM_DESTROY:
- PostQuitMessage(0);
- break;
而CCDirector的end函数并不是立即执行停止,他设置成员变量m_bPurgeDirecotorInNextLoop为true,并在下一帧mainLoop循环时调用purgeDirector()函数进行显示设备和场景的最终释放。所以我们要想在窗口退出时释放Cocos2d-x显示设备和场景,必须做一下相关处理。理解了这一点。其实就很简单了。
只需要在CCocos2DXWin::OnDestroy()中增加两句代码即可:
- void CCocos2DXWin::OnDestroy()
- {
- …
- KillTimer(1);
-
- CCDirector::sharedDirector()->end();
-
- CCDirector::sharedDirector()->mainLoop();
- CWnd::OnDestroy();
- }
- }
OK,CCocos2DXWin类基本完成。现在在Cocos2dXEditorDlg.h中将
改为
- CCocos2DXWin m_Cocos2dXWin;
将CCocos2DXWin的头文件包含进来,然后在控件初始化(比如OnInitDialog函数)中调用一下m_Cocos2dXWin.CreateMainCoco2dWindow()。编译成功后就算完成了将Cocos2d-x嵌入MFC的CWnd控件的过程。不过,您需要在窗口大小变化时对这个控件重设位置及大小以使窗口始终上下充满空间。这些属于MFC的基础知识,在此不再赘述。
最后运行一下看下成果吧。下面是我运行的结果(:左边面板稍加制做~):