【前言】

  把Cocos2dx渲染到另一个应用程序框架中的方法,在2.x时代有很多大神已经实现了,而3.x的做法网上几乎找不着。这两天抽空强行折腾了一下,不敢独享,贴出来供大家参考。


【已知存在的问题】

  程序退出时会发生非常严重的内存泄漏,博主检查了很久,但技术不够暂时无法解决。如果有大神能搞定,求告知一下做法,谢谢!

  在程序从开始运行到关闭期间,有且仅有一个cocos2dx窗体存在时可以选择性无视内存泄漏。如果非常在意这一点,建议使用cocos2d-x 2.2.6这个版本,放在MFC中的内存泄漏很小。

  *使用VLD检查泄漏会报错


【为什么要这么做】

  在进行游戏开发途中,多多少少会用到一些辅助工具,比如CocosStudio。但是在更多的时候,CocosStudio并不能以不变应万变(比如在博文《我用Cocos2d-x制作〈Love Live!学院偶像祭〉的Live场景》中提到的谱面编辑器的功能,CocoStudio无法做到)。在这种情况下,开发人员就需要一款针对当前项目而设计的工具。

  如果辅助工具需要提供丰富的界面和控件,纯用Cocos2d-x来制作就会十分鸡肋。比如这个打开文件的控件:

  

  当然,一定要做的话用cocos2dx也是可以做的,但是相当麻烦。如果有兴趣可以自己尝试写一下,提高自己的姿势水平。

  所以这个时候应当把cocos2dx层放在一个提供了各种控件的应用程序框架里面,cocos2dx仅用于做显示,其余的数据操作交由框架完成。

  目前博主比较熟悉的框架是MFC和C# Winform。说实话C# Winform做窗体比MFC方便快捷太多。但是如果使用C# Winform就得去做C#调用C++,同时对于某些特定参数(比如string到const char*的转换)必须做特殊处理,比较麻烦,否则DLL堆栈会出错。而MFC不存在这个问题。


【核心思想】

  Cocos2dx在Windows上运行起来是一个窗口,那么在其内部一定调用了CreateWindowEx这个API。那么只要我们找到这个API,把参数设为子窗口,并把父窗口的句柄传进去,就可以达到要求。创建出来的窗体就是父窗体中的子窗体了。

  还要注意一点是cocos2dx原生程序有一个自己的消息循环,如果直接调用Application::run会导致MFC层卡死,我们需要把消息循环交给框架的主线程来操作。

  流程图如下:

  


【需要的工具】

  1、    安装了MFC组件的Visual Studio 2013

  2、    Cocos2d-x 3.6

  3、    GLFW (下载地址:点我)

  4、    CMake(下载地址:点我)


