概述
由于需要将cocos的程序在win32下运行,但cocos使用的是系统本身窗口,从游戏角度来说,这个是比较不能接受的。那么肯定需要重绘窗口,简单来说有两种方案:
- 使用mfc的窗口,重绘来实现
- 使用例如duilib接管ui来实现
从后续开发简易度来说,使用duilib会比较好些,这里也使用了这个方案。
本文参考了以下方案
Duilib接入步骤
- 准备工作
- 下载cocos和对应win32开发工具vs2013,建议使用vs2013,因为下面几个工具确定可以用它来编译
- 下载glfw,cocos是使用它接管跨平台的窗口创建和管理的,方便对接opengl。需要注意的是,找对cocos对应glfw版本。
- 下载并安装cmake,建议下载最新版本。
- 下载并编译duilib
- 修改GLFW源码
- 进入glfw源码目录,打开cmakegui,source code处选择下下来的glfw解压的文件夹,build the binaries选择生成解决方案的文件夹,然后生成对应VS版本的解决方案
- 进入build the binaries选择生成解决方案的文件夹,打开vs解决方案
打开win32_windows.c,修改createWindow函数,注释处均为修改的地方
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// 增加三个参数 HWND parent, int px, int py,表示父窗口是谁,以及对应位置
static int createWindow(_GLFWwindow* window, const _GLFWwndconfig* wndconfig, HWND parent, int px, int py)
{
int xpos, ypos, fullWidth, fullHeight;
WCHAR* wideTitle;
DWORD style = getWindowStyle(window);
DWORD exStyle = getWindowExStyle(window);
// 如果有父窗口,修改窗口属性
if (parent)
{
style = WS_CHILDWINDOW | WS_VISIBLE;
exStyle = WS_EX_APPWINDOW;
}
if (window->monitor)
{
GLFWvidmode mode;
// NOTE: This window placement is temporary and approximate, as the
// correct position and size cannot be known until the monitor
// video mode has been set
_glfwPlatformGetMonitorPos(window->monitor, &xpos, &ypos);
_glfwPlatformGetVideoMode(window->monitor, &mode);
fullWidth = mode.width;
fullHeight = mode.height;
}
else
{
xpos = CW_USEDEFAULT;
ypos = CW_USEDEFAULT;
if (parent==NULL && wndconfig->maximized)
style |= WS_MAXIMIZE;
getFullWindowSize(style, exStyle,
wndconfig->width, wndconfig->height,
&fullWidth, &fullHeight);
}
// 如果有父窗口,修改窗口位置
if (parent)
{
xpos = px;
ypos = py;
}
wideTitle = _glfwCreateWideStringFromUTF8Win32(wndconfig->title);
if (!wideTitle)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Win32: Failed to convert window title to UTF-16");
return GLFW_FALSE;
}
window->win32.handle = CreateWindowExW(exStyle,
_GLFW_WNDCLASSNAME,
wideTitle,
style,
xpos, ypos,
fullWidth, fullHeight,
parent, // No parent window
NULL, // No window menu
GetModuleHandleW(NULL),
NULL);
free(wideTitle);
//...
//
}打开win32_windows.c,修改_glfwPlatformCreateWindow函数,对接createWindow函数的参数,并修改对应的头文件internal.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18int _glfwPlatformCreateWindow(_GLFWwindow* window,
const _GLFWwndconfig* wndconfig,
const _GLFWctxconfig* ctxconfig,
const _GLFWfbconfig* fbconfig,
HWND parent, int px, int py)
{
//...
//
if (!createWindow(window, wndconfig, parent, px, py))
return GLFW_FALSE;
//...
//
if (!createWindow(window, wndconfig, parent, px, py))
return GLFW_FALSE;
//...
//
}glfw3.h,修改接口
1
2
3
4
5// 为了兼容,保留老接口
GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height, const char* title, GLFWmonitor* monitor, GLFWwindow* share);
// 增加新接口
GLFWAPI GLFWwindow* glfwCreateWindowEx(int width, int height, const char* title, GLFWmonitor* monitor, GLFWwindow* share,
intptr_t parent, int px, int py);windows.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
25GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height, const char* title, GLFWmonitor* monitor, GLFWwindow* share)
{
return glfwCreateWindowEx(width, height, title, monitor, share, 0, 0, 0);
}
GLFWAPI GLFWwindow* glfwCreateWindowEx(int width, int height,
const char* title,
GLFWmonitor* monitor,
GLFWwindow* share,
intptr_t parent, int px, int py)
{
//...
//
// Open the actual window and create its context
if (!_glfwPlatformCreateWindow(window, &wndconfig, &ctxconfig, &fbconfig, (HWND)parent, px, py))
{
glfwMakeContextCurrent((GLFWwindow*) previous);
glfwDestroyWindow((GLFWwindow*) window);
return NULL;
}
//...
//
}重新编译,然后glfw3.h和glfw3.lib复制到Cocos2dx\external\glfw3\include\win32和E:\daclient\Cocos2dx\external\glfw3\prebuilt\win32目录下
- 修改对应的cocos源码
cocos是使用glfwCreateWindow创建窗口的,找到对应位置,在CCGLViewImpl-desktop.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// 增加了三个参数,对应调用此函数的地方也要做响应修改
bool GLViewImpl::initWithRect(const std::string& viewName, Rect rect, float frameZoomFactor, bool resizable,
HWND parent, int px, int py)
{
//
//...
// 去掉标题栏
glfwWindowHint(GLFW_DECORATED, GL_FALSE); // 这个样式实际上去掉了系统提供的默认标题栏。所有窗口区域都为客户区
int neededWidth = rect.size.width * _frameZoomFactor;
int neededHeight = rect.size.height * _frameZoomFactor;
// glfwCreateWindowEx替换glfwCreateWindow
_mainWindow = glfwCreateWindowEx(neededWidth, neededHeight, _viewName.c_str(), _monitor, nullptr,
(intptr_t)parent, px, py);
//
// ...
}cocos的Application::getInstance()->run();会阻塞线程,但之后线程会被duilib主窗口接管,所以渲染主循环会放到时钟中去,那么需要将run函数拆分。找到CCApplication-win32.cpp
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// 新增三个函数
int Application::loopinit()
{
PVRFrameEnableControlWindow(false);
initGLContextAttrs();
// Initialize instance and cocos2d.
if (!applicationDidFinishLaunching())
{
return 1;
}
auto director = Director::getInstance();
auto glview = director->getOpenGLView();
// Retain glview to avoid glview being released in the while loop
glview->retain();
_loopinit = true;
return 0;
}
int Application::looponce()
{
if (!_loopinit) return 1;
auto director = Director::getInstance();
auto glview = director->getOpenGLView();
director->mainLoop();
glview->pollEvents();
return 0;
}
int Application::loopfini()
{
if (!_loopinit) return 1;
auto director = Director::getInstance();
auto glview = director->getOpenGLView();
if (glview->isOpenGLReady())
{
director->end();
director->mainLoop();
director = nullptr;
}
glview->release();
_loopinit = false;
return 0;
}
- 引入duilib,根据duilib的例子,创建duilib窗口,建议把duilib相关的内容放到单独的cpp文件,不与cocos的文件有牵扯,容易出类库包含不兼容问题。
1
2
3
4// duilib窗口对应函数
HWND DuiWinCreate(HINSTANCE hInstance);
void DuiWinShow();
void DuiWinRun();
duilib窗口的属性配置
duilib库有一些bug,如Dialog的domodel返回值错误;PostQuitMessage(0L)有时候不能退出
- 修改主工程
1 | // 修改main.cpp |
1 | // 对应创建窗口的地方 |
至此已经对接完成,本方案不会影响ios和android包的实现。
其他
- 显示应用在系统状态的图片,加载一个mfc资源,使用HICON hIcon = ::LoadIcon(hInstance, MAKEINTRESOURCE(MAINICON));即可
- 使用自定义鼠标cur,似乎不能实现,duilib使用的是系统鼠标,并做一些自动切换