vs2015编译D3D程序时链接出错

用vs2015编译一个DirectX 11的程序的过程中,在链接的时候会报无法解析的外部符号 __vsnprintf
解决方法是加入链接库legacy_stdio_definitions.lib

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

关于新手引导设计的一些新思路

背景

在游戏设计过程中,新手引导往往是在游戏制作偏后期的时候开始制作,这时候往往功能已经有了相应的完善,而且每个功能都已经独立完整,这时候基本不可能去更改游戏的逻辑部分,也很少有见过预留下新手引导的接口之类的。其中还有一个非常大的问题是,各自有各自的风格,变量命名方式,还有规范性,特别是Lua下基本是各自一个风格。这些都给新手引导的设计上留下了无数的坑。

思路

因为不太了解其它引擎的特性,所以只讨论关于cocos2d-x(lua).
在cocos2d更新到3.x之后,新的cocostudio的加入为整个引导流程带来了很大的便利,这里考虑到新手引导至少99%以上都是对于UI控件的引导,只有极少数是自建的Node的子类。

事件的支持

Widget::addTouchEventListener(const ccWidgetTouchCallback& callback); 方法中,原来的做法是直接覆盖掉原来的旧的事件监听,我把这里改成了一个std::map ,用一个uint32_t做为key,来作为一个事件的handle.可以用来删除事件。每次事件触发都把整个map中的事件监听执行一遍,新手引导的事件无非就是触发一个事件跳到下一个引导,这样有个好处就是可以在任意能够获取到需要引导的控件的地方加入一个新的事件,这样就能在事件结束之后删掉事件监听,完全不影响原来的控件和系统,实现解耦,同时还能获取和真实事件完全一样的事件,如按钮结束了发送消息跳转还是移动跳转都可以随机应变,而不是在Dispatcher注册一个监听来拦截消息。而且这整个过程对于Lua来说是非常容易支持的,找到相应功能的设计者,获取相应的控件,注入新的事件,引导结束,销毁新的事件,进入下一步。

例如:

1
2
3
4
5
6
--function1.lua  功能1的lua文件
UIDialog = {}

UIDialog.uiButtons = {}
---....省略注册过程
---假设UIDialog.uiButtons里有N个UI::Button并且都有各自的事件,其中有非常多复杂的逻辑
1
2
3
4
5
6
7
8
9
10
11
12
---Newbie_Guide.lua 新手引导
Guide = {}
function Guide.init()
local listenerID = nil
local function guideListener(sender,event)
if eventType == end then
--发送消息给服务器完成引导,进入下一步引导
sender:removeEventListener(listenerID)
end
end
listenerID = UIDilog.uiButtons[x]:addTouchEventListener(guideListener)
end

通过这个过程就能把新手引导从功能中剥离出来。同时可以把这个方法用于多个各种类似的场景。用于解耦。

pipeline

虽然使用另外添加事件能够完成大多数功能,甚至于传入一个可点击控件名称或者Tag就可以了。但是实际使用中还是会遇到很多奇怪的问题,比如要引导的控件是在ListView中,或者有重名之类的。这里把每个引导步骤当成一个pipeline:
例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
GuideStepInfo = {} --用于初始化步骤信息
GuideStepInfo.onBegan = function()

end
GuideStepInfo.getGuideBtn = function()

local guideBtn = .....
return guideBtn
end

GUideStepIinfo.finished = function()
--send info
end

GuideStep = class("GuideStep")

function Guidestep:ctor(guideInfo)

end

相当于每个过程都是可编程的,而不是固定于名字,但是可以在guideStep加入一个默认的用于处理通用情况。有点类似于OpenGL..所以想到这里就加入了一个状态机。用于主循环每个步骤,调用相应的方法。然后把最终结果用一个UI显示到界面上。通过这种方法能够一定程度上解除新手引导带来的耦合性。

用QT Creator在osx下构建cocos2d-x项目

关于初衷

cocos2d-x默认提供了xcode visual studio eclipse 等几种IDE的工程配置,几乎已经满足了全部的开发需求,似乎并没有需要去用到Qt来构建项目的必要。但是cocos2d-x的工具目前并不算完善,经常会有需求基于cocos2d-x来制作工具。而Qt .Net MFC几乎成了首选,如果再考虑跨平台还有性能以及同c++的交互性来说,就只剩下Qt了。

工程配置

cocos2d-x并没有提供Qt的项目工程配置,为了能够用Qt开发有几种解决方案,一种是最费力的就是手动建立Qt的project file.但是缺点太明显就是费力,而且当引擎更新时整个人都会思密达了。第二种方案是利用Qt的visual studio插件。而cocos2d-x本身提供了vs的库配置。这种方案优点是简单的不能再简单了,缺点就是不能跨平台,那还不如用MFC来得舒服,最后一种解决方案就是cocos2d-x提供了更为通用的解决方案—cmake.

关于cmake

cmake是个类似于ant的一个项目构建工具,开源,跨平台,主要是为了c/c++项目,起源是KDE。关于cmake的更多了解在《Cmake Practice —Cjacker》

具体配置

在生成一个cocos2d-x项目后可以用Qt creator打开根目录下的cmake项目(CMD+O)
CMD+O

打开CMakeLists更改项目的PLATFORM_SPECIFIC_SRCPLATFORM_SPECIFIC_HEADERS
因为我们并没有使用COCOA,所以用和linux一样的配置。

另外虽然使用的是和linux一样的配置。但是我们需要把资源文件拷到${APPNAME}.app/文件夹中。
具体修改可以查看如下文件
CMakeLists.txt
在之后可以看到整个工程项目如下:
result.png
编译运行之后就可以看到运行结果,和xcode一样会生成一个.app文件

c/c++在头件中定义变量引起的重定义

因为打算定义一个全局使用的变量,所以想着把所有的要实现的东西放在一个头文件中。如下:

1
2
3
4
5
6
7
#ifndef Data_H
#define Data_H
const char* testArray =
{
"testArray",
};
#endif

本想着有:

1
2
3
#ifndef
#define
#endif

的嵌套,这样也应该只会执行一次,可是Link的时候无情得抛出了duplicate declare的错误。很是纳闷。想着不是自己想的那样。于是改成了:

1
2
3
4
5
6
7
#ifndef Data_H
#define Data_H
const static char* testArray =
{
"testArray",
};
#endif

把testArray改成static .成功的Link了。可是在使用的时候发现实际上这个testArray并不是同一个数组,如下:

1
2
3
4
5
6
//object1.h
class Object1
{
public:
Object1();
};

1
2
3
4
5
//object1.cpp
Object1::Object1()
{
cout<<&testArray<<endl;
}
1
2
3
4
5
6
7
//object2.h
class Object2
: public Object1
{
public:
Object2();
};
1
2
3
4
5
//object2.cpp
Object2::Object2()
{
cout<<&testArray<<endl;
}
1
2
3
4
5
//main.cpp
void main()
{

auto obj = new Object2();
}

会得到两个不一样的指像testArray的地址。
默默得再也不敢在头文件中定义变量了。改成了:

1
2
3
4
5
//data.h
#ifndef Data_H
#define Data_H
extern const char*testArray[];
#endif

1
2
//data.cpp
const char *testArray[] = {"testArray"};