【操作步骤】

  1、    创建项目

    创建一个MFC项目(我使用的对话框型)。注意在向导中“MFC的使用”这一项要选择“在共享DLL中使用MFC”:

    

  2、    拷贝必要文件

    把cocos2dx的源码和模板项目中的Classes和Resources文件夹拷贝到项目目录下(项目模板位于引擎目录\templates\cpp-template-default下),一定要使用这个结构:

    

  3、    修改项目属性

    打开MFC项目解决方案,在属性管理器(视图——属性管理器)中为项目添加cocos2dx的两个属性表。属性表位于解决方案目录\cocos2d\cocos\2d:

    

    然后将libcocos2d,libbox2d,libspine加入解决方案中,并把libcocos2d设为MFC项目的依赖项:

    

    再在MFC项目的附加包含目录中加入:

    $(EngineRoot)cocos\audio\include
    $(EngineRoot)external
    $(EngineRoot)external\chipmunk\include\chipmunk
    $(EngineRoot)extensions
    ..\Classes
    ..
    %(AdditionalIncludeDirectories)
    $(_COCOS_HEADER_WIN32_BEGIN)
    $(_COCOS_HEADER_WIN32_END)

  

    预处理器定义中加入:

    COCOS2D_DEBUG=1

    附加库目录中加入:

    $(_COCOS_LIB_PATH_WIN32_BEGIN)

    $(_COCOS_LIB_PATH_WIN32_END)

  

    附加依赖项加入:

    $(_COCOS_LIB_WIN32_BEGIN)

    $(_COCOS_LIB_WIN32_END)

    libcocos2d.lib

    再修改项目属性——工作目录,以及生成目录:

    

    

    再将Classes下的所有文件加入MFC项目:

    

    最后设置不使用预编译头,不然每加入一个类都得加上#include “stdafx.h”,麻烦:

    

  4、 修改GLFW

    Cocos2dx 2.x中创建窗口在CCEGLView类中完成,直接修改它就行。到3.x后使用glfw管理窗口,CreateWindowEx被封装进去了。而cocos2dx并没有附带glfw的源码,只有头文件和lib文件。所以我们需要下载glfw的源码进行修改。

    用CMakeGUI打开GLFW,source code处选择下下来的glfw解压的文件夹,build the binaries选择生成解决方案的文件夹,然后生成对应VS版本的解决方案(glfw解压的文件夹不要删除):

    

    然后打开生成的sln,查找CreateWindowEx,修改它所在的函数(win32_window.c,633行):

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
static int createWindow(_GLFWwindow* window,
                        const _GLFWwndconfig* wndconfig,
                        const _GLFWctxconfig* ctxconfig,
                        const _GLFWfbconfig* fbconfig,
                        HWND parent) // 父窗体句柄
{
    int xpos, ypos, fullWidth, fullHeight;
    WCHAR* wideTitle;
    window->win32.dwStyle = WS_CHILDWINDOW | WS_VISIBLE; // 子窗体样式
    window->win32.dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
     
    xpos = 0;
    ypos = 0;
    fullWidth = wndconfig->width;
    fullHeight = wndconfig->height;   
    wideTitle = _glfwCreateWideStringFromUTF8(wndconfig->title);
    if (!wideTitle)
    {
        _glfwInputError(GLFW_PLATFORM_ERROR,
                        "Win32: Failed to convert window title to UTF-16");
        return GL_FALSE;
    }
    window->win32.handle = CreateWindowExW(window->win32.dwExStyle,
                                           _GLFW_WNDCLASSNAME,
                                           wideTitle,
                                           window->win32.dwStyle,
                                           xpos, ypos,
                                           fullWidth, fullHeight,
                                           parent, // 传入父窗体句柄
                                           NULL, // No window menu
                                           GetModuleHandleW(NULL),
                                           window); // Pass object to WM_CREATE
    //
    // ...
}

    然后从内向外依次修改调用它的地方:

    win32_window.c,769行  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int _glfwPlatformCreateWindow(_GLFWwindow* window,
                              const _GLFWwndconfig* wndconfig,
                              const _GLFWctxconfig* ctxconfig,
                              const _GLFWfbconfig* fbconfig,
                              HWND parent)
{
    // ...
    //
    if (!createWindow(window, wndconfig, ctxconfig, fbconfig, parent))
        return GL_FALSE;
    // ...
    //
        if (!createWindow(window, wndconfig, ctxconfig, fbconfig, parent))
            return GL_FALSE;
    //
    // ...
}

    internal.h,524行

1
2
3
4
5
int _glfwPlatformCreateWindow(_GLFWwindow* window,
                              const _GLFWwndconfig* wndconfig,
                              const _GLFWctxconfig* ctxconfig,
                              const _GLFWfbconfig* fbconfig,
                              HWND parent);

    window.c,116行

1
2
3
4
5
6
7
8
9
10
11
12
GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height,
                                     const char* title,
                                     GLFWmonitor* monitor,
                                     GLFWwindow* share,
                                     int parent)
{
    // ...
    // 
    if (!_glfwPlatformCreateWindow(window, &wndconfig, &ctxconfig, &fbconfig, (HWND)parent)) 
    //
    // ...
}

    glfw3.h,1645行:

1
GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height, const char* title, GLFWmonitor* monitor, GLFWwindow* share, int parent); 

    改好后使用MinSizeRel选项进行编译,编译好后在GLFW解决方案目录\src\MinSizeRel下找到glfw3.lib文件,连同glfw3.h(在glfw解压目录\include\GLFW)一起,分别放入MFC项目解决方案目录\cocos2d\external\glfw3\prebuilt\win32 和 MFC项目解决方案目录\cocos2d\external\glfw3\include\win32下覆盖原文件。

  5、    修改Cocos层

    在GLViewImpl类(3.2中是GLView类)的头文件中加入一个方法和成员:

1
2
3
4
5
public:
    static void SetParent(HWND parent){ m_sParent = parent; }
private:
    static HWND m_sParent;

    别忘了在cpp中加入

1
HWND GLViewImpl::m_sParent = NULL;

    然后修改GLViewImpl::initWithRect方法,修改调用glfwCreateWindow的地方:

1
2
3
4
5
6
7
8
9
10
11
12
13
bool GLViewImpl::initWithRect(const std::string& viewName, Rect rect, float frameZoomFactor)
{
    // ...
    //
    _mainWindow = glfwCreateWindow(rect.size.width * _frameZoomFactor,
                                   rect.size.height * _frameZoomFactor,
                                   _viewName.c_str(),
                                   _monitor,
                                   nullptr,
                                   (int)m_sParent); // 传入父窗口句柄
    //
    // ...
}

    修改Application类的run方法,去掉里面的消息循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int Application::run()
{
    PVRFrameEnableControlWindow(false);
    initGLContextAttrs();
    // Initialize instance and cocos2d.
    if (!applicationDidFinishLaunching())
    {
        return 1;
    }
    // Retain glview to avoid glview being released in the while loop
    Director::getInstance()->getOpenGLView()->retain();
    return 0;
}

  

  6、   编辑MFC窗体

    接下来在MFC窗体中添加一个Picture Control控件,控件ID设为IDC_RENDERWND,然后选中控件(非常蛋疼的是只能在控件边框处点击才能选中)点右键——“添加变量”:

    

  7、添加渲染类

    在解决方案资源管理器中的MFC项目上点右键——“添加”——“类…”,添加一个MFC类:

    

    

    然后修改类:

+ View Code

    实现:

+ View Code

    然后将刚才绑定的控件m_RenderWnd的类型由CStatic改为CRenderWnd,并在主窗体的OnInitDialog方法中加入一行:

1
2
3
4
5
6
7
8
9
10
BOOL CCocos2dxMFCDlg::OnInitDialog()
{
        // ...
        //
    // TODO:  在此添加额外的初始化代码
        this->m_RenderWnd.Initialize();  
    return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
} 

  8、运行起来

    理论上要做的操作已经做完了,现在只需要编译就能运行起来。然而触控会这么好心地做好事不留坑嘛?

    当然不会了~传说cocos系列的坑连起来可以绕地球多少圈来着,这里噗通一下就入坑了,不信你F5一下:

    

    这什么鬼?!其实是ApplicationProtocol中Platform枚举中的一个值和MFC的某个宏同名了。解决方法是在stdafx.h中加入这样一句:

1
#undef OS_WINDOWS

    然后继续编译。当然是坑不单行,又报错:

    

    不过这个简单,根据报错内容,在项目的预处理器定义中加入_CRT_SECURE_NO_WARNINGS。

    

    按理说最后是不是应该出现一个BOSS级深坑来着?BOSS来了:此时编译可以通过了,但是一运行必然报错。看看输出窗口:

    

    嗷,原来是找不到文件。但是我们之前已经设置了工作目录,Resources下面也有文件啊(这个坑在2.2.6中并没有)。

    从Label::createWithTTF一路追踪下去,最后发现cocos2dx搜索文件的目录是在这里设置的(CCFileUtils-win32.cpp 59行):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static void _checkPath()
{
    if (0 == s_resourcePath.length())
    {
        WCHAR *pUtf16ExePath = nullptr;
        _get_wpgmptr(&pUtf16ExePath);
        // We need only directory part without exe
        WCHAR *pUtf16DirEnd = wcsrchr(pUtf16ExePath, L'\\');
        char utf8ExeDir[CC_MAX_PATH] = { 0 };
        int nNum = WideCharToMultiByte(CP_UTF8, 0, pUtf16ExePath, pUtf16DirEnd-pUtf16ExePath+1, utf8ExeDir, sizeof(utf8ExeDir), nullptrnullptr);
        s_resourcePath = convertPathFormatToUnixStyle(utf8ExeDir);
    }
}

    _get_wpgmptr是个嘛玩意?查一下可以知道,这个函数用于取得进程exe所在的目录。

    我们再看看cocos2dx 2.2.6中对应的部分(CCFileUtilsWin32.cpp 34行):

