博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
逃逸的小球(BALL Escape)
阅读量:636 次
发布时间:2019-03-14

本文共 4912 字,大约阅读时间需要 16 分钟。

    由于最近要找工作,要将自己浸泡在编码的环境中热身,也抱着认真学习MFC精神的意图编写了这个小程序;本文章重在锻炼MFC中绘图、按键控制,编写过程中也遇到了一些困难,尤其是第四节提到的关键技术攻克,还望各位大侠指点。

一. 功能介绍

此应用程序具有一个红球和30个蓝球,用户按F2开始游戏,通过键盘方向键控制红球走向避免和篮球进行碰撞,记录红球逃逸时间,测试用户的应变能力;背景音乐采用经典魂斗罗。

程序外观如下图所示(比较丑陋):

二. 采用的MFC技术

2.1 控件背景重绘

在程序中将对话框的背景色设置为白色,则需要再对话框绘制前将其画刷设置为白色;

生成一个白色画刷:

#define WRITE RGB(255, 255, 255)// 设置对话框画刷颜色m_Brush.CreateSolidBrush(WRITE);

重新绑定颜色控制消息处理函数,并且隐藏OnCtlColor函数(通常人都称为重载,这个是不严格的,应该称其为隐藏),返回绘制对话框的画刷:

//设置背景色画刷HBRUSH CEscapeBallDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor){	HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);	//判断是否为对话框	if(nCtlColor == CTLCOLOR_DLG)		return m_Brush;  	return hbr;}

2.2 按键响应

针对按回车会关闭窗口问题,并且程序中采用F2开始游戏,采用隐藏消息预处理函数PreTranslateMessage:

//用于处理按键控制:F2,方向键BOOL CEscapeBallDlg::PreTranslateMessage(MSG* pMsg){	if (pMsg->wParam == VK_RETURN)        //回车键不进行处理		return TRUE;	if (pMsg->wParam == VK_F2 && !m_bEscaping)			  //按键F2开始游戏			BeginGame();							return CDialog::PreTranslateMessage(pMsg);}

2.3 播放音乐

在应用程序中添加音乐,自然也添加了激情。有些应用程序发布的时候并没有带音频文件,其将音频作为资源集成到MFC中;

先将WAV格式文件导入到MFC工程资源中,对音频采用异步、循环播放的模式:

PlaySound(MAKEINTRESOURCE(IDR_WAVE_HUNDOULUO), NULL, SND_RESOURCE|SND_ASYNC|SND_LOOP);

三. 球移动碰撞算法

整个程序挖掘不出高端算法,本人也就介绍下程序中采用的简单的篮球运动方向算法;蓝球的运动方向是在 游戏开始 和 碰撞边界反弹的时候进行确定,采用半随机的方式。所谓半随机:在确定某个篮球运动方向前,先抛硬币正反面决定是否随机产生方向;如果不随机方向,则根据红球当前的位置作为走向,如果随机,这产生一个随机的方向。

