(转载)强行在MFC窗体中渲染Cocos2d-x 3.6

强行在MFC窗体中渲染Cocos2d-x 3.6

GuyaWeiren2015-06-29 15:14:063696 次阅读

【前言】

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

【已知存在的问题】

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

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

*使用VLD检查泄漏会报错

【为什么要这么做】

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

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

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

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

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

【核心思想】

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

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

流程图如下:

【需要的工具】

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

2、    Cocos2d-x 3.6

3、    GLFW (下载地址:点我

4、    CMake(下载地址:点我

【操作步骤】

1、创建项目

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

2、拷贝必要文件

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

3、修改项目属性

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

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

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


1

2

3

4

5

6

7

8

9

$(EngineRoot)cocos\audio\include

$(EngineRoot)external

$(EngineRoot)external\chipmunk\include\chipmunk

$(EngineRoot)extensions

..\Classes

..

%(AdditionalIncludeDirectories)

$(_COCOS_HEADER_WIN32_BEGIN)

$(_COCOS_HEADER_WIN32_END)

预处理器定义中加入:


1

COCOS2D_DEBUG=1

附加库目录中加入:


1

2

$(_COCOS_LIB_PATH_WIN32_BEGIN)

$(_COCOS_LIB_PATH_WIN32_END)

附加依赖项加入:


1

2

3

$(_COCOS_LIB_WIN32_BEGIN)

$(_COCOS_LIB_WIN32_END)

libcocos2d.lib

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

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

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

4、修改GLFW

Cocos2d-x 2.x中创建窗口在CCEGLView类中完成,直接修改它就行。到3.x后使用glfw管理窗口,CreateWindowEx被封装进去了。而Cocos2d-x并没有附带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类:

然后修改类:


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

#pragma once

 

 

// CRenderWnd

 

class CRenderWnd : public CWnd

{

    DECLARE_DYNAMIC(CRenderWnd)

 

public:

    CRenderWnd();

    virtual ~CRenderWnd();

 

protected:

    DECLARE_MESSAGE_MAP()

public:

    afx_msg void OnTimer(UINT_PTR nIDEvent);

    afx_msg void OnDestroy();

 

public:

    void Initialize();

 

private:

    BOOL m_bInited;

};

实现:


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

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

// RenderWnd.cpp : 实现文件

//

 

#include "stdafx.h"

#include "Cocos2dxMFC.h"

#include "RenderWnd.h"

 

#include "cocos2d.h"

#include "AppDelegate.h"

 

// CRenderWnd

 

IMPLEMENT_DYNAMIC(CRenderWnd, CWnd)

 

CRenderWnd::CRenderWnd()

    : m_bInited(FALSE)

{

 

}

 

CRenderWnd::~CRenderWnd()

{

}

 

 

BEGIN_MESSAGE_MAP(CRenderWnd, CWnd)

    ON_WM_TIMER()

    ON_WM_DESTROY()

END_MESSAGE_MAP()

 

 

 

// CRenderWnd 消息处理程序

 

AppDelegate app;

void CRenderWnd::Initialize()

{

    cocos2d::GLViewImpl::SetParent(this->GetSafeHwnd());

    cocos2d::Application::getInstance()->run();

 

    this->m_bInited = TRUE;

    SetTimer(1, 1, NULL);

}

 

 

void CRenderWnd::OnTimer(UINT_PTR nIDEvent)

{

    if (this->m_bInited)

    {

        auto director = cocos2d::Director::getInstance();

        director->mainLoop();

        director->getOpenGLView()->pollEvents();

 

        CWnd::OnTimer(nIDEvent);

    }

}

 

 

void CRenderWnd::OnDestroy()

{

    CWnd::OnDestroy();

 

    if (this->m_bInited)

    {

        auto director = cocos2d::Director::getInstance();

        director->getOpenGLView()->release();

        director->end();

        director->mainLoop();

 

        this->m_bInited = FALSE;

    }

}

然后将刚才绑定的控件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一路追踪下去,最后发现Cocos2d-x搜索文件的目录是在这里设置的(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), nullptr, nullptr);

 

        s_resourcePath = convertPathFormatToUnixStyle(utf8ExeDir);

    }

}

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

我们再看看Cocos2d-x 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;

    }

}

9、最后的小修改

如果你用的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

时间: 2024-08-04 19:18:56

(转载)强行在MFC窗体中渲染Cocos2d-x 3.6的相关文章

强行在MFC窗体中渲染Cocos2d-x 3.6

[前言] 把Cocos2dx渲染到另一个应用程序框架中的方法,在2.x中有很多大神已经实现了,而3.x的做法网上几乎找不着.这两天抽空强行折腾了一下,不敢独享,贴出来供大家参考. [已知存在的问题] 程序退出时会发生非常严重的内存泄漏,博主检查了很久,但技术不够暂时无法解决.如果有大神能搞定,求告知一下做法,谢谢! 在程序从开始运行到关闭期间,有且仅有一个cocos2dx窗体存在时可以选择性无视内存泄漏.如果非常在意这一点,建议使用cocos2d-x 2.2.6这个版本,放在MFC中的内存泄漏很

