### 设计打人游戏

许多初学编程的开发人员喜爱设计一些简单的小游戏，一可以增强编程的兴趣和爱好，二可以提高编程技能。可谓“一举两得”。在本节中我们就设计了一个简单的打人游戏。效果如图 1 所示。

图 1 运行效果

下面我们来介绍实例的设计过程。

（1）创建一个基于对话框的工程，工程名称为“My01”。

（2）向对话框中添加群组框、静态文本、编辑框和按钮控件，如图 2 所示。

图 2 对话框设计

（3）按“Ctrl + W”组合键打开类向导窗口，为控件关联成员变量。如图 3 所示。

图 3 类向导窗口

（4）向工程中导入 3 幅位图，他们分别用于表示人物未出现、出现和打中状态，如图 4 所示。

图 4 人物状态

（5）向工程中导入 2 个鼠标指针文件，用于表示鼠标指针默认的形状和鼠标按下时的形状，如图 5 所示。

图 5 鼠标指针形状

（6）定义一个枚举类型，用于表示游戏状态。

enum GameState {GS_BEGIN, GS_END, GS_NONE}; //游戏状态

（7）向对话框类中添加一组成员变量。

CPoint m_ptOrg; //表格原点坐标

int m_nCellWidth; //单元格宽度

int m_nCellHeight; //单元格高度

int m_nPerson; //人物出现的索引

CRect m_GridRC; //表格区域

CRect m_PersonRC; //人物出现的区域

BOOL m_bStart; //游戏是否开始

GameState m_State; //游戏状态

BOOL m_bHit; //是否击中人物

int m_nLevels[100]; //游戏级别

（8）在对话框类的构造函数中初始化数据成员。

m_ptOrg.x = m_ptOrg.y = 15; //设置坐标原点

m_nCellHeight = 39; //设置单元格高度

m_nCellWidth = 37; //设置单元格宽度

m_nPerson = -1;

for(int i=0; i<100; i++) //设置游戏级别分数段

{

m_nLevels[i] = 1200 - i*100;

}

m_bHit = FALSE;

m_State = GS_NONE; //初始化游戏状态

（9）在对话框初始化时设置随机数种子。

srand((unsigned)time(NULL));

（10）在对话框的 OnPaint 方法中绘制表格和人物。

void CMy01Dlg::OnPaint()

{

//…代码省略

m_GridRC.left = m_ptOrg.x; //设置表格区域

m_GridRC.top = m_ptOrg.y;

m_GridRC.right = m_ptOrg.x + 9*m_nCellWidth;

m_GridRC.bottom = m_ptOrg.y + 9*m_nCellHeight;

//绘制表格

CDC* pDC = GetDC(); //获取窗口设备上下文

pDC->FillRect(m_GridRC, NULL); //填充区域

for(int i=0; i<=9; i++) //绘制列

{

pDC->MoveTo(m_ptOrg.x, m_ptOrg.y + i*m_nCellHeight);

pDC->LineTo(m_ptOrg.x + 9*m_nCellWidth ,m_ptOrg.y + i*m_nCellHeight);

}

for(int j=0; j<=9; j++) //绘制行

{

pDC->MoveTo(m_ptOrg.x + j*m_nCellWidth, m_ptOrg.y);

pDC->LineTo(m_ptOrg.x + j*m_nCellWidth, m_ptOrg.y + 9*m_nCellHeight);

}

if (m_nPerson > 0) //绘制人物

{

CBitmap bmp; //定义位图对象

bmp.LoadBitmap(IDB_PERSON); //加载位图

CDC memDC;

memDC.CreateCompatibleDC(pDC); //创建兼容的设备上下文

memDC.SelectObject(&bmp); //选中位图

pDC->StretchBlt(m_PersonRC.left, m_PersonRC.top, m_PersonRC.Width(),

m_PersonRC.Height(), &memDC, 0, 0, m_PersonRC.Width()-1,

m_PersonRC.Height()-1, SRCCOPY); //绘制任务

bmp.DeleteObject(); //释放位图对象

memDC.DeleteDC(); //释放内存设备上下文

}

ReleaseDC(pDC);

}

（11）处理对话框的 WM_TIMER 消息，在游戏开始后，会定时显示人物，如果人物没有被击中，则增加逃逸数量。在积分达到一定阶段将进入下一关。

void CMy01Dlg::OnTimer(UINT nIDEvent)

