基于MFC实现的赛车游戏
一、问题描述
游戏背景为一环形车道图,选择菜单选项“开始游戏”则可开始游戏。游戏的任务是使用键盘上的方向键操纵赛道上的蓝色赛车追赶红色赛车,红色赛车沿车道顺时针行驶,出发点和终点均位于车道左上方。任一赛车先达到终点则比赛结束。
二、编程要求
- 矩形车道和赛车的图像可辨认,显示正确
- 赛道内有红色和蓝色两辆赛车,红色赛车由程序控制,蓝色赛车用键盘操纵
- 赛车在矩形车道上运行,方向应与赛车形状相匹配
- 用光标键控制蓝色赛车追赶红色赛车,赛车不能偏离跑道
- 当某一赛车到达终点时,显示消息框
- 正确设置游戏菜单(开始,结束)
三、要点分析
本题主要涉及到的知识点有:定时器消息、位图显示、画笔/画刷的使用、显示文字等工作,难度适中。
该题的难点在于如何保证赛车的头部永远向前。这里使用了一幅位图,它由四部分组成,如图1所示。为了方便编程,假定赛车是正方形,即赛车位图的宽度和高度相同。在本题中每一辆赛车位图的宽度和高度均为48个像素点,整个位图的宽度为192个像素点,高度为48个像素点。根据赛车在运行时的方向,将赛车位图的一部分显示到屏幕。
四、解题步骤
首先用AppWizard生成一个名为Car的SDI程序框架,其选项均用缺省设置。
编辑项目的菜单资源,在框架窗口的主菜单(IDR_MAINFRAME)中添加一个下拉菜单选项“游戏”,添加两个菜单选项“开始”和“结束”,并删除不必要的菜单选项,如图2所示。菜单属性设置如表5所示。
ID | Caption | Prompt |
---|---|---|
ID-BEGIN | 开始 | 开始 |
ID_STOP | 结束 | 结束 |
利用ClassWizrd为视图类添加与这些菜单选项对应的成员函数。进入ClassWizard的Message Maps选项卡,选择Class Name项为CCarView,在Object IDs列表框中分别选择新添加的菜单选项的ID,在Messages列表框中选择COMMAND,按下Add Function按钮添加成员函数。ClassWizard会为这些菜单选项添加相应的消息响应函数(目前尚没有具体内容,需要程序员自行加入有关的处理代码),并将其声明加入OCarView类定义,在消息映射宏中加入相应的消息映射。
使用Developer Studio菜单的Insert / Resource…选项调出Insert Source对话框,为项目添加两个位图资源,分别表示红车和蓝车的位图。相应位图的资源ID分别为IDB_BITMAP1和IDB_BITMAP2。
完成以上工作后,即可修改程序框架,添加必要的代码。
五、源程序清单
在视图类OCarView类头文件首部添加三个常量,在类中再添加若干数据成员:
const CRect rectRed = CRect (30, 30, 750, 490); //红车位置
const CRect rectBlue = CRect (30, 90, 700, 430); //蓝车位置
const CRect rectGreen = Crect (150, 150, 640, 340); //草坪位置
class OCarView: public CView
{
//此处略去若干行由系统生成的代码
CBitmap m_bmpRed; //红车的位图
CBitmap m_bmpBlue; //蓝车的位图
int m_nWidth; //位图的宽度
int m_nHeight; //位图的高度
CRect m_rectRed; //红车的区域
int m_nRed; //红车的方向,0:左;1:下;2:右;3:上
CRect m_rectBlue; //蓝车的区域
int m_nBlue; //蓝车的方向,0:左;1:下;2:右;3:上
};
在视图类的构造函数中添加代码:
CCarView ::CCarView ( )
{
m_bmpRed. LoadBitmap (IDB_BITMAP1);
m_bmpBlue. LoadBitmap (IDB_BITMAP2);
BITMAP BM;
m_bmpRed. GetBitmap (&BM);
m_nWidth = BM. bmWidth/4;
m_nHeight = BM. bmHeight;
}
在视图类的虚函数OnInitialUpdate ( )中添加代码:
void CCarView :: OnInitialUpdate ( )
{
CView :: OnInitialUpdate ( );
//定义红车的赛道
m_rectRed = CRect (rectRed. Left, rectRed. top, rectRed. left + m_nWidth, rectRed. top + m_nHeight);
//定义蓝车的赛道
m_rectBlue = CRect (rectBlue. left, rectBlue. top,
rectBlue. left + m_nWidth, rectBlue. top + m_nHeight);
//赛车开始时方向均向右
m_nRed = m_nBlue = 0;
}
在OnDraw ( )函数中添加代码:
void CCarView :: OnDraw (CDC * pDC)
{
CCarDoc * pDoc = GetDocument ( );
ASSERT_VALID (pDoc);
CBrush brushgreen, * pubrushOld;
CPen penBlue, * ppenOld;
penBlue. CreatePen (PS_SOLID, 1, RGB (0, 0, 128) );
brushgreen. CreateSolidBrush (RGB (0, 255, 0) );
ppenOld = pDC - > SelectObject (&penBlue);
pbrushOld = pDC - > SelectObject (&brushgreen);
pDC - > Rectangle (&rectRed); //画赛道
pDC - > SelectStockObject (WHITE_BRUSH);
pDC - > Rectangle (&rectGreen); //画中间区域
CDC MemDC;
MemDC. CreateCompatibleDC (NULL); //建立虚拟设备环境
MemDC. SelectObject (&m_bmpRed);
pDC - > BitBlt (m_rectRed. left + 1, m_rectRed. top + 1, m_nWidth, m_nHeight, &MemDC, m_nWidth * m_nRed, 0, SRCCOPY); //画红车
MemDC. SelectObject (&m_bmpBlue);
pDC - > BitBlt (m_rectBlue. left + 1, m_rectBlue. top + 1, m_nWidth, m_nHeight, &MemDC, m_nWidth * m_rectBlue, 0, SRCCOPY); //画蓝车
pDC - > SelectObject (pbrushOld);
pDC - > SelectObject (ppenOld);
}
在视图类中添加定时器消息处理函数OnTimer ( ),并在其中添加代码:
void CCarView :: OnTimer (UINT nIDEvent)
{
InvalidateRect (m_rectRed);
switch (m_nRed) //红车的方向
{
case 0: //向右
if (m_rectRed. right + 10 < rectRed. right)
m_rectRed + = CSize (10, 0);
else
m_nRed = 1;
break;
case 1: //向下
if (m_rectRed. bottom + 10 < rectRed. bottom)
m_rectRed + = CSize (0, 10);
else
m_nRed = 2;
break;
case 2: //向左
if (m_rectRed. left > rectRed. left)
m_rectRed + = CSize (-10, 0);
else
m_nRed = 3;
break;
case 3: //向上
if (m_rectRed. top > rectRed. top)
m_rectRed + = CSize (0, -10);
else
{
KillTimer (1); //游戏结束
MessageBox (〃You Lose.〃);
}
}
InvalidateRect (m_rectRed);
CView :: OnTimer (nIDEvent);
}
在视图类中添加键盘按下消息处理函数OnKeyDown ( ),并在其中添加代码:
void CCarView :: OnKeyDown (UINT nchar, UINT nRepCnt, UINT nFlags)
{
InvalidateRect (m_rectBlue);
switch (nChar)
{
case 39: //向左光标键
m_nBlue = 0;
if (m_rectBlue. right + 10 < rectBlue. right&&m_rectBlue. bottom < rectGreen. top)
m_rectBlue + = CSize (10, 0);
break;
case 40: //向下光标键
m_nBlue = 1;
if (m_rectBlue. bottom + 10 < rectBlue. bottom&&m_rectBlue. left > rectGreen. right)
m_rectBlue + = CSize (0, 10);
break;
case 37: //向右光标键
m_nBlue = 2;
if (m_rectBlue.left > rectBlue. left&&m_rectBlue. top > rectGreen. bottom)
m_rectBlue + = CSize (-10, 0);
break;
case 38: //向上光标键
m_nBlue = 3;
if (m_rectBlue. top > rectRed. top&&m_rectBlue. right < rectGreen. left)
m_rectBlue + = CSize (0, -10);
if (m_rectBlue. Bottom< rectBlue. top)
{
KillTimer (1); / /游戏结束
MessageBox (〃You Win.〃);
}
}
InvalidateRect (m_rectBlue);
CView :: OnKeyDown (nChar, nRepCnt, nFlags);
}
在视图类中新增菜单对应的消息处理函数中添加代码:
void CCarView :: OnBegin ( )
{
OnInitialUpdate ( );
SetTimer (1, 100, NULL); / /设置定时器
}
void CCarView :: OnStop ( )
{
KillTimer (1); / /删除定时器
}
六、输入输出
用户可选择框架窗口菜单“演示”中的选项“开始”,运行结果如图3所示。
七、小结
在视图类中重载了键盘按下消息处理函数OnKeyDown ( ),在该函数中,仅响应用户按下的四个方向键,用以修改赛车的位置。
在OnKeyDown ( )中调用了两次InvalidateRect ( ),两者都只有一个参数,表明擦除时应重画背景。
八、进一步工作
本程序还可以添加“信号灯”功能,在中间白色区域画一信号灯,当用户选择了“开始”后不能马上比赛,而要等到信号灯从红变黄,再从黄变绿后,才能开始比赛。
另外还可以添加难度菜单,难度越高,红车速度越快。