贵港网站seo,做简历做得好的网站,wordpress个人下载网站模板下载,企业网站开发背景则么写写在前面的话 本次贪吃蛇教程主要知识点包括以下几个方面 1 CView类中的消息响应 2 控件的消息响应 3 基于CView类内的具体游戏实现 4 数组 5 游戏图形的实现用CDC类实现。 本版游戏的具体实现是在CViewl类中实现#xff0c;所以其他… 写在前面的话 本次贪吃蛇教程主要知识点包括以下几个方面 1 CView类中的消息响应 2 控件的消息响应 3 基于CView类内的具体游戏实现 4 数组 5 游戏图形的实现用CDC类实现。 本版游戏的具体实现是在CViewl类中实现所以其他类不用添加任何代码。 由于此次贪吃蛇需要MFC的开发环境所以打开VisualC新建一个MFC AppWizard单文档工程随意取名一个工程名称。 本次贪吃蛇基本流程与大体思想 1 定义蛇类和食物类初始化贪吃蛇各项成员变量包括图像的出现在屏幕的初始位置长度以及蛇的行走方向。食物类的定义包括出现的初始位置以及食物是否被吃掉的判断。 2 用数组初始化长度为3的贪吃蛇并且默认食物未出现 3 在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 Virtual Funciton”选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 在Message Maps页面在要进行消息响应的控件ID列表Object IDs中上选择对应的ID, 这里我们选择IDM_START具体实现的环境是CView类所以我们必须把“Class name”的默认“CMainFrame”改为“CView类”并且在“Messages”类型设置中用“COMMAND”设置为其为命令消息。其余各项也按照同理进行设置。 PSWindows消息的分类有3种标准消息命令消息通告消息 1 标准消息除COMMAND之外所以WM_开头都是标准消息 2 命令消息就是COMMAND 3 通告消息由控件产生的消息这类信息也能以WM_COMMAND类型出现 最后我们回到原来的ClassView去看看我们一共添加的函数 所有的函数集合全部有以下 在此处我们可以看到我们添加的Windows消息所有的 消息响应函数的声明 // 第二部分 具体实现游戏、 Step 1 首先我们在文件开头处分别定义 蛇 和食物 的全局变量 struct Snake { int x,y; int len; int direct; }Snake[50]; struct Food { int x; int y; int isfood; }Food; 再者 void CSNAKEView::OnInitialUpdate() { CView::OnInitialUpdate(); Snake[0].x10; Snake[0].y10; Snake[1].x11; Snake[1].y10; Snake[2].x12; Snake[2].y10; Snake[0].direct3; Snake[0].len3; Food.isfood1; // TODO: Add your specialized code here and/or call the base class } 代码说明 初始化贪吃蛇起初有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 default switch(nChar) { case VK_UP:if(Snake[0].direct!2)Snake[0].direct1;break; case VK_DOWN:if(Snake[0].direct!1)Snake[0].direct2;break; case VK_LEFT:if(Snake[0].direct!4)Snake[0].direct3;break; case VK_RIGHT:if(Snake[0].direct!3)Snake[0].direct4;break; } CView::OnKeyDown(nChar, nRepCnt, nFlags); CView::OnKeyDown(nChar, nRepCnt, nFlags); } OnKeyDown函数的第一个参数 UINT nChar 是接收用户键入的信息然后我们用switch进行选择判断 代码说明 Snake[0]代表的是蛇头我们对蛇头的方向Snake[0].direct进行判断。 case VK_UP:if(Snake[0].direct!2)Snake[0].direct1;break; 意思就是当Snake[0].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 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); } 这个函数功能是用鼠标右键屏幕就会马上显示当前位置的坐标信息。 其实这个函数并不是本游戏中必要添加的函数只是为了在后面游戏页面的设计的时候可以用鼠标右键屏幕了解大概的屏幕坐标信息才特地做的 这里补充一个知识点 WIN32坐标系有3种 1 世界坐标系 2 页面坐标系 3 设备坐标系 世界坐标系与页面坐标系称谓“逻辑空间”实际上就等同于我们现实生活中的数学坐标系一样。 设备坐标系则不然电脑屏幕就是一个用设备坐标系的特征为以用户区窗口左上角为0 0原点X坐标向右为正Y坐标向下为正。 Step 4 void CSNAKEView::oninit() { CDC *pDCGetDC(); CBrush DrawBrush(RGB(100,100,100)); CBrush *DrawbrushpDC-SelectObject(DrawBrush); for(int i0;iSnake[0].len-1;i) pDC-Rectangle(Snake[i].x*20,Snake[i].y*20,(Snake[i].x1)*20,(Snake[i].y1)*20); pDC-SelectObject(DrawBrush); } 代码说明 利用Windows给我们提供的CDC类来进行画图我们首先用一个指向CDC类的指针去接受与该窗口相关联的DC句柄然后用定义画刷一个DrawBrush对象并且用RGB(100,100,100)来给画刷初始化颜色。并且用SelectObject(DrawBrush);函数把对象画刷选入到设备描述表中用for循环依次把贪吃蛇的3个节点画出来。 void far rectangle(int left, int top, int right, int bottom); 这个函数的的功能根据 函数功能:该函数画一个矩形,用当前的画笔画矩形轮廓,用当前画刷进行填充. Step 5 控件添加代码 void CSNAKEView::OnStart() { // TODO: Add your command handler code here SetTimer(1,3000,NULL); AfxMessageBox(3秒后开始游戏!); } void CSNAKEView::OnPause() { // TODO: Add your command handler code here KillTimer(1); AfxMessageBox(暂停游戏...); } void CSNAKEView::OnExit() { // TODO: Add your command handler code here AfxMessageBox(退出游戏...); exit (0); } void CSNAKEView::OnContinue() { // TODO: Add your command handler code here SetTimer(1,10,NULL); } 代码说明 由于之前我们设定了WM_TIMER消息我们能运用计时器功能 用WM_TIMER来设置定时器 先请看SetTimer这个API函数的原型 UINT_PTR SetTimer( HWND hWnd, // 窗口句柄 UINT_PTR nIDEvent, // 定时器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函数众所周知视图类中的输出.视图类的输出基本上都是在视图类的OnDraw函数中处理的系统会准备好入参然后调用OnDraw函数 本人也没有过多的去研究过次函数仅知道一些皮毛。不过这里有个知识点那便是OnPaint()与OnDraw()的区别OnPaint派生于CWnd类响应WM_PAINT消息。OnDraw是CView类的成员函数并且没有消息响应功能这就是为什么视图类没有只有OnDraw()而没有OnPaint的原因。OnDraw()维护视图客户区例如通过试表在视图中画图而OnPaint维护窗口的客户区 void CSNAKEView::OnDraw(CDC* pDC) { CSNAKEDoc* pDoc GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here CBrush 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,19,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 here and/or call default CDC *pDCGetDC(); CString soure; if(Snake[0].len2)SetTimer(1,370,NULL); if(Snake[0].len3)SetTimer(1,270,NULL); if(Snake[0].len6)SetTimer(1,200,NULL); if(Snake[0].len9)SetTimer(1,100,NULL); soure.Format(得分:%d!,(Snake[0].len-3)*10); //撞界判断 if(Snake[0].x*2037||Snake[0].y*2037||Snake[0].x*20462||Snake[0].y*20462) { KillTimer(1); AfxMessageBox(soure); // s0; } //蛇身相撞判断 if(Snake[0].len3) for(int snSnake[0].len-1;sn0;sn--) { if(Snake[0].x*20Snake[sn].x*20Snake[0].y*20Snake[sn].y*20) { KillTimer(1); AfxMessageBox(soure); // s0; } } /// 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].x1)*20,(Snake[Snake[0].len-1].y1)*20); for(int iSnake[0].len-1;i0;i--) { Snake[i].xSnake[i-1].x; Snake[i].ySnake[i-1].y; } //行走方向判断 if(Snake[0].direct1)Snake[0].y--; if(Snake[0].direct2)Snake[0].y; if(Snake[0].direct3)Snake[0].x--; if(Snake[0].direct4)Snake[0].x; pDC-SelectStockObject(BLACK_PEN); CBrush DrawBrush(RGB(100,100,100)); CBrush *DrawbrushpDC-SelectObject(DrawBrush); pDC-Rectangle(Snake[0].x*20,Snake[0].y*20,(Snake[0].x1)*20,(Snake[0].y1)*20); pDC-SelectObject(DrawBrush); //判断吃豆的条件,撞到就吃 if(Snake[0].x*20Food.x*20Snake[0].y*20Food.y*20) { Snake[0].len; Food.isfood1; Snake[Snake[0].len-1].xSnake[Snake[0].len-2].x; Snake[Snake[0].len-1].ySnake[Snake[0].len-2].y; } //如果食物被吃了 就生成 if(Food.isfood1) { srand((unsigned)time(NULL)); do { for(int isfoSnake[0].len-1;isfo0;isfo--) if(Snake[0].x*20Snake[isfo].x*20Snake[0].y*20Snake[isfo].y*20) { Food.xrand()%25; Food.yrand()%25; } } while(Food.x*2070||Food.y*2070||Food.x*20430||Food.y*20430); pDC-Rectangle(Food.x*20,Food.y*20,(Food.x1)*20,(Food.y1)*20); Food.isfood0; } CView::OnTimer(nIDEvent); } / 下面由我来详细分析代码 if(Snake[0].len2)SetTimer(1,370,NULL); if(Snake[0].len3)SetTimer(1,270,NULL); if(Snake[0].len6)SetTimer(1,200,NULL); if(Snake[0].len9)SetTimer(1,100,NULL); 此段代码的作用是根据蛇的长度来进行SetTimer函数的定义可以根据长度来进行游戏难度的设定如上代码分别370ms 270ms 200ms 100ms进行一次新的移动。 //撞界判断 if(Snake[0].x*2037||Snake[0].y*2037||Snake[0].x*20462||Snake[0].y*20462) { KillTimer(1); AfxMessageBox(soure); } 这里为什么要乘以20呢由于我们初始化的时候是 Snake[0].x10; Snake[0].y10; Snake[1].x11; Snake[1].y10; Snake[2].x12; Snake[2].y10; 所以只是把贪吃蛇的起始位置“搬移”到20倍的位置 当然可以等价于 Snake[0].x10*20; Snake[0].y10*20; Snake[1].x11*20; Snake[1].y10*20; Snake[2].x12*20; Snake[2].y10*20; 蛇的一节身体为一个矩形块这样表示每个矩形块只需起点坐标x 和y身体是不断增长的所以用数组存放每一节的坐标 //蛇身相撞判断 if(Snake[0].len3) for(int snSnake[0].len-1;sn0;sn--) { if(Snake[0].x*20Snake[sn].x*20Snake[0].y*20Snake[sn].y*20) { KillTimer(1); AfxMessageBox(soure); // s0; } } 这段是最好理解的了由于判断蛇自己是否咬到了自己根据蛇长sn进行sn次for sn-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].x1)*20,(Snake[Snake[0].len-1].y1)*20);//让它去画最后一个节点 for(int iSnake[0].len-1;i0;i--)//贪吃蛇的蛇身移动 { Snake[i].xSnake[i-1].x; Snake[i].ySnake[i-1].y; } 这段的知识点要严重的说明一下 1 为什么要专门用白笔画最后一个呢因为我们的游戏界面是用白色的贪吃蛇移动的时候肯定是蛇头向前走一单位而尾部肯定要“擦除”掉一个单位那怎么擦除呢我们只能用和背景色一样的画笔。原来蛇的位置和新蛇的位置差一个单位所以看起来蛇会多一节身体所以将蛇的最后一节用背景色覆盖SelectStockObject(WHITE_PEN) 让它起到“消失”最后一个节点的功能 让我们以为蛇是向前走了。 如 假设现在贪吃蛇有4个单位0123 Snake[Snake[0].len-1].x*20 Snake[Snake[0].4-1].x*20 Snake[3].x*20 数组3 恰好是最后一个节点的下标别忘了数组是从0开始算的 请仔细领悟 2 for(int iSnake[0].len-1;i0;i--) { Snake[i].xSnake[i-1].x; Snake[i].ySnake[i-1].y; } 进行次数为长度-1次的循环 为什么要进行长度-1次呢我这里要说明一下这里是不包括蛇头的进行的平移用数组赋值的方法把前一节点保存在后一个结点然后头结点向前移动。 //方向判断 if(Snake[0].direct1)Snake[0].y--; if(Snake[0].direct2)Snake[0].y; if(Snake[0].direct3)Snake[0].x--; if(Snake[0].direct4)Snake[0].x; pDC-SelectStockObject(BLACK_PEN); CBrush DrawBrush(RGB(100,100,100)); CBrush *DrawbrushpDC-SelectObject(DrawBrush); pDC-Rectangle(Snake[0].x*20,Snake[0].y*20,(Snake[0].x1)*20,(Snake[0].y1)*20); pDC-SelectObject(DrawBrush); “1234”本别代表“上下左右” x,y进行移动然后用选定画刷画出一个新节点的矩形作为头结点 //食物判断 if(Food.isfood0) { srand((unsigned)time(NULL)); do { for(int isfoSnake[0].len-1;isfo0;isfo--) if(Snake[0].x*20Snake[isfo].x*20Snake[0].y*20Snake[isfo].y*20) { Food.xrand()%; Food.yrand()%; } } while(Food.x*2070||Food.y*2070||Food.x*20430||Food.y*20430); pDC-Rectangle(Food.x*20,Food.y*20,(Food.x1)*20,(Food.y1)*20); Food.isfood1; } 这里关于 srand((unsigned)time(NULL)); rand() 的用法 在本博客有研究帖 这里不再赘述。 当食物被吃了指定食物的地方出现的地方在小于70 和大于430的矩形区域内 并且把食物属性更改为 “存在” 原博主那的博文里面代码有点小小的错误在这里我已经修改好了。可能还存在不足之处望多包涵 // 转载http://blog.sina.com.cn/s/blog_6cbbf04b0100m49k.html