热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Cocos2d-X游戏工具开发之一:将Cocos2d-X嵌入MFC的子窗体方法讲解

[Cocos2d-x相关教程来源于红孩儿的游戏编程之路CSDN博客地址:http:blog.csdn.nethonghaier]红孩儿Cocos2d-X学习园地QQ群:249

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

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

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

        本节所用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成员函数声明:

//创建Cocos2dX窗口
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变量获取窗口的客户区大小
CRect tClientRect;
GetClientRect(&tClientRect);
//调用cocos2d中的CCApplication::sharedApplication()获取Cocos2d-x程序类单件实例对象。调用其run函数。在这里我们传入四个参数,与第一章“HelloWorld”不同的是这里增加了新参数“当前窗口的句柄”。这是使用MFC来嵌入Cocos2d-x窗口的关健所在。我们暂时埋个伏笔,后面再详解。
cocos2d::CCApplication::sharedApplication().run(GetSafeHwnd(),TEXT("第一个Cocos2d-x程序"),tClientRect.Width(),tClientRect.Height());
//这里将变量设置为TRUE
m_bInitCocos2dX = TRUE;
return TRUE;
}

         现在还不能运行。因为CCApplication的run函数跟本就没有HWND参数,我们必须对CCApplication动个小手术。使它能够接收到窗体的句柄并使用这个句柄来创建OpenGL。

       

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

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

int CCApplication::run(HWND hWnd,LPCTSTR szTitle,UINT wWidth,UINT wHeight)
{
PVRFrameEnableControlWindow(false);

// Main message loop:
MSG msg;
LARGE_INTEGER nFreq;
LARGE_INTEGER nNow;

QueryPerformanceFrequency(&nFreq);
//将原临时变量nLast改为成员变量m_nLast。因为后面要把每帧的渲染做到外部调用的public函数里,所以需要存一下上帧的时间。
QueryPerformanceCounter(&m_nLast);

// 关键点1:必须将参数hWnd也做为参数传给initInstance函数。
if (! initInstance(hWnd,szTitle,wWidth,wHeight) || ! applicationDidFinishLaunching())
{
return 0;
}

CCEGLView& mainWnd = CCEGLView::sharedOpenGLView();
//关键点2:我们如果要使用指定的CWnd窗口来创建OpenGL,那么必须放弃使用Cocos2d-x封装好的消息循环。因为这些工作都会交由MFC来做。我们为了不破坏原来的Cocos2d-x创建窗口方式。就在这里做个判断处理,如果要使用原来由Cocos2d-x创建窗口的方式,只需要第一个参数传NULL就好了。
if(NULL == hWnd)
{
mainWnd.centerWindow();
ShowWindow(mainWnd.getHWnd(), SW_SHOW);

while (1)
{
if (! PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
// 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();
}
else
{
Sleep(0);
}
continue;
}

if (WM_QUIT == msg.message)
{
// Quit message loop.
break;
}

// Deal with windows message.
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));
//|| ! pMainWnd->Create(TEXT("cocos2d: Hello World"), 480, 320));
#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
{
//在这里做个判断,如果参数中的窗口句柄不为空,则使用参数句柄做为成员变量m_hWnd的值。而在
if(hWnd)
{
m_hWnd = hWnd ;
}
else
{
CC_BREAK_IF(m_hWnd);

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);

// create window
m_hWnd = CreateWindowEx(
WS_EX_APPWINDOW | WS_EX_WINDOWEDGE, // Extended Style For The Window
kWindowClassName, // Class Name
pTitle, // 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);
}
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);

// 当调用CCEGL的静态函数create来创建OpenGL窗口时,传入的参数是CCEGLView实例对象,在内部会取得实例对象的m_hWnd变量做为OpenGL的窗口句柄。这样我们就实现了使用参数传递的窗口句柄来创建OpenGL窗口。
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)
{
// 这里我们先判断一下是否初始化完成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);

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

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

                                       

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

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

         我们打开CCApplication_win32.h,为CCApplication类增加一个public的renderWorld函数。

//帧循环调用渲染
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。

                                      


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();
}
}

         我们在“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);
//调用显示设备单件实例对象的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嵌入MFC的CWnd控件的过程。不过,您需要在窗口大小变化时对这个控件重设位置及大小以使窗口始终上下充满空间。这些属于MFC的基础知识,在此不再赘述。


       最后运行一下看下成果吧。下面是我运行的结果(:左边面板稍加制做~):





