小明陪你编游戏系列(三)第一次重构与状态机的实现

要使别人喜欢你,首先你得改变对人的态度,把精神放得轻松一点,表情自然,笑容可掬,这样别人就会对你产生喜爱的感觉了。 —— 卡耐基

前情提要

上回我们简单的实现了第一期所说的内容,但是并没有对属性值具体的处理,不过这一期仍然不会对它处理。因为在正式开发之前,小明决定设计一个妥善的结构来作为一个号的开始。

已经完成的代码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文件中创建一个合适的对话框窗口,形如:


为这几个按钮添加对应的控件ID,相信熟悉win32 API的朋友们,调用rc资源应该没什么问题,如果有问题详细建议参见《精通win32 API》一书,很简单,这里写一些扼要的代码。小明偷偷地告诉你,其实About对话框已经给了大家一个很明显的调用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。

人应该支配习惯,而决不能让习惯支配人,一 个人不能去掉他的坏习惯,那简直一文不值。 —— 奥斯特洛夫斯基

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值