【续】强行在C# Winform中渲染Cocos2d-x 3.6

[前言] 上一篇讲了怎么把Cocos2d-x 3.6渲染进MFC窗体,这里来讲一下怎么在C# Winform中做到同样的功能.如果你不熟悉MFC的使用但对C# Winform比较在行,请往下看. 这一篇是作为上一篇的副属文,所以文中提到的部分操作需要在上一篇中找……博主懒逼不在这复制粘贴了. [核心思想] 同上一章不同的是,C#是托管环境,并不能直接用“类.方法()”这样的形式来访问Cocos层的代码.我们需要在其间建立一个DLL层(C++编写)作为Cocos层的接口,让C#通过接口来控制Coc

MFC窗体程序中添加调试控制台

在编写复杂程序的过程中,我们经常需要将一些信息输出到文件或者屏幕上.较控制台应用程序,MFC窗体程序要显得麻烦一些! 下面有2种方法来实现为MFC窗体程序添加调试控制台,方便程序员调试程序和了解当前程序的运行状态. 重要Windows API:AllocConsole();   //创建Console窗口 FreeConsole();     //销毁Console窗口 (1)启动控制台窗口 需要包含的头文件 #include <io.h> #include <cstdio> #i

MFC 如何在一个窗体中嵌套在另一个窗体中

其中的一个方法是讲子窗体设置为非模式对话框,具体操作为 :设置子窗体的border属性为none,style为 child. 在父窗体中需要用create来实现,具体例子如下. 在父窗体的OnInitDialog()函数中添加如下代码: CPage *m_page = new CPage();// CPage 是子窗体所关联的类名. m_page->Create(IDD_XXX,this); // 通过create方法来创建一个子窗体.其中IDD_XXX是子窗体的ID号.    CRect re

Windows下使用GetGlyphOutline在OpenGL中渲染字体

欢迎转载,请标明出处:http://blog.csdn.net/tianyu2202/ 无图无JB,先上图.使用OpenGL绘制字体,支持多种字体,支持TrueType轮廓字体,支持自选字体纹理大小和输出大小,支持在三维空间内绘制. 关于OpenGL中字体的显示网上其实有很多的教程,不过经常用到的方式有比较简单的Bitmap方式.比较复杂的FreeType方式.而本文介绍的方式虽然只能在Windows下实现,却有着和FreeType一样的显示效果,最重要的是非常简单,仅仅200多行代码即可实现.

度量快速开发平台窗体中定时执行实现思路

度量快速开发平台的智能窗体中,可以实现定时执行代码功能,一个简单的功能是 每隔5秒执行段代码功能,可以利用智能窗体的功能管理来实现. 这样就在窗体运行的时候,实现自动刷新.非常方便. 如果要实现更复杂的定时执行功能,可以借助度量快速开发平台强大的二次开发扩展,直接调用visual .net中的timer组件实现.如下: '挂载C#中的Timer组件.dim timer=CreateObject("System.Windows.Forms.Timer")timer.Interval =

在MFC 窗口中运行 cocos2d-x 3.2 (一) 基本配置

软件平台 Visual Studio 2012 , Cocos2d-x 3.2 步骤 1. 首先用VS2012创建 MFC 对话框应用程序.(选项默认即可) 2. 在命令行 创建一个Cocos2dx 项目 3.把Cocos2dx 项目中的 cocos2d , Classes, Resources 三个目录复制到第一步所创建MFC项目.vcxproj 文件所在目录下 4.打开MFC项目的 属性管理器 (可以在 工具栏->视图->属性管理器 打开),在Debug|Win32 下 新建属性表  Co

如何在MFC DLL中向C#类发送消息

如何在MFC DLL中向C#类发送消息 一. 引言 由于Windows Message才是Windows平台的通用数据流通格式,故在跨语言传输数据时,Message是一个不错的选择,本文档将描述如何在MFC DLL中向C#窗口类发送消息. 二. 实现过程 1. 新建一个基于MFC的DLL工程,在工程作用是用于生成Dll库函数: 2. 在该工程中新增一个类,CMessager: 3. 在头文件中添加代码如下: 1 #define ZS_API extern "C" _declspec (

【转载】Memcached在.Net中的基本操作

一.Memcached ClientLib For .Net 首先,不得不说,许多语言都实现了连接Memcached的客户端,其中以Perl.PHP为主. 仅仅memcached网站上列出的语言就有:Perl.PHP.Python.Ruby.C#.C/C++以及Lua等. 那么,我们作为.Net码农,自然是使用C#.既然Memcached客户端有.Net版,那我们就去下载一个来试试. 下载文件:http://pan.baidu.com/s/1w9Q8I memcached clientlib项目