本次吃豆子游戏主要知识点包括以下几个方面
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);
}//对嘴的方向进行判断 并且实现嘴的开闭
运行结果图
总结:这是我做的第一个小游戏,其中还有很多需要改进的地方,谢谢!