本次吃豆子游戏主要知识点包括以下几个方面

1  CView类中的消息响应

2  控件的消息响应

3  基于CView类内的具体游戏实现

4  游戏图形的实现用CDC类实现

此次吃豆子游戏用的是MFC的开发环境,所以打开VisualC++新建一个MFC APPWizard单文档工程,取名一个Eat Bean1的工程名称。



本次吃豆子基本流程与大体思想

1 定义吃类 和 食物类,初始化 嘴 的各项成员变量,包括嘴的图形出现在屏幕的初始位置,及行走的方向。食物类的定义包括出现的初始位置,以及食物是否被吃掉的判断。

2 在CView类上运用MFC提供的Windows消息中WM_TIMER消息,运用OnTimer()函数让系统提供一个时钟,记录游戏。

4 具体的游戏实现,包括 嘴 撞到墙和吃完豆子自己结束等。

5 具体键盘游戏操作运用到Windows消息响应中的WM_KEYDOWN,用OnKeyDown()来响应玩家的实际操作。

第一部分 

首先在已有的工程"ClassView"中右键CView类添加以下Windows消息

1 WM_KEYDOWN  响应键盘消息

2 WM_RBUTTONDOWN  

3 WM_TIMER    定时器

再右键CView类选择 “ADD VirtualFuncition”选OnlnitialUpdate()

在本次游戏中 OnlnitialUpdate()的主要作用是对 嘴和豆子进行初始化

还要再添加一个成员函数oninit() 对嘴的外形进行初始化


第二步 控件的设计

设计游戏的一些控件来控制 游戏开始 游戏结束 游戏暂停。

我们可以点击 工作空间 的 ResourceView 的 Menu 文件夹 进行控件的具体设计


把IDM_MAINFRAME 中默认的控件全部删除 右键其中的标题栏,点击属性,会得到一个菜单栏标题,建立一个叫做 游戏 菜单栏标题


点击并且在已有控件中的列表点击属性,进行设置。我们分别建立与 标明 对应的ID

游戏开始 IDM_START

游戏暂停 IDM_PAUSE

游戏继续 IDM_CONTINUE

游戏退出 IDM_EXIT

重新开始 IDM_RESTART


成功设置ID之后 我们分别右键对各项属性进行消息响应处理函数的生成

1 右键 游戏开始

2 点击 类向导建立

3 在Messgae Map页面,这里我们选择 IDM_START,具体实现的环境是 CView类,所以我们要把 Classname 的 默认 CMainFrame 改为 CView类,并且在 Messages 设置为命令消息。



最后我们回到原来的ClassView 中去看看我们的所有的函数集合


第三步 具体实现游戏

首先我们在文件开头处分别 定义 嘴 和 食物 的全局变量

OnKeyDown函数的第一个参数UNIT nChar是接收用户键入的信息,然后我们用switch进行选择判断

class Eat
 {
 public:
int x,y;
int direct;
 }Eat[50];  


 class Food
 {
 public:
int x;
int y;
int isfood;
 }Food;
再者
void CEatBean1View::OnInitialUpdate() 
 {
CView::OnInitialUpdate();
Eat[0].x=10;
Eat[0].y=10;
Eat[0].direct=1;//初始化 嘴




Food.x=15;
Food.y=15;
Food.isfood=1;//初始化一个豆子


// TODO: Add your specialized code here and/or call the base class

 }
代码说明 
对嘴和豆子的初始坐标进行初始化
对 OnKeyDown()具体添加代码
 void CEatBean1View::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
 {
// TODO: Add your message handler code here and/or call default
switch(nChar)
{
case VK_UP:if(Eat[0].direct!=2)Eat[0].direct=1;break;
case VK_DOWN:if(Eat[0].direct!=1)Eat[0].direct=2;break;
case VK_LEFT:if(Eat[0].direct!=4)Eat[0].direct=3;break;
case VK_RIGHT:if(Eat[0].direct!=3)Eat[0].direct=4;break;//Eat[0]代表的是嘴,对嘴方向的Eat[0].direct进行判断
}
CView::OnKeyDown(nChar, nRepCnt, nFlags);
 }

代码说明:

Eat[0]代表的是嘴,我们对嘴的方向Eat[0].direct进行判断。

case VK_UP:if(Eat[0].direct!=2)Eat[0].direct=1;break;意思是 当Eat[0].direct的方向此时并不等于 下 的时候 才能做出 上 的操作动作,否则则忽略用户 向上的操作按键效果,其实就是让嘴在最开始的时候一直向上前进。

