MFC写的贪吃蛇代码.docx

上传人:b****7 文档编号:23593507 上传时间:2023-05-18 格式:DOCX 页数:27 大小:926.81KB
下载 相关 举报
MFC写的贪吃蛇代码.docx_第1页
第1页 / 共27页
MFC写的贪吃蛇代码.docx_第2页
第2页 / 共27页
MFC写的贪吃蛇代码.docx_第3页
第3页 / 共27页
MFC写的贪吃蛇代码.docx_第4页
第4页 / 共27页
MFC写的贪吃蛇代码.docx_第5页
第5页 / 共27页
点击查看更多>>
下载资源
资源描述

MFC写的贪吃蛇代码.docx

《MFC写的贪吃蛇代码.docx》由会员分享,可在线阅读,更多相关《MFC写的贪吃蛇代码.docx(27页珍藏版)》请在冰豆网上搜索。

MFC写的贪吃蛇代码.docx

MFC写的贪吃蛇代码

手把手教“MFC版贪吃蛇教程”

写在前面的话

本次贪吃蛇教程主要知识点包括以下几个方面

1CView类中的消息响应

2控件的消息响应

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

4数组

5游戏图形的实现用CDC类实现。

本版游戏的具体实现是在CViewl类中实现,所以其他类不用添加任何代码。

由于此次贪吃蛇需要MFC的开发环境,所以打开VisualC++新建一个MFCAppWizard单文档工程,随意取名一个工程名称。

本次贪吃蛇基本流程与大体思想

1定义蛇类和食物类,初始化贪吃蛇各项成员变量,包括图像的出现在屏幕的初始位置,长度,以及蛇的行走方向。

食物类的定义包括出现的初始位置,以及食物是否被吃掉的判断。

2用数组初始化长度为3的贪吃蛇,并且默认食物未出现

3在CView类上运用MFC提供的Windows消息中WM_TIMER消息,运用OnTimer()函数让系统提供一个时钟节拍,更新游戏

4具体游戏实现,包括蛇撞到自己和围墙都将使游戏结束,判断吃豆等,其中还包括根据蛇的长度来进行游戏难度的改变。

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

///////////////////////////////////////////////////////////////////////

第一部分

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

1WM_KEYDOWN

2WM_RBUTTONDOWN

3WM_TIMER

再右键CView类选择“ADDVirtualFunciton”选OnInitialUpdate()

OnInitialUpdate()的功能如下:

视图窗口完全建立后第一个被框架调用的函数。

框架在第一次调用OnDraw前会调用OnInitialUpdate,因此OnInitialUpdate是设置滚动视图的逻辑尺寸和映射模式的最合适的地方。

时间上,两者先后顺序不同,构造函数生成本类的对象,但没有产生窗口,OnCreate后窗口产生,然后才是视图的OnInitialUpDate,一般在这里对视图的显示做初始化。

简单点,就是ONCREATE只是产生VIEW的基本结构和变量而在OnInitialUpDate()中,主要初始化视图中控件等。

对各个变量进行初始化操作

所以我们要用这个函数来进行贪吃蛇的初始化工作。

再者还要添加一个成员函数oninit()

进行贪吃蛇外观的初始化

控件的设计

再者是设计游戏的一些控件来控制“游戏开始”“游戏结束”和“游戏暂停”。

我们可以点击“工作空间”的“ResourceView”进行控件的具体设计,这里我们在Menu文件夹中把“IDR_MAINFRAME”中默认的控件全部删除

右键其中的标题栏,点击属性,会得到一个菜单栏标题,我们分别建立1个菜单栏标题。

这里我们分别建“游戏”。

点击并且在已有控件中的列表中点击属性,进行“菜单项目属性”的设置。

我们本别建立的属性“标明”与对应的ID有

游戏开始IDM_START

游戏暂停IDM_PAUSE

游戏继续IDM_CONTINUE

游戏退出IDM_EXIT

此处控件的设计是“可见即可得”的控件操作

成功设置ID之后我们分别右键

各项属性进行消息响应处理函数的生成

具体方法操作例子如下

1右键“游戏开始”

2点击“类向导建立”

3在MessageMaps页面,在要进行消息响应的控件ID列表ObjectIDs中上选择对应的ID,这里我们选择IDM_START,具体实现的环境是CView类,所以我们必须把“Classname”的默认“CMainFrame”改为“CView类”,并且在“Messages”类型设置中,用“COMMAND”设置为其为命令消息。

其余各项也按照同理进行设置。

PS:

Windows消息的分类有3种,标准消息,命令消息,通告消息

1标准消息除COMMAND之外,所以WM_开头都是标准消息

2命令消息就是COMMAND

3通告消息由控件产生的消息,这类信息也能以WM_COMMAND类型出现

最后我们回到原来的ClassView去看看我们一共添加的函数

所有的函数集合全部有以下

在此处我们可以看到我们添加的Windows消息所有的消息响应函数的声明

//////////////////////////////////////////////////////////

第二部分

具体实现游戏、

Step1

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

structSnake

{

intx,y;

intlen;

intdirect;

}Snake[50];

structFood

{

intx;

inty;

intisfood;

}Food;

再者

voidCSNAKEView:

:

OnInitialUpdate()

{

CView:

:

OnInitialUpdate();

Snake[0].x=10;

Snake[0].y=10;

Snake[1].x=11;

Snake[1].y=10;

Snake[2].x=12;

Snake[2].y=10;

Snake[0].direct=3;

Snake[0].len=3;

Food.isfood=1;

//TODO:

Addyourspecializedcodehereand/orcallthebaseclass

}

代码说明:

初始化贪吃蛇起初有3个节点,长度为3,起始坐标;

食物默认为1无0有当然我更加喜欢0无1有

Step2对OnKeyDown()具体添加代码

voidCSNAKEView:

:

OnKeyDown(UINTnChar,UINTnRepCnt,UINTnFlags)

{

//TODO:

Addyourmessagehandlercodehereand/orcalldefault

switch(nChar)

{

caseVK_UP:

if(Snake[0].direct!

=2)Snake[0].direct=1;break;

caseVK_DOWN:

if(Snake[0].direct!

=1)Snake[0].direct=2;break;

caseVK_LEFT:

if(Snake[0].direct!

=4)Snake[0].direct=3;break;

caseVK_RIGHT:

if(Snake[0].direct!

=3)Snake[0].direct=4;break;}

CView:

:

OnKeyDown(nChar,nRepCnt,nFlags);

CView:

:

OnKeyDown(nChar,nRepCnt,nFlags);

}

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

代码说明:

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

caseVK_UP:

if(Snake[0].direct!

=2)Snake[0].direct=1;break;

意思就是当Snake[0].direct的方向此时并不等于“下”的时候,才能做出“上”的操作动作,否则则忽略用户“向上”的操作按键效果

Step3对OnRButtonDown()具体添加代码

voidCSNAKEView:

:

OnRButtonDown(UINTnFlags,CPointpoint)

{

//TODO:

Addyourmessagehandlercodehereand/orcalldefault

//TODO:

Addyourmessagehandlercodehereand/orcalldefault

CStringstr;

str.Format("%d,%d",point.x,point.y);

AfxMessageBox(str);

CView:

:

OnRButtonDown(nFlags,point);

CView:

:

OnRButtonDown(nFlags,point);

CView:

:

OnRButtonDown(nFlags,point);

}

这个函数功能是:

用鼠标右键屏幕,就会马上显示当前位置的坐标信息。

其实这个函数并不是本游戏中必要添加的函数,只是为了在后面游戏页面的设计的时候可以用鼠标右键屏幕了解大概的屏幕坐标信息,才特地做的

这里补充一个知识点

WIN32坐标系有3种

1世界坐标系

2页面坐标系

3设备坐标系

世界坐标系与页面坐标系称谓“逻辑空间”,实际上就等同于我们现实生活中的数学坐标系一样。

设备坐标系则不然,电脑屏幕就是一个用设备坐标系的,特征为以用户区窗口左上角为(0,0)原点,X坐标向右为正,Y坐标向下为正。

Step4

voidCSNAKEView:

:

oninit()

{

CDC*pDC=GetDC();

CBrushDrawBrush=(RGB(100,100,100));

CBrush*Drawbrush=pDC->SelectObject(&DrawBrush);

for(inti=0;i<=snake[0].l-1;i++)

pDC->Rectangle(snake[i].x*20,snake[i].y*20,(snake[i].x+1)*20,(snake[i].y+1)*20);

pDC->SelectObject(DrawBrush);

}

代码说明:

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

并且用SelectObject(&DrawBrush);函数把对象画刷选入到设备描述表中,用for循环依次把贪吃蛇的3个节点画出来。

voidfarrectangle(intleft,inttop,intright,intbottom);

这个函数的的功能根据

函数功能:

该函数画一个矩形,用当前的画笔画矩形轮廓,用当前画刷进行填充.

