手把手教MFC贪吃蛇

分类: Cplusplus |
手把手教“MFC版贪吃蛇教程”
写在前面的话
本次贪吃蛇教程主要知识点包括以下几个方面
1
2
3
4
5
由于此次贪吃蛇需要MFC的开发环境,所以打开VisualC++新建一个MFC
AppWizard单文档工程,随意取名一个工程名称。http://s4/middle/6cbbf04bg7505ad384583&690
http://s2/middle/6cbbf04bga6d8db2d7561&690
本次贪吃蛇基本流程与大体思想
1
2
3
4
5
///////////////////////////////////////////////////////////////////////
第一部分
首先在已有工程下的“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()中,主要初始化视图中控件等。对各个变量进行初始化操作
所以我们要用这个函数来进行贪吃蛇的初始化工作。
http://s8/middle/6cbbf04bg7505ae925807&690
再者还要添加一个成员函数oninit()
进行贪吃蛇外观的初始化
控件的设计
再者是设计游戏的一些控件来控制“游戏开始” “游戏结束” 和“游戏暂停”。
我们可以点击“工作空间”的“ResourceView”进行控件的具体设计,这里我们在Menu文件夹中把“IDR_MAINFRAME”中默认的控件全部删除http://s5/middle/6cbbf04bg9238d80945e4&690
右键其中的标题栏,点击属性,会得到一个菜单栏标题,我们分别建立1个菜单栏标题。这里我们分别建“游戏”。http://s10/middle/6cbbf04bg9238dc1bc029&690
点击并且在已有控件中的列表中点击属性,进行“菜单项目属性”的设置。我们本别建立的属性“标明”与对应的ID有
游戏开始
游戏暂停
游戏继续
游戏退出
此处控件的设计是“可见即可得”的控件操作
http://s5/middle/6cbbf04bg9238dd6dd394&690
http://s10/middle/6cbbf04bg9238df68b3a9&690
成功设置ID之后 我们分别右键
各项属性进行消息响应处理函数的生成
具体方法操作例子如下
1右键“游戏开始”
2 点击“类向导建立”
3 在Message Maps页面,在要进行消息响应的控件ID列表Object IDs中上选择对应的ID, 这里我们选择IDM_START,具体实现的环境是CView类,所以我们必须把“Class name”的默认“CMainFrame”改为“CView类”,并且在“Messages”类型设置中,用“COMMAND”设置为其为命令消息。其余各项也按照同理进行设置。
http://s8/middle/6cbbf04bg9238eff12d47&690
PS:Windows消息的分类有3种,标准消息,命令消息,通告消息
1 标准消息除COMMAND之外,所以WM_开头都是标准消息
2 命令消息就是COMMAND
3 通告消息由控件产生的消息,这类信息也能以WM_COMMAND类型出现
最后我们回到原来的ClassView去看看我们一共添加的函数
http://s1/middle/6cbbf04bg9238f47c93e0&690
http://s15/middle/6cbbf04bg9238f538ed7e&690
所有的函数集合全部有以下http://s12/middle/6cbbf04bg923987fd147b&690
在此处我们可以看到我们添加的Windows消息所有的 消息响应函数的声明
http://s2/middle/6cbbf04bg92392b065161&690
//////////////////////////////////////////////////////////
第二部分
Step 1
首先我们在文件开头处分别定义 蛇 和食物 的全局变量
struct Snake
{
}Snake[50];
struct Food
{
}Food;
再者
void CSNAKEView::OnInitialUpdate()
{
}
http://s3/middle/6cbbf04bg9239b174b562&690
代码说明:
Step 2 对OnKeyDown()具体添加代码
void CSNAKEView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT
nFlags)
{
}
http://s2/middle/6cbbf04bg92393f66ec11&690
OnKeyDown函数的第一个参数 UINT nChar 是接收用户键入的信息,然后我们用switch进行选择判断
代码说明:
case
VK_UP:if(Snake[0].direct!=2)Snake[0].direct=1;break;
意思就是当Snake[0].direct的方向此时并不等于“下”的时候,才能做出“上”的操作动作,否则则忽略用户“向上”的操作按键效果
Step
3
void CSNAKEView::OnRButtonDown(UINT nFlags, CPoint point)
{
}
这个函数功能是:用鼠标右键屏幕,就会马上显示当前位置的坐标信息。
其实这个函数并不是本游戏中必要添加的函数,只是为了在后面游戏页面的设计的时候可以用鼠标右键屏幕了解大概的屏幕坐标信息,才特地做的
这里补充一个知识点
1
2 页面坐标系
3 设备坐标系
Step 4
void CSNAKEView::oninit()
{
}
http://s10/middle/6cbbf04bg9239c5eafc99&690
代码说明:
这个函数的的功能根据
函数功能:该函数画一个矩形,用当前的画笔画矩形轮廓,用当前画刷进行填充.
Step 5 控件添加代码
void CSNAKEView::OnStart()
{
}
void CSNAKEView::OnPause()
{
}
void CSNAKEView::OnExit()
{
}
void
CSNAKEView::OnContinue()
{
}
http://s7/middle/6cbbf04bg7505c92bf1b6&690
代码说明:
先请看SetTimer这个API函数的原型
UINT_PTR SetTimer(
HWND hWnd, // 窗口句柄
UINT_PTR nIDEvent, // 定时器ID,多个定时器时,可以通过该ID判断是哪个定时器
UINT uElapse, // 时间间隔,单位为毫秒
TIMERPROC lpTimerFunc // 回调函数
);
在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)
{
}
Step 7 对OnTime()的添加代码
void CSNAKEView::OnTimer(UINT nIDEvent)
{
}
http://s6/middle/6cbbf04bga6d9f25a1475&690
http://s9/middle/6cbbf04bga6d9f2e5cb28&690
/////////////////////////////////
此段代码的作用是根据蛇的长度来进行SetTimer()函数的定义,可以根据长度来进行游戏难度的设定,如上代码分别370ms 270ms 200ms 100ms进行一次新的移动。
这里为什么要乘以20呢?由于我们初始化的时候是
所以只是把贪吃蛇的起始位置“搬移”到20倍的位置 当然可以等价于
蛇的一节身体为一个矩形块,这样表示每个矩形块只需起点坐标x 和y身体是不断增长的,所以用数组存放每一节的坐标
//蛇身相撞判断
这段是最好理解的了,由于判断蛇自己是否咬到了自己,根据蛇长sn,进行sn次for sn-1次循环
并且和Snake[0].x进行比较(之所以进行sn-1次那肯定是不包括蛇头而且蛇的长度也必须大于3才会发生自己咬自己的情况)
KillTimer(1);是停止计时器;和之前的SetTime()对应而已
AfxMessageBox(soure);这里不深究,总之就是输出一个原样输出内容
这段的知识点要严重的说明一下
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
进行次数为"长度-1"次的循环
为什么要进行长度-1次呢?我这里要说明一下,这里是不包括蛇头的进行的平移,用数组赋值的方法,把前一节点保存在后一个结点,然后头结点向前移动。
这里关于
srand((unsigned)time(NULL));
rand()
的用法 在本博客有研究帖 这里不再赘述。
当食物被吃了指定食物的地方出现的地方在小于70 和大于430的矩形区域内
并且把食物属性更改为 “存在”
//////////////////////////////////////////////////////////////////
,CView类的强大我是第一次看到,以后我要多加关注CView类的知识点,写出这个文档的意义我觉得能让我的思路更加清晰,毕竟能和他人解释代码并且让别人能理解那是一件很愉快的事情。http://www/uc/myshow/blog/misc/gif/E___6725EN00SIGG.gif
PS:
本文是本人很久之前写的,当初写的时候也没有想到会受到广大朋友们的关注,听到许多人留言反馈有许多人问题。本人近段时间也要进行考研复习,MFC
VC++都很久没有复习了。今天晚上特地看了一遍,发现很多问题确实比较低级。我也尽量改了。请大家多多包涵。