对 OnRButtonDown()具体添加代码
 void CEatBean1View::OnRButtonDown(UINT nFlags, CPoint point) 
 {
// TODO: Add your message handler code here and/or call default
CString str;
str.Format("%d,%d",point.x,point.y);
AfxMessageBox(str);
CView::OnRButtonDown(nFlags,point);
CView::OnRButtonDown(nFlags,point);
CView::OnRButtonDown(nFlags,point);//用鼠标右键屏幕,就会马上显示坐标


CView::OnRButtonDown(nFlags, point);
 }
OnRButtonDown()的作用是:用鼠标右键屏幕,就会马上显示当前位置的坐标信息。
对 oninit()具体添加代码
void CEatBean1View::oninit()
 {
CDC*pDC=GetDC();
CBrush DrawBrush=(RGB(100,100,100));//定义画刷一个DrawBrush对象,并且用RGB(100,100,100)初始化颜色
CBrush*Drawbrush=pDC->SelectObject(&DrawBrush);
pDC->Pie(Eat[0].x*20,Eat[0].y*20,(Eat[0].x+1)*20,(Eat[0].y+1)*20,(Eat[0].x)*20,(Eat[0].y)*20,(Eat[0].x+1)*20,(Eat[0].y)*20);
        pDC->Ellipse(Food.x*20,Food.y*20,(Food.x+1)*20,(Food.y+1)*20);




     pDC->SelectObject(DrawBrush);


 }

利用Windows给我们提供的CDC类进行画图,首先用一个指向CDC类的指针去接受与该窗口关联的DC句柄,然后定义画刷DrawBrush对象,并且用RGB(100,100,100)进行颜色初始化

pDC->Pie(Eat[0].x*20,Eat[0].y*20,(Eat[0].x+1)*20,(Eat[0].y+1)*20,(Eat[0].x)*20,(Eat[0].y)*20,
(Eat[0].x+1)*20,(Eat[0].y)*20);画出一个没有闭合的嘴
        pDC->Ellipse(Food.x*20,Food.y*20,(Food.x+1)*20,(Food.y+1)*20);画出一个圆来表示已经闭合了得嘴

两个函数的公能:Eliiipse()绘制椭圆。椭圆与其外接矩形的中心由 x1,y1,x2,y2 或lpRect指定,椭圆由当前画笔绘制,内部由当前画刷填充。
该函数绘制的图形可以扩充到但并不包括右边及底部坐标,亦即图形的高度是y2-y1,宽度是x2-x1。如果外接椭圆的宽度或高度是0,则不绘制椭圆。

Pie()画一个饼图。由一段椭圆弧形成,其中心点和两个端点已经用线段连接。弧中心是由x1,y1,x2,y2(或lpRect)指定的外接矩形的中心。弧的起点和终点由x3,y3,x4,y4(或ptStart和 ptEnd)指定。弧由选定笔沿逆时针绘制,另外还有两条从端点到中心点的连线。饼图区域由当前画刷填充。如果x3等于x4且y3等于y4,则结果为一个椭圆。它只有一条中心点与点(x3,y3)或点(x4,y4)的连线。该函数绘制的图形延伸到但不包括右边和底部坐标,即图形高度为y2-y1,宽度为x2-x1,外接矩形的宽度和高度都必须大于2单位且小于32,767单位。

对控件添加代码

void CEatBean1View::OnStart() 
 {
// TODO: Add your command handler code here
SetTimer(1,1000,NULL);
AfxMessageBox("3秒后游戏开始!");


 }


 void CEatBean1View::OnPause() 
 {
// TODO: Add your command handler code here
KillTimer(1);
     AfxMessageBox("暂停游戏...");
 }


 void CEatBean1View::OnContinue() 
 {
// TODO: Add your command handler code here
SetTimer(1,1000,NULL);


 }


 void CEatBean1View::OnExit() 
 {
// TODO: Add your command handler code here
AfxMessageBox("退出游戏...");
AfxGetMainWnd()->SendMessage(WM_CLOSE);//退出
 }


 void CEatBean1View::OnRestart() 
 {
// TODO: Add your command handler code here

 }

解释一下 SetTimer(1,1000,NULL);第一个参数是对应计时器的代号 第二个参数是时间间隔 单位为毫秒 第三个参数作用是使用OnTimer函数
AfxMessageBox的作用是创建消息框。

对 OnDraw()添加代码

void CEatBean1View::OnDraw(CDC* pDC)
 {
CEatBean1Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
CBrush backBrush(RGB(20,0,0));
  CBrush* pOldBrush = pDC->SelectObject(&backBrush);
  CRect rect;
  pDC->GetClipBox(&rect);
  pDC->PatBlt(rect.left, rect.top, rect.Width(), rect.Height(),PATCOPY);
  pDC->SelectObject(pOldBrush);
 //pDC->Ellipse(Eat[1].x*20,Eat[1].y*20,(Eat[1].x+1)*20,(Eat[1].y+1)*20);


  pDC->Rectangle(19,19,501,501);


  oninit();


// TODO: add draw code for native data here
 }
