要使别人喜欢你,首先你得改变对人的态度,把精神放得轻松一点,表情自然,笑容可掬,这样别人就会对你产生喜爱的感觉了。 —— 卡耐基
前情提要
上回我们简单的实现了第一期所说的内容,但是并没有对属性值具体的处理,不过这一期仍然不会对它处理。因为在正式开发之前,小明决定设计一个妥善的结构来作为一个号的开始。
已经完成的代码0积分下载地址如下:
http://download.csdn.net/detail/fukainankai/7334417
这一期将实现一个状态机驱动下的贪吃英雄。
重构分析
(1)总体结构调整
CTeam类属于单独的内容,我们将为该类添加一组独立的.cpp和.h文件进行保存
全局的枚举类型,结构和宏置于type.h中
窗口应该禁止最大化和其他resize操作,允许最小化操作,清除多余的变量。
游戏开始前,或者游戏中按ESC键弹出主菜单
(2)状态机的实现与重构
将初始化工作置于第一次Render中,而hWnd作为构造参数传入
定义状态机的状态类型
定义状态机的变换机制
实现状态机
(3)主菜单的处理与响应
设计主菜单
响应部分主菜单消息
重构实现
这部分主要介绍一下重构分析内容的关键代码。
(1)窗口的类型
创建窗口时,我们在原先的类型中与上非Thickframe便可去除系统的框架,最大化最小化等具体代码如下:
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW&!WS_THICKFRAME,
CW_USEDEFAULT, 0, WIN_WIDTH, WIN_HEIGHT, NULL, NULL, hInstance, NULL);
(2)创建菜单
在rc文件中创建一个合适的对话框窗口,形如:
首先,定义一个主菜单的回调函数,形如:
INT_PTR CALLBACK MainMenu(HWND, UINT, WPARAM, LPARAM);
然后我们需要在合适的地方,创建这个对话框,一处为初始化的地方,一处为响应ESC键的地方,创建的代码如下:
DialogBox(hInst, MAKEINTRESOURCE(IDD_MAIN_MENU), hWnd, MainMenu);
最后我们实现以下这个回调函数。
INT_PTR CALLBACK MainMenu(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
{
// show the dialog in center.
int x=0;
int y=0;
int width = 0;
int height = 0;
RECT rect1,rect2;
HWND hParent;
hParent = GetParent(hDlg);
GetWindowRect(hParent, &rect1);
GetWindowRect(hDlg, &rect2);
x = (rect1.right + rect1.left - rect2.right + rect2.left)/2;
y = (rect1.bottom + rect1.top - rect2.bottom + rect2.top)/2;
width = rect2.right - rect2.left;
height = rect2.bottom - rect2.top;
MoveWindow(hDlg, x, y, width, height, TRUE);
}
return (INT_PTR)TRUE;
case WM_COMMAND:
if (g_pGame->DealMsg(wParam, lParam))
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
这里,在初始化对话框的时候,我们将对话框Move到游戏窗口的中央,其他时候,我们将消息传递给CGame类去处理,处理完毕,则结束对话框。
(3)状态机设计
我们定义了这样一组游戏状态:
enum GameState
{
GS_GameInit = 0, // 初始化状态
GS_Reset, // 重置状态,重置游戏数据
GS_Goahead, // 前进状态
GS_ClearBlock, // 清除Block状态
GS_CreateNewBlock, // 创建新块状态
GS_Amend, // 修正状态
GS_Refresh, // 刷新状态
GS_Wait, // 等待状态
GS_Failed, // 失败状态
GS_Exit, // 退出状态
};
我们简单看一下游戏的正常流程,
初始化->重置->前进->清除块->创建新块->前进->修正->失败->退出
其他一些情况,如果按了方向键,则在修改方向之后,立刻进入前进状态
如果刷新控制变量g_bFresh为true,并且状态为Wait,则改为前进状态
修正成功后,改为Wait状态
(4)状态机实现
while (m_gameState != GS_Wait)
{
switch(m_gameState)
{
case GS_GameInit:
Init();
m_gameState = GS_Reset;
break;
case GS_Reset:
Reset();
m_gameState = GS_Goahead;
break;
case GS_Goahead:
if (m_team.CouldGoAhead())
{
m_team.GoAhead();
m_gameState = GS_ClearBlock;
}
else
{
m_gameState = GS_Amend;
}
break;
case GS_ClearBlock:
{
if (ClearBlock())
{
m_gameState = GS_CreateNewBlock;
}
else
{
m_gameState = GS_Refresh;
}
}
break;
case GS_CreateNewBlock:
CreateNewBlock();
m_gameState = GS_Refresh;
break;
case GS_Amend:
AutoChangeDirection();
if (!m_team.CouldGoAhead())
{
m_gameState = GS_Failed;
}
else
{
m_gameState = GS_Goahead;
}
break;
case GS_Failed:
m_gameState = GS_Exit;
break;
case GS_Refresh:
Refresh();
m_gameState = GS_Wait;
g_bFresh = false;
break;
case GS_Wait:
default:
return false;
break;
}
(5)定时器
在上次实现的时候,已经实现了,只是为了让Team每隔一秒钟前进一次,而定时器的回调函数中也只需要将刷新控制变量置true即可。
消灭臭虫
消灭几个小害虫,但是援引孙文先生的一句话便是,“革命尚未成功,同志仍需努力!~”
(1)无法最小化
在新版的对话框中,存在无法最小化的问题,我们需要在窗口类型中或上WS_MINIMIZEBOX即可(2)最小化后,图片不见了,过一会儿才会出现
因为我们只有在特定时间才会刷新窗口,最小化还原后,我们并没有显示它,所以我们需要在WM_PAINT消息中,将刷新控制变量置true,如下:
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: 在此添加任意绘图代码...
g_bFresh = true;
EndPaint(hWnd, &ps);
break;
(3)我们希望自己来定义背景,所以在窗口注册的时候,背景类型需要置为NULL
wcex.hbrBackground = NULL;//(HBRUSH)(COLOR_WINDOW+1)
下期预告
其实,显示和处理游戏状态应该是两份相互独立的工作,所以在下一期中,我们将会使用多线程来分别独立实现各自的功能。
我们不希望别人多开我们的游戏,下一期我们还会介绍禁止多开的几种方法
简单介绍一下近几期可能要实现的内容,具体的博客发布时间和发布顺序,可能有所变化。
1. 游戏中的攻防战(上篇)通过OD、CE等软件以及Hook函数篡改游戏
2. 游戏中的攻防战(下篇)简单的防注入措施
3. 通过winsocket实现让我们联机玩贪吃英雄
4. 用Lua脚本驱动程序
5. 地图、事件等编辑器的实现
6. 剧情关卡设计
总共十篇博客后,我们会进行一次 完整游戏展示及总结,这样我们的游戏开发第一季 《贪吃英雄》就告一段落了。
希望大家多多支持,提出宝贵的意见,另,小明的开发qq群欢迎广大游戏爱好者的加入,
开发Q群:69788620。
人应该支配习惯,而决不能让习惯支配人,一 个人不能去掉他的坏习惯,那简直一文不值。 —— 奥斯特洛夫斯基