欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Cocos2d-X 2.0嵌入MFC的子窗体的方法(1.0姐妹篇)

程序员文章站 2022-07-14 23:03:21
...

红孩儿的游戏编程之路

CSDN博客地址:http://blog.csdn.net/honghaier

红孩儿Cocos2d-X学习园地:249941957 加群写:Cocos2d-x

另本章为我的Cocos2d-x教程一书初稿。望各位看官多提建议!

Cocos2d-X 2.0嵌入MFC的子窗体的方法(1.0姐妹篇)

本节所用Cocos2d-x版本:cocos2d-2.0-x-2.0.2

之前讲解了如何将Cocos2d-x 1.0版本嵌入MFC的窗体中的具体方法,应朋友们的要求,将Cocos2d-x 2.0版本嵌入MFC窗体的方法也整理发布。

首先,我们用VC++Cocos2d-x的目录里建立了个Unicode字符集MFC对话框程序。这里命名为Cocos2dXEditor。按照HelloWorld工程设置把包含头文件目录,库文件目录,包含库都设置好。并且画好对话框界面

如图:

Cocos2d-X 2.0嵌入MFC的子窗体的方法(1.0姐妹篇)

我把界面设计为三部分,左边和右边用来创建对话框面板,至于要具体显示什么根据我们的工具开发需求而定。暂时先不管。而中间放一个Picture控件,做为Cocos2d-x的显示窗口。为了生成相应的窗口控件变量,我们需要将此Picture控件的ID改成自定义的一个ID,这里我们改成IDC_COCOS2DXWIN保存。

我们画好界面后,选中Picture控件后右键单击,在弹出菜单中找到添加变量。为Picture控件生成一个控件变量。这里命名为m_Cocos2dXWin,并点击完成。

Cocos2d-X 2.0嵌入MFC的子窗体的方法(1.0姐妹篇)

好,现在主对话框类里有了这么一句:

public:
         CStatic      m_Cocos2dXWin;

这个变量是CStatic类型的,它只是一个最简单的CWnd派生类。并不能显示Cocos2d-x。我们需要做点工作来创建一个新的类来替代它完成显示Cocos2d-x的工作。

在工程右键弹出菜单里找到添加一项,在其子菜单中点击

Cocos2d-X 2.0嵌入MFC的子窗体的方法(1.0姐妹篇)

在弹出的添加类对话框中“MFC”项中的“MFC

Cocos2d-X 2.0嵌入MFC的子窗体的方法(1.0姐妹篇)


点击“添加”。这时会弹出“MFC类向导”对话框。我们在类名里输入“CCocos2dXWin”,并选择基类CWnd。然后点击“完成”。

Cocos2d-X 2.0嵌入MFC的子窗体的方法(1.0姐妹篇)

向导会为我们的工程自动加入两个文件“Cocos2dXWin.h”和“Cocos2dXWin.cpp”。这才是我们要进行Cocos2d-x显示的窗体类,它的基类是CWnd,与Picture控件有相同的基类。

打开Cocos2dXWin.h,在CCocos2dXWin类中增加一个public成员函数声明:

//创建Cocos2dX窗口
BOOL        CreateCocos2dXWindow();

我们另外增加一个private变量用来标记窗口是否已经进行了Cocos2d-xOpenGL窗口创建。

private:
	//是否已经初始化
	BOOL				m_bInitCocos2dX;

CPP文件中加入需要用到的头文件:

#include "../Classes/AppDelegate.h"
#include "cocos2d.h"

下面来手动增加函数定义:

//创建Cocos2dX窗口
BOOL	CCocos2DXWin::CreateCocos2dXWindow()
{
	//新建一个CRect变量获取窗口的客户区大小
	CRect	tClientRect;
	GetClientRect(&tClientRect);
	//取得使用的OpenGL视窗
	CCEGLView* eglView = CCEGLView::sharedOpenGLView();
	//以指定的客户区大小创建视窗,这里我们对setFrameSize增加了参数3以传入当前控件的窗口句柄。
	eglView->setFrameSize(tClientRect.Width(),tClientRect.Height(),GetSafeHwnd());
	//调用程序的运行函数,增加参数bool型变量控制是否进行消息循环。因为MFC控件本身有自已的消息响应处理。如果不改动的话,这里就会进入死循环。
	cocos2d::CCApplication::sharedApplication()->run(false);
	//这里将变量设置为TRUE
	m_bInitCocos2dX = TRUE;
	return TRUE;
}