1
2
3
4
5
6
7
8
9
10
11
static void _checkPath()
{
    if (! s_pszResourcePath[0])
    {
        WCHAR  wszPath[MAX_PATH] = {0};
        int nNum = WideCharToMultiByte(CP_ACP, 0, wszPath,
            GetCurrentDirectoryW(sizeof(wszPath), wszPath),
            s_pszResourcePath, MAX_PATH, NULL, NULL);
        s_pszResourcePath[nNum] = '\\';
    }
}  

    很明显,2.2.6中使用GetCurrentDirectoryW获取当前目录的,使用这个函数就能获取正确的工作目录了。为什么用cocos new出来的3.6项目没这个问题?因为new出来的项目的预链接事件中最后有这么一句:

  

    这个命令会把Resources下的所有文件拷贝到输出目录(也就是进程exe所在的目录)下,自然不会出现找不到文件的问题了。

    不知道这么做的意义和目的是什么?但是此时我想说:

    

    我还想说:

    

    修改的方法很简单,参考2.2.6把_checkPath中_get_wpgmptr函数改为GetCurrentDirectoryW:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static void _checkPath()
{
    if (0 == s_resourcePath.length())
    {
        char pathBuffer[MAX_PATH] = { 0 };
        WCHAR  wszPath[MAX_PATH] = { 0 };
        int nNum = WideCharToMultiByte(CP_ACP, 0, wszPath,
            GetCurrentDirectory(sizeof(wszPath), wszPath),
            pathBuffer, MAX_PATH, NULL, NULL);
        pathBuffer[nNum] = '\\';
        s_resourcePath = pathBuffer;
    }
}

  

  ⑨、最后的小修改

    如果你用的MFC窗体是一个Dialog类型的,运行后会发现按回车或Esc后窗体直接关闭了。所以还需要屏蔽掉回车和Esc键的响应。在MFC对话框类中添加一个方法重写PreTranslateMessage:

1
2
private:
    virtual BOOL PreTranslateMessage(MSG* pMsg);

  实现:

1
2
3
4
5
6
7
8
9
10
11
BOOL CCocos2dxMFCDlg::PreTranslateMessage(MSG* pMsg)
{
    if (pMsg->message == WM_KEYDOWN)
    {
        if (pMsg->wParam == VK_ESCAPE || pMsg->wParam == VK_RETURN)
        {
            return TRUE;
        }
    }
    return CDialogEx::PreTranslateMessage(pMsg);
}

 


【运行起来】

  如果编译没有出错的话,运行起来会看到这个样子:

  

  只要将接口留出来,就可以很方便地通过MFC层的控件来控制cocos层了。至于要做成一个什么样的工具,全靠大家发挥咯~


【后记】

  采用这套思路理论上可以把cocos渲染到任何一个支持调用C++层代码的框架中。

  需要渲染在C# Winform中的童鞋请看这篇博客,里面有讲处理方法及string到const char*的转换。

文章转载自http://www.cnblogs.com/GuyaWeiren/p/4600937.html,感谢博主的分享。