{

m_nPerson = rand() % 73; //产生一个随机数

if (m_nPerson == 0)

m_nPerson = 1;

int nX = m_nPerson % 9; //计算人物出现的位置

int nY = m_nPerson / 9;

if(nX == 0)

{

nX = 9;

}

else

{

nY++;

}

//确定小人出现的区域

m_PersonRC.left = m_ptOrg.x + (nX-1)*m_nCellWidth;

m_PersonRC.right = m_PersonRC.left + m_nCellWidth;

m_PersonRC.top = m_ptOrg.y + (nY-1)*m_nCellHeight;

m_PersonRC.bottom = m_PersonRC.top + m_nCellHeight;

InvalidateRect(m_PersonRC); //更新任务区域

if (m_bHit == FALSE) //人物没有被击中

{

m_EscCount++; //增加逃逸数量

UpdateData(FALSE);

}

m_bHit = FALSE;

if (m_nLevel <= 100)

{

if (m_Grade >= m_nLevel*50*15)

{

m_nLevel++;

UpdateData(FALSE);

NextLevel(); //下一关

}

}

else //

{

OnEndgame(); //结束游戏

}

CDialog::OnTimer(nIDEvent);

}

（12）向对话框中添加 NextLevel 方法，开始新一关游戏。

void CMy01Dlg::NextLevel()

{

KillTimer(1); //结束计时器

m_State = GS_BEGIN;

m_bHit = TRUE;

SetTimer(1, m_nLevels[m_nLevel-1], NULL); //重新开始计时器

}

（13）处理“开始游戏”按钮的单击事件，开始新的游戏。

void CMy01Dlg::OnStart()

{

if (m_State != GS_BEGIN)

{

m_State = GS_BEGIN;

m_bHit = TRUE;

SetTimer(1, m_nLevels[m_nLevel-1], NULL); //创建一个计时器，开始新的游戏

}

}

（14）处理“终止游戏”按钮的单击事件，结束正在进行的游戏。

void CMy01Dlg::OnEndgame()

{

if (m_State == GS_BEGIN) //游戏已开始

{

m_State = GS_END; //设置游戏状态

KillTimer(1); //结束计时器

m_Grade = 0; //初始化数据

m_EscCount = 0;

m_DeathCount = 0;

m_MisCount = 0;

m_nLevel = 1;

UpdateData(FALSE);

m_nPerson = -1;

InvalidateRect(m_PersonRC); //更新任务区域

}

}

（15）处理鼠标按下时的事件，设置鼠标指针形状。

void CMy01Dlg::OnLButtonDown(UINT nFlags, CPoint point)

{

m_bHit = TRUE;

if (m_GridRC.PtInRect(point)) //如果指针位于表格区域

{

//设置新的鼠标指针形状

SetCursor(LoadCursor(AfxGetResourceHandle(), MAKEINTRESOURCE(IDC_CURSOR1)));

}

CDialog::OnLButtonDown(nFlags, point);

}

（16）处理鼠标按钮释放时的事件，判断是否击中目标，如果击中目标，则设置人物被击中，增加积分，否则累计未击中次数。

void CMy01Dlg::OnLButtonUp(UINT nFlags, CPoint point)

{

if (m_State == GS_BEGIN)

{

if (m_PersonRC.PtInRect(point)) //击中目标

{

CDC *pDC = GetDC(); //获取窗口设备上下文

CBitmap bmp;

bmp.LoadBitmap(IDB_HITPERSON); //加载位图

CDC memDC;

memDC.CreateCompatibleDC(pDC); //创建内存设备上下文

memDC.SelectObject(&bmp); //选中位图

pDC->StretchBlt(m_PersonRC.left, m_PersonRC.top, m_PersonRC.Width(),

m_PersonRC.Height(), &memDC, 0, 0, m_PersonRC.Width()-1,

m_PersonRC.Height()-1, SRCCOPY); //绘制位图

bmp.DeleteObject(); //释放位图对象

memDC.DeleteDC();

ReleaseDC(pDC);

m_DeathCount++;

m_Grade = m_DeathCount*50; //设置积分

}

else //未击中目标

{

m_MisCount++; //累加未击中次数

}

UpdateData(FALSE);

}

if (m_GridRC.PtInRect(point))

{

//设置鼠标指针新的形状

SetCursor(LoadCursor(AfxGetResourceHandle(), MAKEINTRESOURCE(IDC_CURSOR2)));

}

CDialog::OnLButtonUp(nFlags, point);

}

(17)运行程序，效果如图 6 所示。

图 6 运行效果