cocos2d-x 3.x嵌入MFC单文档视图

关于windows下制作cocos2d-x的工具用到的最多的就是MFC,相关的MFC嵌入cocos2d-x的文章也非常多,不过主要是来用的cocos2d-x 2.x的版本,而且有个问题是所见过的解决方案都是用glfw做一个子窗口然后挂在父窗口上,就是用的::setParent()这个方法。考虑到这个方法在表现效果以及用法上都有局限,所以想说下关于在Single Document上用OpenGL渲染。好处就是表现上会有很大的提升,在窗口放大缩小的时候也有很好的表现。同时也把cocos2d-x的版本提升到3.x,目前最高版本是3.7,但是我做这个工具的时候还是3.2的时候,所以这里就用的3.2,不过想来差别应该不会大。
具体效果如图所示:
result

创建新的GLView

在cocos2d-x 3.x中glview都是通过GLFW来处理事件,以及创建windows窗口的,而在2.x中是直接通过windows api CreateWindow()的,然后设置事件循环处理事件。所以可以仿造2.x的这种方试来创建glview.而其中最重要的东西就是窗口句柄,通过传递窗口句柄,初始化openGL,然后得到渲染区域。所以继承了CCGLView.得到MFCGLView ,其中最关建的初始化过程如下:

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
40
41
//MFCGLView.h
#ifndef __MFC_GLVIEW__H_
#define __MFC_GLVIEW__H_
#include "cocos2d.h"
#include "cocos-ext.h"
#include "CCGLView.h"
NS_CC_BEGIN

class MFCGLView : public GLView
{
public:
//创建MFCGLView
static MFCGLView* create(HWND hWnd);
//构造析构函数
MFCGLView(void);
virtual ~MFCGLView(void);
//自定义GLView必须重写的函数
virtual bool isOpenGLReady();
virtual void end();
virtual void swapBuffers();
virtual void setFrameSize(float width, float height);
virtual void setIMEKeyboardState(bool bOpen);
//初始化GLView
virtual bool init(HWND hWnd);
//窗口消息处理
virtual LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam);
private:
//初始化OpenGL
bool InitGL();
//释放OpenGL
void ReleaseGL();
HWND m_hWnd; //View对应的窗口句柄
HDC m_hDC; //窗口绘图设备
HGLRC m_hRC; //OpenGL渲染环境

bool m_bCaptured; //是否捕捉鼠标
float m_fFrameZoomFactor; //帧缩放比例
};
NS_CC_END

#endif // !__MFC_GLVIEW__H_

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
40
41
42
43
44
45
bool MFCGLView::InitGL()
{
//初始化窗口像素格式
PIXELFORMATDESCRIPTOR pfd =
{
sizeof(PIXELFORMATDESCRIPTOR), // size
1, // version
PFD_SUPPORT_OPENGL | // OpenGL window
PFD_DRAW_TO_WINDOW | // render to window
PFD_DOUBLEBUFFER, // support double-buffering
PFD_TYPE_RGBA, // color type
32, // preferred color depth
0, 0, 0, 0, 0, 0, // color bits (ignored)
0, // no alpha buffer
0, // alpha bits (ignored)
0, // no accumulation buffer
0, 0, 0, 0, // accum bits (ignored)
24, // depth buffer
8, // no stencil buffer
0, // no auxiliary buffers
PFD_MAIN_PLANE, // main layer
0, // reserved
0, 0, 0, // no layer, visible, damage masks
};

m_hDC = GetDC(m_hWnd); //获取渲染区域,m_hWnd是成员变量,通过创建的时候传入
int pixelFormat = ChoosePixelFormat(m_hDC, &pfd);
SetPixelFormat(m_hDC, pixelFormat, &pfd);

//创建渲染环境
m_hRC = wglCreateContext(m_hDC);
wglMakeCurrent(m_hDC, m_hRC);

//初始化openGL其它信息
//.......
return true;
}

void MFCGLView::swapBuffers() //缓存交换
{
if (m_hDC != NULL)
{
::SwapBuffers(m_hDC);
}
}

新的AppDelegate

Appdelegate和原来的没有太多的区别,替换原来的CCGLView,建立循环,还有一些事件的支持,比如MFC窗口大小变化的时候的事件OnSize(),具体如下:

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
40
41
42
43
44
45
46
47
48
49
50
51
52
void AppDelegate::init(HWND uWind, int32_t nWidth, int32_t nHeight)
{
m_uWnd = uWind;

auto director = Director::getInstance();
auto glview = director->getOpenGLView();
if(!glview) {
glview = MFCGLView::create(uWind);
director->setOpenGLView(glview);
}
if (!applicationDidFinishLaunching())
{
return;
}
cocosInited = true;
}

int AppDelegate::run()
{
if (!m_uWnd)
return Application::run();
else
{
if (cocosInited)
Director::getInstance()->mainLoop();
return 1;
}
}

void AppDelegate::onSize(int nWidth, int nHeight)
{
auto glview = Director::getInstance()->getOpenGLView();
if (glview)
{
if (cocosInited)
{
glview->setFrameSize(nWidth, nHeight);
}
}
}

void AppDelegate::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
auto glview = (MFCGLView*)Director::getInstance()->getOpenGLView();
if (glview)
{
if (cocosInited)
{
glview->WindowProc(message, wParam, lParam);
}
}
}

接入到MFC中

最后关键一步就是接入到MFC中,创建一个新的单文档的MFC程序,然后在对应的视图入口(OnInitialUpdate()函数)处加入一个AppDelegate,然后取得文档区域的句柄,然后新建一个timer用于整个cocos2dx的循环,根据帧率的需要设置调用频率,以及接入事件。例如:

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
void CsceneEditorView::OnInitialUpdate()
{
CView::OnInitialUpdate();
RECT rc;
::GetClientRect(m_hWnd, &rc);
m_app.init(m_hWnd, rc.right - rc.left, rc.bottom - rc.top);
SetTimer(1, 15, NULL);
// TODO: 在此添加专用代码和/或调用基类
}



void CsceneEditorView::OnTimer(UINT_PTR nIDEvent)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
m_app.run();
CView::OnTimer(nIDEvent);
}


void CsceneEditorView::OnSize(UINT nType, int cx, int cy)
{
CView::OnSize(nType, cx, cy);
m_app.onSize(cx, cy);
// TODO: 在此处添加消息处理程序代码
}


LRESULT CsceneEditorView::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
// TODO: 在此添加专用代码和/或调用基类
m_app.WindowProc(message, wParam, lParam);
return CView::WindowProc(message, wParam, lParam);
}

这样整个程序就能跑起来,然后可以后续建立更多的通用工具,例如cocos中的控件选中框什么的。以及CString到string的转换工具,这些都会在工具中经常用到。另外如果再有点想法把Lua接进去也是可以的。还有多文档的话也可以,不过要用到多视口。原理也差不多。
再来一张图看关闭一些Dock后,文档区域就会变大,这样就会显得比较高大上了。
anotherview

补上github地址:
https://github.com/enhhh/cocos2dx-MFC