Step5控件添加代码

voidCSNAKEView:

:

OnStart()

{

//TODO:

Addyourcommandhandlercodehere

SetTimer(1,3000,NULL);

AfxMessageBox("3秒后开始游戏!

");

}

voidCSNAKEView:

:

OnPause()

{

//TODO:

Addyourcommandhandlercodehere

KillTimer

(1);

AfxMessageBox("暂停游戏...");

}

voidCSNAKEView:

:

OnExit()

{

//TODO:

Addyourcommandhandlercodehere

AfxMessageBox("退出游戏...");

exit0;

}

voidCSNAKEView:

:

OnContinue()

{

//TODO:

Addyourcommandhandlercodehere

SetTimer(1,10,NULL);

}

代码说明:

由于之前我们设定了WM_TIMER消息,我们能运用计时器功能

用WM_TIMER来设置定时器

  先请看SetTimer这个API函数的原型

  UINT_PTRSetTimer(

  HWNDhWnd,//窗口句柄

  UINT_PTRnIDEvent,//定时器ID,多个定时器时,可以通过该ID判断是哪个定时器

  UINTuElapse,//时间间隔,单位为毫秒

  TIMERPROClpTimerFunc//回调函数

  );

SetTimer(m_hWnd,1,1000,NULL);//一个1秒触发一次的定时器

  在MFC程序中SetTimer被封装在CWnd类中,调用就不用指定窗口句柄了

所以我们这里可以只去后3个参数写成SetTimer(1,10,NULL);

1000为1秒

关于afxMessageBox的研究在本博客中有写有,这里就不在赘述

Step6对OnDraw()的添加代码

OnDraw()函数众所周知视图类中的输出.视图类的输出基本上都是在视图类的OnDraw函数中处理的,系统会准备好入参,然后调用OnDraw函数

本人也没有过多的去研究过次函数,仅知道一些皮毛。

不过这里有个知识点那便是OnPaint()与OnDraw()的区别,OnPaint()派生于CWnd类,响应WM_PAINT消息。

OnDraw是CView类的成员函数,并且没有消息响应功能,这就是为什么视图类没有只有OnDraw()而没有OnPaint()的原因。

OnDraw()维护视图客户区(例如通过试表在视图中画图),而OnPaint()维护窗口的客户区

voidCSNAKEView:

:

OnDraw(CDC*pDC)

{

CSNAKEDoc*pDoc=GetDocument();

ASSERT_VALID(pDoc);

//TODO:

adddrawcodefornativedatahere

CBrushbackBrush(RGB(100,100,0));

CBrush*pOldBrush=pDC->SelectObject(&backBrush);

CRectrect;

pDC->GetClipBox(&rect);

pDC->PatBlt(rect.left,rect.top,rect.Width(),rect.Height(),PATCOPY);

pDC->SelectObject(pOldBrush);

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

oninit();

}

代码说明:

此处是用画刷画一个背景,并且画出3个矩形区域

函数原型:

intGetClipBox(HDChdc,LPRECTlprc);

该函数得到一个能够完包含当前可见区域的最小矩形的大小。

函数原型:

intnYLeft,intnWidth,intnHeight,DWORDdwRop); 

该函数使用当前选入指定设备环境中的刷子绘制给定的矩形区域。

 

Step7对OnTime()的添加代码

voidCSNAKEView:

:

OnTimer(UINTnIDEvent)

