




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、手把手教“MFC版贪吃蛇教程”写在前面的话本次贪吃蛇教程主要知识点包括以下几个方面1 CView类中的消息响应2 控件的消息响应3 基于CView类内的具体游戏实现4 数组5 游戏图形的实现用CDC类实现。本版游戏的具体实现是在CViewl类中实现,所以其他类不用添加任何代码。由于此次贪吃蛇需要MFC的开发环境,所以打开VisualC+新建一个MFC AppWizard单文档工程,随意取名一个工程名称。本次贪吃蛇基本流程与大体思想1 定义蛇类和食物类,初始化贪吃蛇各项成员变量,包括图像的出现在屏幕的初始位置,长度,以及蛇的行走方向。食物类的定义包括出现的初始位置,以及食物是否被吃掉的判断。2
2、用数组初始化长度为3的贪吃蛇,并且默认食物未出现3 在CView类上运用MFC提供的Windows消息中WM_TIMER消息,运用OnTimer()函数让系统提供一个时钟节拍,更新游戏4 具体游戏实现,包括蛇撞到自己和围墙都将使游戏结束,判断吃豆等,其中还包括根据蛇的长度来进行游戏难度的改变。5 具体键盘游戏操作运用到Windows消息响应中的WM_KEYDOWN,用OnKeyDown()来响应玩家的实际操作。/第一部分 首先在已有工程下的“ClassView”中右键CView类添加以下Windows信息1 WM_KEYDOWN2 WM_RBUTTONDOWN3 WM_TIMER再右键CVie
3、w类选择 “ADD Virtual Funciton”选OnInitialUpdate()OnInitialUpdate()的功能如下:视图窗口完全建立后第一个被框架调用的函数。框架在第一次调用OnDraw前会调用OnInitialUpdate,因此OnInitialUpdate是设置滚动视图的逻辑尺寸和映射模式的最合适的地方。时间上,两者先后顺序不同,构造函数生成本类的对象,但没有产生窗口,OnCreate后窗口产生,然后才是视图的OnInitialUpDate,一般在这里对视图的显示做初始化。简单点,就是ONCREATE只是产生VIEW的基本结构和变量而在OnInitialUpDate()
4、中,主要初始化视图中控件等。对各个变量进行初始化操作所以我们要用这个函数来进行贪吃蛇的初始化工作。再者还要添加一个成员函数oninit()进行贪吃蛇外观的初始化控件的设计再者是设计游戏的一些控件来控制“游戏开始” “游戏结束” 和“游戏暂停”。我们可以点击“工作空间”的“ResourceView”进行控件的具体设计,这里我们在Menu文件夹中把“IDR_MAINFRAME”中默认的控件全部删除右键其中的标题栏,点击属性,会得到一个菜单栏标题,我们分别建立1个菜单栏标题。这里我们分别建“游戏”。点击并且在已有控件中的列表中点击属性,进行“菜单项目属性”的设置。我们本别建立的属性“标明”与对应的I
5、D有游戏开始 IDM_START游戏暂停 IDM_PAUSE游戏继续 IDM_CONTINUE游戏退出 IDM_EXIT此处控件的设计是“可见即可得”的控件操作成功设置ID之后 我们分别右键各项属性进行消息响应处理函数的生成具体方法操作例子如下1右键“游戏开始”2 点击“类向导建立”3 在Message Maps页面,在要进行消息响应的控件ID列表Object IDs中上选择对应的ID, 这里我们选择IDM_START,具体实现的环境是CView类,所以我们必须把“Class name”的默认“CMainFrame”改为“CView类”,并且在“Messages”类型设置中,用“COMMAND
6、”设置为其为命令消息。其余各项也按照同理进行设置。PS:Windows消息的分类有3种,标准消息,命令消息,通告消息1 标准消息除COMMAND之外,所以WM_开头都是标准消息2 命令消息就是COMMAND3 通告消息由控件产生的消息,这类信息也能以WM_COMMAND类型出现最后我们回到原来的ClassView去看看我们一共添加的函数所有的函数集合全部有以下在此处我们可以看到我们添加的Windows消息所有的 消息响应函数的声明/第二部分具体实现游戏、Step 1首先我们在文件开头处分别定义 蛇 和食物 的全局变量struct Snakeint x,y;int len;int direct;
7、Snake50;struct Foodint x;int y;int isfood;Food;再者void CSNAKEView:OnInitialUpdate()CView:OnInitialUpdate();Snake0.x=10;Snake0.y=10;Snake1.x=11;Snake1.y=10;Snake2.x=12;Snake2.y=10;Snake0.direct=3;Snake0.len=3;Food.isfood=1;/ TODO: Add your specialized code here and/or call the base class代码说明:初始化贪吃蛇起初有
8、3个节点,长度为3,起始坐标;食物默认为 1无0有 当然我更加喜欢0无1有Step 2 对OnKeyDown()具体添加代码void CSNAKEView:OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)/ TODO: Add your message handler code here and/or call defaultswitch(nChar)case VK_UP:if(Snake0.direct!=2)Snake0.direct=1;break; case VK_DOWN:if(Snake0.direct!=1)Snake0.direc
9、t=2;break;case VK_LEFT:if(Snake0.direct!=4)Snake0.direct=3;break; case VK_RIGHT:if(Snake0.direct!=3)Snake0.direct=4;break; CView:OnKeyDown(nChar, nRepCnt, nFlags);CView:OnKeyDown(nChar, nRepCnt, nFlags);OnKeyDown函数的第一个参数 UINT nChar 是接收用户键入的信息,然后我们用switch进行选择判断代码说明:Snake0代表的是蛇头,我们对蛇头的方向Snake0.direct进
10、行判断。case VK_UP:if(Snake0.direct!=2)Snake0.direct=1;break; 意思就是当Snake0.direct的方向此时并不等于“下”的时候,才能做出“上”的操作动作,否则则忽略用户“向上”的操作按键效果Step 3 对OnRButtonDown()具体添加代码void CSNAKEView:OnRButtonDown(UINT nFlags, CPoint point)/ TODO: Add your message handler code here and/or call default/ TODO: Add your message handl
11、er code here and/or call defaultCString str;str.Format(%d,%d,point.x,point.y);AfxMessageBox(str);CView:OnRButtonDown(nFlags, point);CView:OnRButtonDown(nFlags, point);CView:OnRButtonDown(nFlags, point);这个函数功能是:用鼠标右键屏幕,就会马上显示当前位置的坐标信息。其实这个函数并不是本游戏中必要添加的函数,只是为了在后面游戏页面的设计的时候可以用鼠标右键屏幕了解大概的屏幕坐标信息,才特地做的这里
12、补充一个知识点WIN32坐标系有3种1 世界坐标系2 页面坐标系3 设备坐标系世界坐标系与页面坐标系称谓“逻辑空间”,实际上就等同于我们现实生活中的数学坐标系一样。设备坐标系则不然,电脑屏幕就是一个用设备坐标系的,特征为以用户区窗口左上角为(0 , 0)原点,X坐标向右为正,Y坐标向下为正。Step 4void CSNAKEView:oninit()CDC *pDC=GetDC();CBrush DrawBrush=(RGB(100,100,100);CBrush *Drawbrush=pDC-SelectObject(&DrawBrush);for(int i=0;iRectangle(sn
13、akei.x*20,snakei.y*20,(snakei.x+1)*20,(snakei.y+1)*20);pDC-SelectObject(DrawBrush);代码说明:利用Windows给我们提供的CDC类来进行画图,我们首先用一个指向CDC类的指针去接受与该窗口相关联的DC句柄,然后用定义画刷一个DrawBrush对象,并且用RGB(100,100,100)来给画刷初始化颜色。并且用SelectObject(&DrawBrush);函数把对象画刷选入到设备描述表中,用for循环依次把贪吃蛇的3个节点画出来。void far rectangle(int left, int top, i
14、nt right, int bottom);这个函数的的功能根据函数功能:该函数画一个矩形,用当前的画笔画矩形轮廓,用当前画刷进行填充.Step 5 控件添加代码void CSNAKEView:OnStart()/ TODO: Add your command handler code hereSetTimer(1,3000,NULL);AfxMessageBox(3秒后开始游戏!);void CSNAKEView:OnPause()/ TODO: Add your command handler code hereKillTimer(1);AfxMessageBox(暂停游戏.);void
15、CSNAKEView:OnExit()/ TODO: Add your command handler code hereAfxMessageBox(退出游戏.);exit 0;void CSNAKEView:OnContinue()/ TODO: Add your command handler code hereSetTimer(1,10,NULL);代码说明:由于之前我们设定了WM_TIMER消息,我们能运用计时器功能用WM_TIMER来设置定时器先请看SetTimer这个API函数的原型UINT_PTR SetTimer(HWND hWnd, / 窗口句柄UINT_PTR nIDEve
16、nt, / 定时器ID,多个定时器时,可以通过该ID判断是哪个定时器UINT uElapse, / 时间间隔,单位为毫秒TIMERPROC lpTimerFunc / 回调函数);SetTimer(m_hWnd,1,1000,NULL); /一个1秒触发一次的定时器在MFC程序中SetTimer被封装在CWnd类中,调用就不用指定窗口句柄了所以我们这里可以只去后3个参数写成SetTimer(1,10,NULL);1000为1秒关于afxMessageBox的研究在本博客中有写有,这里就不在赘述Step 6 对OnDraw()的添加代码OnDraw()函数众所周知视图类中的输出.视图类的输出基本
17、上都是在视图类的OnDraw函数中处理的,系统会准备好入参,然后调用OnDraw函数本人也没有过多的去研究过次函数,仅知道一些皮毛。不过这里有个知识点那便是OnPaint()与OnDraw()的区别,OnPaint()派生于CWnd类,响应WM_PAINT消息。OnDraw是CView类的成员函数,并且没有消息响应功能,这就是为什么视图类没有只有OnDraw()而没有OnPaint()的原因。OnDraw()维护视图客户区(例如通过试表在视图中画图),而OnPaint()维护窗口的客户区void CSNAKEView:OnDraw(CDC* pDC)CSNAKEDoc* pDoc = GetD
18、ocument();ASSERT_VALID(pDoc);/ TODO: add draw code for native data hereCBrush backBrush(RGB(100,100,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-Rectangle(19,1
19、9,501,501);oninit();代码说明:此处是用画刷画一个背景,并且画出3个矩形区域函数原型:int GetClipBox(HDC hdc, LPRECT lprc);该函数得到一个能够完包含当前可见区域的最小矩形的大小。 函数原型:int nYLeft, int nWidth, int nHeight, DWORD dwRop);该函数使用当前选入指定设备环境中的刷子绘制给定的矩形区域。Step 7 对OnTime()的添加代码void CSNAKEView:OnTimer(UINT nIDEvent)/ TODO: Add your message handler code he
20、re and/or call defaultCDC *pDC=GetDC();CString soure;if(Snake0.len=2)SetTimer(1,370,NULL);if(Snake0.len=3)SetTimer(1,270,NULL);if(Snake0.len=6)SetTimer(1,200,NULL);if(Snake0.len=9)SetTimer(1,100,NULL);soure.Format(得分:%d!,(Snake0.len-3)*10); /撞界判断if(Snake0.x*20=37|Snake0.y*20=462|Snake0.y*20=462)Kill
21、Timer(1);AfxMessageBox(soure);/ s=0;/蛇身相撞判断if(Snake0.len3)for(int sn=Snake0.len-1;sn0;sn-)if(Snake0.x*20=Snakesn.x*20&Snake0.y*20=Snakesn.y*20)KillTimer(1);AfxMessageBox(soure);/ s=0;/pDC-SelectStockObject(WHITE_PEN);pDC-Rectangle(SnakeSnake0.len-1.x*20,SnakeSnake0.len-1.y*20,(SnakeSnake0.len-1.x+1)
22、*20,(SnakeSnake0.len-1.y+1)*20);for(int i=Snake0.len-1;i0;i-)Snakei.x=Snakei-1.x;Snakei.y=Snakei-1.y;/行走方向判断if(Snake0.direct=1)Snake0.y-;if(Snake0.direct=2)Snake0.y+;if(Snake0.direct=3)Snake0.x-;if(Snake0.direct=4)Snake0.x+;pDC-SelectStockObject(BLACK_PEN);CBrush DrawBrush=(RGB(100,100,100);CBrush *
23、Drawbrush=pDC-SelectObject(&DrawBrush);pDC-Rectangle(Snake0.x*20,Snake0.y*20,(Snake0.x+1)*20,(Snake0.y+1)*20);pDC-SelectObject(DrawBrush);/判断吃豆的条件,撞到就吃if(Snake0.x*20=Food.x*20&Snake0.y*20=Food.y*20)Snake0.len+;Food.isfood=1;SnakeSnake0.len-1.x=SnakeSnake0.len-2.x;SnakeSnake0.len-1.y=SnakeSnake0.len-
24、2.y;/如果食物被吃了 就生成if(Food.isfood=1)srand(unsigned)time(NULL);dofor(int isfo=Snake0.len-1;isfo=0;isfo-)if(Snake0.x*20=Snakeisfo.x*20&Snake0.y*20=Snakeisfo.y*20)Food.x=rand()%;Food.y=rand()%;while(Food.x*2070|Food.y*20430|Food.y*20430);pDC-Rectangle(Food.x*20,Food.y*20,(Food.x+1)*20,(Food.y+1)*20);Food.
25、isfood=0;CView:OnTimer(nIDEvent);/下面由我来详细分析代码if(Snake0.len=2)SetTimer(1,370,NULL);if(Snake0.len=3)SetTimer(1,270,NULL);if(Snake0.len=6)SetTimer(1,200,NULL);if(Snake0.len=9)SetTimer(1,100,NULL);此段代码的作用是根据蛇的长度来进行SetTimer()函数的定义,可以根据长度来进行游戏难度的设定,如上代码分别370ms 270ms 200ms 100ms进行一次新的移动。/撞界判断if(Snake0.x*20
26、=37|Snake0.y*20=462|Snake0.y*20=462)KillTimer(1);AfxMessageBox(soure);这里为什么要乘以20呢?由于我们初始化的时候是Snake0.x=10;Snake0.y=10;Snake1.x=11;Snake1.y=10;Snake2.x=12;Snake2.y=10;所以只是把贪吃蛇的起始位置“搬移”到20倍的位置 当然可以等价于Snake0.x=10*20;Snake0.y=10*20;Snake1.x=11*20;Snake1.y=10*20;Snake2.x=12*20;Snake2.y=10*20;蛇的一节身体为一个矩形块,
27、这样表示每个矩形块只需起点坐标x 和y身体是不断增长的,所以用数组存放每一节的坐标/蛇身相撞判断if(Snake0.len3)for(int sn=Snake0.len-1;sn0;sn-)if(Snake0.x*20=Snakesn.x*20&Snake0.y*20=Snakesn.y*20)KillTimer(1);AfxMessageBox(soure);/ s=0;这段是最好理解的了,由于判断蛇自己是否咬到了自己,根据蛇长sn,进行sn次for sn-1次循环并且和Snake0.x进行比较(之所以进行sn-1次那肯定是不包括蛇头而且蛇的长度也必须大于3才会发生自己咬自己的情况)Kill
28、Timer(1);是停止计时器;和之前的SetTime()对应而已AfxMessageBox(soure);这里不深究,总之就是输出一个原样输出内容pDC-SelectStockObject(WHITE_PEN);/把白色的“PEN”选入设备进行画图pDC-Rectangle(SnakeSnake0.len-1.x*20,SnakeSnake0.len-1.y*20,(SnakeSnake0.len-1.x+1)*20,(SnakeSnake0.len-1.y+1)*20);/让它去画最后一个节点for(int i=Snake0.len-1;i0;i-)/贪吃蛇的蛇身移动Snakei.x=Sn
29、akei-1.x;Snakei.y=Snakei-1.y;这段的知识点要严重的说明一下1 为什么要专门用白笔画最后一个呢?因为我们的游戏界面是用白色的,贪吃蛇移动的时候,肯定是蛇头向前走一单位,而尾部肯定要“擦除”掉一个单位,那怎么擦除呢?我们只能用和背景色一样的画笔。原来蛇的位置和新蛇的位置差一个单位,所以看起来蛇会多一节身体,所以将蛇的最后一节用背景色覆盖SelectStockObject(WHITE_PEN) 让它起到“消失”最后一个节点的功能让我们以为蛇是向前走了。如 假设现在贪吃蛇有4个单位0123SnakeSnake0.len-1.x*20 SnakeSnake0.4-1.x*20 =Snake3.x*20数组3 恰好是最后一个节点的下标,别忘了数组是从0开始算的请仔细领悟2for(int i=Snake0.len-1;i0;i-)Snakei.x=Snakei-1.x;Snakei.y=Snakei-1.y;进行次数为长度-1次的循环为什么要进行长度-1次呢?我这里要说明一下,这里
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 本溪市明山区2024-2025学年小升初复习数学模拟试卷含解析
- 云南旅游职业学院《混凝土和砌体结构设计》2023-2024学年第二学期期末试卷
- 广西中远职业学院《形体训练与健美》2023-2024学年第一学期期末试卷
- 宁波财经学院《趣味逻辑学与人生智慧》2023-2024学年第二学期期末试卷
- 兴义民族师范学院《数字系统设计》2023-2024学年第二学期期末试卷
- 渤海理工职业学院《外国电影史》2023-2024学年第二学期期末试卷
- 柱上式无功补偿装置项目风险评估报告
- 广州科技贸易职业学院《心理学核心理论专题研究》2023-2024学年第一学期期末试卷
- 贵州体育职业学院《童装结构与工艺设计》2023-2024学年第二学期期末试卷
- 右江民族医学院《英语学术文献阅读》2023-2024学年第二学期期末试卷
- 2024年人教版八年级生物下册期末考试卷(附答案)
- 1.设备设施运行、检修、维护、保养管理制度
- 二位数乘二位数600道
- 2024年湖南省高考物理试卷真题(含答案解析)
- 初级制图员职业鉴定考试题及答案
- 大型机械设备进出场及安拆施工专项方案
- 创维电视机E710U系列电子说明书
- 《山谷回音真好听》名师课件(简谱)
- 医院抗菌药物临时采购使用申请表
- 高考英语核心词汇1000个
- 校园海绵城市设计方案
评论
0/150
提交评论