推荐阅读
  • C# 7.0 新特性:基于Tuple的“多”返回值方法
    本文介绍了C# 7.0中基于Tuple的“多”返回值方法的使用。通过对C# 6.0及更早版本的做法进行回顾,提出了问题:如何使一个方法可返回多个返回值。然后详细介绍了C# 7.0中使用Tuple的写法,并给出了示例代码。最后,总结了该新特性的优点。 ... [详细]
  • 本文介绍了在MFC下利用C++和MFC的特性动态创建窗口的方法,包括继承现有的MFC类并加以改造、插入工具栏和状态栏对象的声明等。同时还提到了窗口销毁的处理方法。本文详细介绍了实现方法并给出了相关注意事项。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • 本文介绍了RxJava在Android开发中的广泛应用以及其在事件总线(Event Bus)实现中的使用方法。RxJava是一种基于观察者模式的异步java库,可以提高开发效率、降低维护成本。通过RxJava,开发者可以实现事件的异步处理和链式操作。对于已经具备RxJava基础的开发者来说,本文将详细介绍如何利用RxJava实现事件总线,并提供了使用建议。 ... [详细]
  • 设计模式——模板方法模式的应用和优缺点
    本文介绍了设计模式中的模板方法模式,包括其定义、应用、优点、缺点和使用场景。模板方法模式是一种基于继承的代码复用技术,通过将复杂流程的实现步骤封装在基本方法中,并在抽象父类中定义模板方法的执行次序,子类可以覆盖某些步骤,实现相同的算法框架的不同功能。该模式在软件开发中具有广泛的应用价值。 ... [详细]
  • 本文介绍了一种图的存储和遍历方法——链式前向星法,该方法在存储带边权的图时时间效率比vector略高且节省空间。然而,链式前向星法存图的最大问题是对一个点的出边进行排序去重不容易,但在平行边无所谓的情况下选择这个方法是非常明智的。文章还提及了图中搜索树的父子关系一般不是很重要,同时给出了相应的代码示例。 ... [详细]
  • PHP中的单例模式与静态变量的区别及使用方法
    本文介绍了PHP中的单例模式与静态变量的区别及使用方法。在PHP中,静态变量的存活周期仅仅是每次PHP的会话周期,与Java、C++不同。静态变量在PHP中的作用域仅限于当前文件内,在函数或类中可以传递变量。本文还通过示例代码解释了静态变量在函数和类中的使用方法,并说明了静态变量的生命周期与结构体的生命周期相关联。同时,本文还介绍了静态变量在类中的使用方法,并通过示例代码展示了如何在类中使用静态变量。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • Iamtryingtocreateanarrayofstructinstanceslikethis:我试图创建一个这样的struct实例数组:letinstallers: ... [详细]
  • 本文整理了315道Python基础题目及答案,帮助读者检验学习成果。文章介绍了学习Python的途径、Python与其他编程语言的对比、解释型和编译型编程语言的简述、Python解释器的种类和特点、位和字节的关系、以及至少5个PEP8规范。对于想要检验自己学习成果的读者,这些题目将是一个不错的选择。请注意,答案在视频中,本文不提供答案。 ... [详细]
  • 在C#中,使用关键字abstract来定义抽象类和抽象方法。抽象类是一种不能被实例化的类,它只提供部分实现,但可以被其他类继承并创建实例。抽象类可以用于类、方法、属性、索引器和事件。在一个类声明中使用abstract表示该类倾向于作为其他类的基类成员被标识为抽象,或者被包含在一个抽象类中,必须由其派生类实现。本文介绍了C#中抽象类和抽象方法的基础知识,并提供了一个示例代码。 ... [详细]
  • 本文介绍了GTK+中的GObject对象系统,该系统是基于GLib和C语言完成的面向对象的框架,提供了灵活、可扩展且易于映射到其他语言的特性。其中最重要的是GType,它是GLib运行时类型认证和管理系统的基础,通过注册和管理基本数据类型、用户定义对象和界面类型来实现对象的继承。文章详细解释了GObject系统中对象的三个部分:唯一的ID标识、类结构和实例结构。 ... [详细]
author-avatar
乌鸦bz_371
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有