{

//TODO:

Addyourmessagehandlercodehereand/orcalldefault

CDC*pDC=GetDC();

CStringsoure;

if(Snake[0].len==2)SetTimer(1,370,NULL);

if(Snake[0].len==3)SetTimer(1,270,NULL);

if(Snake[0].len==6)SetTimer(1,200,NULL);

if(Snake[0].len==9)SetTimer(1,100,NULL);

soure.Format("得分:

%d!

",(Snake[0].len-3)*10);

//撞界判断

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

{

KillTimer

(1);

AfxMessageBox(soure);

//s=0;

}

//蛇身相撞判断

if(Snake[0].len>3)

for(intsn=Snake[0].len-1;sn>0;sn--)

{

if(Snake[0].x*20==Snake[sn].x*20&&Snake[0].y*20==Snake[sn].y*20)

{

KillTimer

(1);

AfxMessageBox(soure);

//s=0;

}

}

///////////////////////////////////////////////////////////////////////////

pDC->SelectStockObject(WHITE_PEN);

pDC->Rectangle(Snake[Snake[0].len-1].x*20,Snake[Snake[0].len-1].y*20,(Snake[Snake[0].len-1].x+1)*20,(Snake[Snake[0].len-1].y+1)*20);

for(inti=Snake[0].len-1;i>0;i--)

{

Snake[i].x=Snake[i-1].x;

Snake[i].y=Snake[i-1].y;

}

//行走方向判断

if(Snake[0].direct==1)Snake[0].y--;

if(Snake[0].direct==2)Snake[0].y++;

if(Snake[0].direct==3)Snake[0].x--;

if(Snake[0].direct==4)Snake[0].x++;

pDC->SelectStockObject(BLACK_PEN);

CBrushDrawBrush=(RGB(100,100,100));

CBrush*Drawbrush=pDC->SelectObject(&DrawBrush);

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

pDC->SelectObject(DrawBrush);

//判断吃豆的条件,撞到就吃

if(Snake[0].x*20==Food.x*20&&Snake[0].y*20==Food.y*20)

{

Snake[0].len++;

Food.isfood=1;

Snake[Snake[0].len-1].x=Snake[Snake[0].len-2].x;

Snake[Snake[0].len-1].y=Snake[Snake[0].len-2].y;

}

//如果食物被吃了就生成

if(Food.isfood==1)

{

srand((unsigned)time(NULL));

do

{

for(intisfo=Snake[0].len-1;isfo>=0;isfo--)

if(Snake[0].x*20==Snake[isfo].x*20&&Snake[0].y*20==Snake[isfo].y*20)

{

Food.x=rand()%;

Food.y=rand()%;

}

}

while(Food.x*20<70||Food.y*20<70||Food.x*20>430||Food.y*20>430);

pDC->Rectangle(Food.x*20,Food.y*20,(Food.x+1)*20,(Food.y+1)*20);

Food.isfood=0;

}

CView:

:

OnTimer(nIDEvent);

}

/////////////////////////////////

下面由我来详细分析代码

if(Snake[0].len==2)SetTimer(1,370,NULL);

if(Snake[0].len==3)SetTimer(1,270,NULL);

if(Snake[0].len==6)SetTimer(1,200,NULL);

if(Snake[0].len==9)SetTimer(1,100,NULL);

此段代码的作用是根据蛇的长度来进行SetTimer()函数的定义,可以根据长度来进行游戏难度的设定,如上代码分别370ms270ms200ms100ms进行一次新的移动。

//撞界判断

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

{

KillTimer

(1);

AfxMessageBox(soure);

}

这里为什么要乘以20呢?

由于我们初始化的时候是

Snake[0].x=10;

Snake[0].y=10;

Snake[1].x=11;

Snake[1].y=10;

Snake[2].x=12;

Snake[2].y=10;

所以只是把贪吃蛇的起始位置“搬移”到20倍的位置当然可以等价于

Snake[0].x=10*20;

Snake[0].y=10*20;

Snake[1].x=11*20;

Snake[1].y=10*20;

Snake[2].x=12*20;

Snake[2].y=10*20;

蛇的一节身体为一个矩形块,这样表示每个矩形块只需起点坐标x和y身体是不断增长的,所以用数组存放每一节的坐标

//蛇身相撞判断

if(Snake[0].len>3)

for(intsn=Snake[0].len-1;sn>0;sn--)

{

if(Snake[0].x*20==Snake[sn].x*20&&Snake[0].y*20==Snake[sn].y*20)

{

KillTimer

(1);

AfxMessageBox(soure);

//s=0;

}

}

这段是最好理解的了,由于判断蛇自己是否咬到了自己,根据蛇长sn,进行sn次forsn-1次循环

并且和Snake[0].x进行比较(之所以进行sn-1次那肯定是不包括蛇头而且蛇的长度也必须大于3才会发生自己咬自己的情况)

KillTimer

(1);是停止计时器;和之前的SetTime()对应而已

AfxMessageBox(soure);这里不深究,总之就是输出一个原样输出容

pDC->SelectStockObject(WHITE_PEN);//把白色的“PEN”选入设备进行画图

pDC->Rectangle(Snake[Snake[0].len-1].x*20,Snake[Snake[0].len-1].y*20,

(Snake[Snake[0].len-1].x+1)*20,(Snake[Snake[0].len-1].y+1)*20);//让它去画最后一个节点

for(inti=Snake[0].len-1;i>0;i--)//贪吃蛇的蛇身移动

{

Snake[i].x=Snake[i-1].x;

Snake[i].y=Snake[i-1].y;

}

这段的知识点要严

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 工学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1