强行在MFC窗体中渲染Cocos2d-x 3.6相关推荐

  1. 《MFC dialog中加入OpenGL窗体》

    <MFC dialog中加入OpenGL窗体> 最近学习了如何在MFC对话框程序中加入OpenGL窗体的方法,在这里将自己的实现过程归纳一下. 步骤零: 加入PictureControl控 ...

  2. 在MFC项目中使用Quick PDF Library Lite读写pdf文件

    简介 安装 示例程序 Hello World Draw Text Fonts and Text Set Document Properties Image to pdf Add hyperlink t ...

  3. c# 火狐浏览器怎么嵌入窗体中_C#WinForm窗体内Panel容器中嵌入子窗体、程序主窗体设计例子...

    C#WinForm父级窗体内Panel容器中嵌入子窗体.程序主窗体设计例子 在项目开发中经常遇到父级窗体嵌入子窗体所以写了一个例子程序,顺便大概划分了下界面模块和配色,不足之处还望指点 主窗体窗体采用 ...

  4. 向窗体中拖放图片并显示

    Frm_Main.cs View Code 1 using System; 2 using System.Collections.Generic; 3 using System.ComponentMo ...

  5. C#的委托事件在winform窗体中实现传值备忘

    用一个例子来说明吧,这样比较好明白一些. 就是打开主窗体,datagridview加载了一些数据,然后我们在打开的子窗体中,输入一些条件,让主窗体中的数据随着加载的条件来动态实现加载数据. 这就需要涉 ...

  6. 【HTML】处理<br>换行符追加到前端换行无效的问题 --- html中渲染的字符串中包含HTML标签无效的处理方法,字符串中包含HTML标签被转义的问题 解决

    [HTML]处理 换行符追加到前端换行无效的问题 --- html中渲染的字符串中包含HTML标签无效的处理方法,字符串中包含HTML标签被转义的问题 解决 参考文章: (1)[HTML]处理 换行符 ...

  7. mfc 内嵌 本地html,MFC程序中内嵌网页,附示例工程

    最近在课程设计,因为小悠太懒了,是在不想用C++完成一些简单的文件上传下载操作,于是就想着将网页内嵌到程序中,上传下载神马的就直接使用网页Web来完成就好了,本示例中将演示在MFC程序中内嵌一个htm ...

  8. access窗体中再制作查询窗体_如何给java窗体添加下拉菜单项,以选择游戏难度为例 - 攻向前端的小白...

    首先我们来介绍一下,以记事本为例,菜单栏中的[文件],[编辑]等全部叫一个菜单条:在Java中用JMenuBar表示: 菜单条中每一个具体的项叫做菜单,在Java中用JMenu表示: 菜单中的每一个项 ...

  9. vue中组件在不同页面中渲染出错

    vue中组件在不同页面中渲染出错 封装了一个组件 封装了一个组件 遇到问题了,特向各位大佬请教 比如我封装了一个 child 组件 我在father 页面中引入 可以正常使用 而且css 样式不会出现 ...

  10. C# WinForm中 获得当前鼠标所在控件 或 将窗体中鼠标所在控件名显示在窗体标题上...

    转:/********************** * 课题:将窗体中鼠标所在控件名显示在窗体标题上  * 作者:ZAXX  * QQ : 94027486  * 本课题可简单扩展:获取屏幕上鼠标所在 ...

最新文章

  1. PyTorch 笔记(04)— Tensor 属性方法(获取元素个数numel/neleme、查看形状size()/shape、增减维度squeeze()/unsqueeze()、resize形状)
  2. 汤家凤高等数学2020年强化笔记-第一模块极限与连续与第二模块微分学
  3. img标签的onerror事件来显示默认图片
  4. 【STM32】DAC相关函数和类型
  5. spring java配置_Spring基于java的配置
  6. 工作201:所需功能优化
  7. Android笔记 codeUI与html UI
  8. java awt point_100分 解决java import java.awt.Point;import java.awt.Rectangle;
  9. oracle 存储 秘密,鲜为人知的Exadata存储性能秘密
  10. TCP/IP协议详解:IP、ARP、RARP、ICMP、IGMP
  11. 【Python cursor指针】——Python Tkinter Cursor鼠标指针属性值
  12. 身份证号码中间位数隐藏
  13. 高等数学:一元函数微分学
  14. return的常见用法
  15. 讲一个程序员如何副业月赚三万的真实故事
  16. python 入门总结(一)
  17. 台式计算机显示器点不亮的原因,电脑点不亮原因及处理方法
  18. .NET基础-11-ArrayList|Hashtable|File文件操作|Dircetioy文件夹操作|Path路径操作
  19. 雅俗共赏——广告词之很好很强大
  20. 在IDE中使用Bito - 一个不需要VPN就可以使用的chatgpt

热门文章

  1. 如何在 Linux 上最好地设置命令别名
  2. 【读书笔记】马化腾:先人一步-冷湖,腾讯成长之路:模仿+学习+实践+创新+合作+超越
  3. linux下mysql8.0初始密码_Linux下MySQL8.0修改初始密码
  4. react骨架屏自动生成_网页骨架屏自动生成方案(dps)
  5. 买房税费大攻略!哪些费用必须交?
  6. 最难忘的一节计算机课,我最难忘的一节课
  7. c语言中个各标点符号作用,C语言运算符和标点符号.xls
  8. pagefile.sys从C盘移到其他盘
  9. idea激活到2100年
  10. 6.1.3. Mentor: Design Not Just for Usability, but Learnability