代码说明 此处使用画刷画出一个背景,并且画出三个矩形
对 OnTime()添加代码
CDC*pDC=GetDC();




      if(Eat[0].x*20<=37||Eat[0].y*20<=37||Eat[0].x*20>=462||Eat[0].y*20>=462)
{


          KillTimer(1);
          AfxMessageBox("游戏结束");
} //进行撞墙判断
if(Food.x*20==Eat[0].x*20&&Food.y*20==Eat[0].y*20)
{
Food.isfood=0;
}//对嘴是否将豆子吃掉进行判断 如果嘴的坐标和豆子的坐标重合 就说明嘴将豆子的坐标吃掉了
 if(
 Food.isfood==0)
{
KillTimer(1);

AfxMessageBox("游戏结束");
  }//吃完豆子结束游戏


pDC->SelectStockObject(WHITE_PEN);
 pDC->Rectangle(Eat[0].x*20,Eat[0].y*20,(Eat[0].x+1)*20,(Eat[0].y+1)*20);


   
      


if(Eat[0].direct==1)
{ Eat[0].y--;
   pDC->SelectStockObject(BLACK_PEN);
   
   CBrush DrawBrush=(RGB(100,100,100));
   CBrush *Drawbrush=pDC->SelectObject(&DrawBrush);
   if  ((Eat[0].x+Eat[0].y)%2==0)pDC->Ellipse(Eat[0].x*20,Eat[0].y*20,(Eat[0].x+1)*20,(Eat[0].y+1)*20);
   else 


 (pDC->Pie(Eat[0].x*20,Eat[0].y*20,(Eat[0].x+1)*20,(Eat[0].y+1)*20,(Eat[0].x)*20,(Eat[0].y)*20,(Eat[0].x+1)*20,(Eat[0].y)*20));




   pDC->SelectObject(DrawBrush);
   }//


   if(Eat[0].direct==2)
    { Eat[0].y++;
   pDC->SelectStockObject(BLACK_PEN);
   
   CBrush DrawBrush=(RGB(100,100,100));
   CBrush *Drawbrush=pDC->SelectObject(&DrawBrush);
    if  ((Eat[0].x+Eat[0].y)%2==0)pDC->Ellipse(Eat[0].x*20,Eat[0].y*20,(Eat[0].x+1)*20,(Eat[0].y+1)*20);
   else
   pDC->Pie(Eat[0].x*20,Eat[0].y*20,(Eat[0].x+1)*20,(Eat[0].y+1)*20,(Eat[0].x+1)*20,(Eat[0].y+1)*20,(Eat[0].x)*20,(Eat[0].y+1)*20);
   pDC->SelectObject(DrawBrush);
    }


     if(Eat[0].direct==3)
    { Eat[0].x--;
   pDC->SelectStockObject(BLACK_PEN);
   
   CBrush DrawBrush=(RGB(100,100,100));
   CBrush *Drawbrush=pDC->SelectObject(&DrawBrush);
    if  ((Eat[0].x+Eat[0].y)%2==0)pDC->Ellipse(Eat[0].x*20,Eat[0].y*20,(Eat[0].x+1)*20,(Eat[0].y+1)*20);
   else
   pDC->Pie(Eat[0].x*20,Eat[0].y*20,(Eat[0].x+1)*20,(Eat[0].y+1)*20,(Eat[0].x)*20,(Eat[0].y+1)*20,(Eat[0].x)*20,(Eat[0].y)*20);
   pDC->SelectObject(DrawBrush);
    }




if(Eat[0].direct==4)
    { Eat[0].x++;
   pDC->SelectStockObject(BLACK_PEN);
   
   CBrush DrawBrush=(RGB(100,100,100));
   CBrush *Drawbrush=pDC->SelectObject(&DrawBrush);
    if  ((Eat[0].x+Eat[0].y)%2==0)pDC->Ellipse(Eat[0].x*20,Eat[0].y*20,(Eat[0].x+1)*20,(Eat[0].y+1)*20);
   else
   pDC->Pie(Eat[0].x*20,Eat[0].y*20,(Eat[0].x+1)*20,(Eat[0].y+1)*20,(Eat[0].x+1)*20,(Eat[0].y)*20,(Eat[0].x+1)*20,(Eat[0].y+1)*20);
   pDC->SelectObject(DrawBrush);
    } 
        CView::OnTimer(nIDEvent);
 }//对嘴的方向进行判断 并且实现嘴的开闭




运行结果图 


总结:这是我做的第一个小游戏,其中还有很多需要改进的地方,谢谢!