我们要修改两处地方。我们先对CCApplication动个小手术,使相应参数能够发挥作用。

找到CCApplication.cpp中的run函数做以下修改:

[Cocos2d-x相关教程来源于红孩儿的游戏编程之路 CSDN博客地址:http://blog.csdn.net/honghaier]  

int CCApplication::run(bool bMsgLoop)
{
    PVRFrameEnableControlWindow(false);

    // Main message loop:
    MSG msg;
    LARGE_INTEGER nFreq;
    LARGE_INTEGER nNow;
    //帧定时器取得CPU时钟频率和计数
    QueryPerformanceFrequency(&nFreq);
    QueryPerformanceCounter(&m_nLast);

    // 调用派生类的程序启动处理函数
    if (!applicationDidFinishLaunching())
    {
        return 0;
    }
      CCEGLView* pMainWnd = CCEGLView::sharedOpenGLView();
	//手动修改
     if(true == bMsgLoop)
	{
		//窗口居中显示
		mainWnd->centerWindow();
		ShowWindow(mainWnd->getHWnd(), SW_SHOW);

		while (1)
		{
			if (! PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
			{
				// Get current time tick.
				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)
			{
				// Quit message loop.
				break;
			}

			// 按键消息处理
			if (! m_hAccelTable || ! TranslateAccelerator(msg.hwnd, m_hAccelTable, &msg))
			{
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
		}

		return (int) msg.wParam;
	}
	return 0;
}

修改.h函数声明与cpp保持一致,再打开CCEGLView.cpp,找到setFrameSize函数,继续手术:

void CCEGLView::setFrameSize(float width, float height,HWND hWnd)
{
        //由指定的大小和句柄创建窗体
   	Create((LPCTSTR)m_szViewName, (int)width, (int)height,hWnd);
    	//调用基类的setFrameSize函初始化整屏幕和分辨率
        CCEGLViewProtocol::setFrameSize(width, height);
}

再找到Create函数:

//创建窗口
bool CCEGLView::Create(LPCTSTR pTitle, int w, int h, HWND hWnd)
{
	bool bRet = false;
	do 
	{
		//如果已经创建了窗体,直接返回,不允许再重复创建。
		CC_BREAK_IF(m_hWnd);
		//在这里做个判断,如果参数中的窗口句柄不为空,则使用参数句柄做为成员变量m_hWnd的值。
		if(hWnd)
		{
			m_hWnd = hWnd ;
			//新增bool变量m_bIsPopupWin,用于标记是否使用已经创建好的WINDOWS控件窗口句柄做为当前OpenGL视窗的WINDOWS窗口句柄。
            m_bIsPopupWin = false;
		}
		else
		{

			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);
		 //初始化OpenGL
        		 bRet = initGL();
       		 CC_BREAK_IF(!bRet);
        		 s_pMainWindow = this;
bRet = true;
	} while (0);

	return bRet;
}

修改.h函数声明与cpp保持一致。这样我们就完成了对于创建窗口函数的修改。但是因为屏蔽了原窗口的创建,所以也同时屏蔽了Cocos2d-x对于窗口消息的处理。我们在类视图中找到CCocos2dXWin,在右键弹出菜单中点击属性。打开类属性编辑框。

Cocos2d-X 2.0嵌入MFC的子窗体的方法(1.0姐妹篇)


WindowProc一栏中增加函数WindorProc,完成后进入CCocos2dXWinWindorProc函数,因为我们在CCEGLViewCreate函数中可以找到在注册窗口类时,其设定的窗口消息处理回调函数是WindowProc.而其实例对象是单件。故可以这样修改:

LRESULT CCocos2DXWin::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
	// 这里我们先判断一下是否初始化完成Cocos2dX再进行消息处理
	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()函数尾部加入一句:

SetTimer(1,1,NULL);


加入一个ID1的定时器,设置它每1毫秒响应一次,其处理函数为默认,则使用CCocos2DXWinWINDOWS消息处理函数对于WM_TIMER进行响应就可以了。

按之前增加WindorProc函数的方式找到CCocos2DXWin的消息WM_TIMER一项,增加函数OnTimer.如图:


Cocos2d-X 2.0嵌入MFC的子窗体的方法(1.0姐妹篇)


在其生成的函数OnTimer中我们新写一个函数调用,则每次定时响应调用:

void CCocos2DXWin::OnTimer(UINT_PTR nIDEvent)
{
	//我们写一个renderWorld函数代表Cocos2d-x的世界渲染
	cocos2d::CCApplication::sharedApplication()->renderWorld();
	CWnd::OnTimer(nIDEvent);
}

我们打开CCApplication.h,CCApplication类增加一个publicrenderWorld函数。

//帧循环调用渲染
	bool	renderWorld();

cpp中我们将消息循环中的相关处理移入进来

bool CCApplication::renderWorld()
{   
	LARGE_INTEGER nNow;
	// Get current time tick.
	QueryPerformanceCounter(&nNow);

	// If it's the time to draw next frame, draw it, else sleep a while.
	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

Cocos2d-X 2.0嵌入MFC的子窗体的方法(1.0姐妹篇)


void CCocos2DXWin::OnDestroy()
{
	//在Cocos2d-x的引擎文件CCEGLView_win32.cpp中CCEGLView::release()会再次发送调用DestroyWindow,所以这里用变量m_bInitCocos2dX做下判断,避免二次销毁
	if(TRUE == m_bInitCocos2dX)
	{
		//退出将m_bInitCocos2dX设为FALSE
		m_bInitCocos2dX = FALSE;
		//释放定时器资源
		KillTimer(1);
		CWnd::OnDestroy();
	}
}

我们知道在Cocos2d-x程序退出时先后响应WM_CLOSEWM_DESTROY。在CCEGLView::WindowProc可以看到


	case WM_CLOSE:
		CCDirector::sharedDirector()->end();
		break;

	case WM_DESTROY:
                destroyGL();
		PostQuitMessage(0);
		break;

CCDirectorend函数并不是立即执行停止,他设置成员变量m_bPurgeDirecotorInNextLooptrue,并在下一帧mainLoop循环时调用purgeDirector()函数进行显示设备和场景的最终释放。所以我们要想在窗口退出时释放Cocos2d-x显示设备和场景,必须做一下相关处理。理解了这一点。其实就很简单了。

只需要在CCocos2DXWin::OnDestroy()中增加两句代码即可:

void CCocos2DXWin::OnDestroy()
{
	…
	KillTimer(1);
	//调用显示设备单件实例对象的end函数
	CCDirector::sharedDirector()->end();
	//处理一次下一帧,必须调用.
	CCDirector::sharedDirector()->mainLoop();
	CWnd::OnDestroy();
	}
}

OK,CCocos2DXWin类基本完成。现在在Cocos2dXEditorDlg.h中将

CStatic m_Cocos2dXWin;

改为

CCocos2DXWin m_Cocos2dXWin;

并将CCocos2DXWin的头文件包含进来就可以顺利编译成功。在控件初始化(比如OnInitDialog函数)中调用一下m_Cocos2dXWin.CreateMainCoco2dWindow()。此时我们就算完成了将Cocos2d-x嵌入MFCCWnd控件的过程。不过,您需要在窗口大小变化时对这个控件重设位置及大小以及投影矩阵。

void CCocos2DXWin::OnSize(UINT nType, int cx, int cy)
{
	CWnd::OnSize(nType, cx, cy);
	// TODO: 在此处添加消息处理程序代码
	if(TRUE == m_bInitCocos2dX)
	{
		CRect	tClientRect;
		GetClientRect(&tClientRect);
		//重新设置窗口大小及投影矩阵
		CCEGLView::sharedOpenGLView()->resize(tClientRect.Width(),tClientRect.Height());
		CCDirector::sharedDirector()->reshapeProjection(CCSizeMake(tClientRect.Width(),tClientRect.Height()));
        }
}


做下总结:

1.0相比,作者删除了对于过多的目标平台的区分。(1)只保留了WINDOWS,ANDROID,IOS三个目标平台,(2)各类更清晰紧凑了,CCEGLViewCCApplication之间的耦合性大大降低。(3)将引用改成了指针,这点需要多留心。


最后运行一下看下成果吧。

下面是我我现在做的编辑器的截图:


Cocos2d-X 2.0嵌入MFC的子窗体的方法(1.0姐妹篇)