/************************************************************************函数名称:ComputeForward*函数参数:BallInfo *bBall: 当前篮球的信息,包括位置、大小、颜色等*          BallInfo *rBall: 当前红球的信息,包括位置、大小、颜色等*		   BOOL is_rand   : 是否采用半随机方法;*                           TRUE表示采用随机算法*                           FALSE表示以红球作为运动方向*返回值 : 无*功能   : 计算蓝球运动偏移方向和大小*************************************************************************/void CEscapeBallDlg::ComputeForward(BallInfo *bBall, BallInfo* rBall, BOOL is_rand = TRUE){	int x = bBall->pos_x;	int y = bBall->pos_y;	int x1 = rBall->pos_x;	int y1 = rBall->pos_y;	//随机硬币	BOOL is_randDirect = (rand() % 2) ? TRUE:FALSE;	if (is_rand && is_randDirect)	{		int rand_x = rand() % m_MaxX;		int rand_y = rand() % m_MaxY;		x1 = rand_x;		y1 = rand_y;	}	//非特殊情况,根据斜率比求出移动偏移量	int offset_x = fabs((x - x1) * 1.0) 				   / sqrt((pow((x - x1) * 1.0, 2.0) + pow((y - y1) * 1.0, 2.0))) 				   * MOVEOFFSET;	int offset_y = fabs((y - y1) * 1.0) 				   / sqrt((pow((x - x1) * 1.0, 2.0) + pow((y - y1) * 1.0, 2.0))) 				   * MOVEOFFSET;		//计算球的走向	bBall->offset_x = (x1 - x) < 0 ? -1 * offset_x : offset_x;	bBall->offset_y = (y1 - y) < 0 ? -1 * offset_y : offset_y;}

四. 关键技术攻克

4.1 问题研究

通过方向键控制小球,那如何捕获按键呢?在2.2节中介绍了采用隐藏消息预处理函数PreTranslateMessage中添加对按键消息的处理,顺利获取方向键虚拟键盘码,每捕获取一次按键消息就将红色控制球移动给定的距离值。

当方向键按下不松开的情况下,操作系统也会不断产生按键消息,但有一个很严重的问题,当按下一个方向键不松开的情况下,一开始移动总有一小会儿的延迟。

经过细致的研究发现:以按下左方向键不放为例,会连续不断地产生按左方向键的消息,如下图所示,但是LEFT1消息与LEFT2消息到来的时间间隔,比后面的任意按键消息的间隔要长,而且后面的消息时间间隔基本相同;如果你打开一个文本编辑器去控制光标的移动,同样会发现相同的问题,如果按下方向键不松开,按下方向键的开始时候光标移动会有短暂的延迟。

为什么会产生这种现象?因为当你按下某一个按键的时候,操作系统底层会先产生一个按键消息,然后进行一个时间判定,如果在这段时间内按键一直处于按下状态,那么接下来会按照某一极小的时间间隔产生按键消息 直到按键松开;由此可得,刚按下按键操作的延迟主要来源于底层对"是否一直按下此键"做出的判断时间。

这个情况在编辑器中允许出现,可在APM高操作的游戏中可不能出现,大大降低了用户操作的快感,那如何解决这个问题呢?

4.2 解决方法

思考了不少时间,尝试了不少方法,就在本人准备放弃了一刻,尝试了最后一个思路,而且一次性成功了,与大家分享一下。

当根据系统发送的消息不能很好的完成操作处理的时,可以自己获取系统的状态并且进行相应的处理操作。程序中游戏开始后创建一个线程,获取按键的状态(通过GetKeyStae函数),并根据按键状态信息,得到红球的运动方向(总共八个方向),进行相应的移动操作。

//红球控制线程UINT CEscapeBallDlg::RedBallMoveFunc(LPVOID lParam){	//获得对话框的指针	CEscapeBallDlg* pDlg = (CEscapeBallDlg *)lParam;	while (pDlg->m_bEscaping)  //正在游戏中	{		//仅仅采用低四位作为上下左右的标示		//1表示按下,反正则没有按下		//从高位到低位分别为上、下、左、右按键		BYTE keyState = 0;   		keyState = (GetKeyState(VK_UP) & 0x8000) ? (keyState | 0x08) : keyState;		keyState = (GetKeyState(VK_DOWN) & 0x8000) ? (keyState | 0x04) : keyState;		keyState = (GetKeyState(VK_LEFT) & 0x8000) ? (keyState | 0x02) : keyState;		keyState = (GetKeyState(VK_RIGHT) & 0x8000) ? (keyState | 0x01) : keyState;		//抵消两个相反方向的按键		if ((keyState & 0x01) && (keyState & 0x02))		{			keyState = keyState & 0x0C;		}		if ((keyState & 0x04) && (keyState & 0x08))		{			keyState = keyState & 0x03;		}		int offset_x = 0;    //x偏移大小		int offset_y = 0;    //y偏移大小		//八个方向判断		switch(keyState)		{		case UP: 			offset_x = 0;			offset_y = -MOVEOFFSET;			break;		case DOWN: 			offset_x = 0;			offset_y = MOVEOFFSET;			break;		case LEFT: 			offset_x = -MOVEOFFSET;			offset_y = 0;			break;		case RIGHT: 			offset_x = MOVEOFFSET;			offset_y = 0;			break;		case LEFTUP: 			offset_x = -SQRTMOVEOFFSET;			offset_y = -SQRTMOVEOFFSET;			break;		case LEFTDOWN: 			offset_x = -SQRTMOVEOFFSET;			offset_y = SQRTMOVEOFFSET;			break;		case RIGHTUP: 			offset_x = SQRTMOVEOFFSET;			offset_y = -SQRTMOVEOFFSET;			break;		case RIGHTDOWN: 			offset_x = SQRTMOVEOFFSET;			offset_y = SQRTMOVEOFFSET;			break;		default:			break;		}		if (offset_x != 0 || offset_y != 0)  //计算如果发生按键,红球根据偏移进行移动 			pDlg->ReDrawRedBall(offset_x, offset_y);		if (!pDlg->CheckEscape())            //发生碰撞则结束游戏		{			pDlg->EndGame();				 //结束本轮游戏		}		Sleep(30);    //适当的延迟,不然球速太快了	}	return 0;}

转载地址:http://iygoz.baihongyu.com/

你可能感兴趣的文章