贪吃蛇游戏
一、整体思路
1、贪吃蛇对大家来说并不陌生,既然要设计贪吃蛇,那么我们首先要定义蛇和食物这样两个对象,并给它们添加一些成员变量。
2、添加虚函数OnInitialUpdate()做一些初始化工作。
3、添加消息响应句柄WM_KEYDOWN实现蛇的运动。
4、添加WM_TIMER消息,最重要最核心的就是如何在OnTimer里去实现。
5、判断蛇撞屏幕边界以及撞自身,吃了食物后蛇如何变长等等,是我们设计的难点。
那么接下来,我们一起去探索吧!
二、实现步骤(前期)
1、新建一个MFC单文档应用程序,如下图所示。
2、我们可以先定义好蛇和食物这样两个结构体,如下图所示。
typedef struct
{
int x,y; //定义蛇的位置坐标
int len; //定义蛇的长度
int direct; //定义蛇的运动方向
}MySnake; //定义蛇对象
typedef struct
{
int x,y; //定义食物的位置坐标
bool isfood; //定义食物有无
}MyFood; //定义食物对象
3、接下来添加虚函数OnInitialUpdate(),在里面对蛇和食物做一些初始化处理,如下图所示。
代码如下:
void CGluttonousSnakeView::OnInitialUpdate()
{
CView::OnInitialUpdate();
// TODO: Add your specialized code here and/or call the base class
//对蛇进行初始化(假设刚开始蛇有两节)
m_snake[0].x = 20;
m_snake[0].y = 20; //起始蛇头位置
m_snake[1].x = 20;
m_snake[1].y = 21;
m_snake[0].direct = 40; //蛇刚开始向下运动,40为键盘向下的ASCII码
m_snake[0].len = 2; //初始化蛇的长度为2个小方格
m_food.isfood = false; //false表示刚开始界面上没有食物
}
4、添加成员函数,定义画刷给蛇画上颜色,让它变得更加的迷人,如下图所示。
void CGluttonousSnakeView::DrawSnake(CDC *pDC)
{
DrawArea(pDC); //游戏区域背景(下面马上会用到)
CBrush brush, *pOldBrush; //创建画刷
brush.CreateSolidBrush(RGB(255,0,0)); //染上性感的红色
pOldBrush = pDC->SelectObject(&brush);
for(int i = 0; i <= m_snake[0].len - 1; i++) //依次把蛇的初始化两节画出来
pDC->Rectangle(m_snake[i].x * 20, m_snake[i].y * 20, (m_snake[i].x + 1) * 20, (m_snake[i].y + 1) * 20); //乘20是把它放到20倍的位置处,自己测试用其他数均可
brush.DeleteObject();
pDC->SelectObject(pOldBrush);
}
5、添加成员函数,画蛇的活动范围,如下图所示。
代码如下:
void CGluttonousSnakeView::DrawArea(CDC *pDC)
{
CBrush brush, *pOldBrush;
brush.CreateSolidBrush(RGB(255,255,255)); //用白色填充,为下文埋下伏笔
pOldBrush = pDC->SelectObject(&brush);
pDC->Rectangle(100,100,800,800); //游戏区域大小,可随意设定,You Happy就OK
brush.DeleteObject();
pDC->SelectObject(pOldBrush);
}
6、这时就可以在OnDraw(CDC* pDC)里调用DrawSnake(CDC *pDC),编译运行后的结果如下图所示。
三、实现步骤(中期)
7、我们的蛇和游戏区域已经有了,现在就引蛇出“动”吧。
8、接下来,在ResourceView的Menu中添加菜单,并对其重命名和添加“COMMAND”命令消息响应,具体如下图所示。
9、紧接着我们在上面添加的两个命令消息里写一些内容,代码如下。
void CGluttonousSnakeView::OnMGameStart()
{
// TODO: Add your command handler code here
SetTimer(1,300,NULL);
//启动定时器。第一个参数是时钟ID号;第二个是间断的时间,决定时钟每秒
//钟被调用多少次; 第三个是回调函数(默认为空)。
//如果1s中断10次的话,300单位是毫秒,也就是0.3秒,0.3s/次
}
void CGluttonousSnakeView::OnMGameOver()
{
// TODO: Add your command handler code here
KillTimer(1); //停止时钟
}
10、咋们趁热打铁,在CGluttonousSnakeView中右击Add Windows Message Handler…,调用系统时钟(WM_TIMER),待会儿再在OnTimer(UINT nIDEvent)里添加代码,如下图所示。
11、做了这么久,突然发现还没有添加键盘响应实现蛇的上下左右运动,那么我们就先将它完成吧。在CGluttonousSnakeView中右击Add Windows Message Handler…,选中“WM_KEYDOWN”,并添加实现代码,如下图所示。
代码如下:
void CGluttonousSnakeView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// TODO: Add your message handler code here and/or call default
switch(nChar)
{//对蛇头方向进行判断
case 37:
if(37 == nChar && m_snake[0].direct != 39)
m_snake[0].direct = 37; //蛇向左运动,并且方向不是向右,则按左键有响应(通俗地说就是,蛇不能掉头)
break;
case 38:
if(38 == nChar && m_snake[0].direct != 40)
m_snake[0].direct = 38; //蛇向上运动,并且方向不是向下,则按上键有响应
break;
case 39:
if(39 == nChar && m_snake[0].direct != 37)
m_snake[0].direct = 39; //蛇向右运动,并且方向不是向左,则按右键有响应
break;
case 40:
if(40 == nChar && m_snake[0].direct != 38)
m_snake[0].direct = 40; //蛇向下运动,并且方向不是向上,则按下键有响应
break;
}
bool m_pause; //空格键控制暂停
if(32 == nChar) //空格键ASCII码为32
{
m_pause = !m_pause;
if(m_pause)
KillTimer(1);
else
SetTimer(1,5000,NULL);
AfxMessageBox("暂停5秒再继续吧!");
}
CView::OnKeyDown(nChar, nRepCnt, nFlags);
}
四、实现步骤(后期)
12、到了这儿就是设计的重难点所在了,我先分析下在OnTimer(UINT nIDEvent)里的设计思路。(前期、中期运行后没有错误,大家可以在每写一个模块后就编译运行下,养成良好习惯,这样错误会及时发现,不至于给后期带来过多麻烦。)我们要做蛇撞边界的判断和撞自身的判断,还要对蛇的运动方向做判断(这个比较简单),最后就是蛇吃食物自身变长,当食物被吃后,如何随机生成?
带着这些问题,我们一步步去实现。
13、为了让代码整体模块化,我们可将蛇撞边界、撞自身,以及食物的随机产生等模块化,需要的时候直接调用就行,这样也更加方便项目的后期维护。
14、判断蛇撞边界和撞自身的代码如下。
void CGluttonousSnakeView::JudgeHit(CDC *pDC)
{
CString str;
str.Format("游戏结束!您获得了 %d 分",10 * (m_snake[0].len - 2 ));
//撞边界判断
if(m_snake[0].x * 20 <= 100 || m_snake[0].y * 20 <= 100 || m_snake[0].x * 20 >= 780 || m_snake[0].y * 20 >= 780)
{
KillTimer(1);
//AfxMessageBox(str);
CFont ft; //设置输出字体
ft.CreatePointFont(300,_T("隶书"),NULL); //输出字体大小、风格
pDC->SelectObject(&ft);
pDC->SetTextColor(RGB(255,0,0)); //字体颜色
pDC->TextOut(185,440,str); //游戏结束后,让字体输出到屏幕上
}
//撞蛇身判断
if(m_snake[0].len >= 4) //蛇身大于等于4时才会撞上自己
{
for(int she = m_snake[0].len - 1; she > 0; she--)
{
if(m_snake[0].x * 20 == m_snake[she].x * 20 && m_snake[0].y * 20 == m_snake[she].y * 20) //蛇头跟相撞的点重合
{
KillTimer(1);
//AfxMessageBox(str);
CFont ft;
ft.CreatePointFont(300,_T("隶书"),NULL);
pDC->SelectObject(&ft);
pDC->SetTextColor(RGB(255,0,0));
pDC->TextOut(185,440,str);
}
}
}
pDC->SelectStockObject(WHITE_PEN); //把白色PEN选入设备进行画图,将最后一节用背景色覆盖
pDC->Rectangle(m_snake[m_snake[0].len - 1].x * 20, m_snake[m_snake[0].len - 1].y * 20, (m_snake[m_snake[0].len - 1].x + 1) * 20, (m_snake[m_snake[0].len - 1].y + 1) * 20);
/*让它画最后一个节点*/
for(int i = m_snake[0].len - 1; i > 0; i--) //蛇身移动
{
m_snake[i].x = m_snake[i - 1].x;
m_snake[i].y = m_snake[i - 1].y;
}
JudgeDirection(pDC); //判断运动方向
pDC->SelectStockObject(BLACK_PEN);
CBrush brush, *pOldBrush;
brush.CreateSolidBrush(RGB(255,0,0));
pOldBrush = pDC->SelectObject(&brush);
pDC->Rectangle(m_snake[0].x * 20 , m_snake[0].y * 20 , (m_snake[0].x + 1) * 20, (m_snake[0].y + 1) * 20);
brush.DeleteObject();
pDC->SelectObject(pOldBrush);
}
15、判断随机产生食物的代码如下。
void CGluttonousSnakeView::RandomFood(CDC *pDC)
{
if(m_snake[0].x * 20 == m_food.x * 20 && m_snake[0].y * 20 == m_food.y * 20) //如果食物被吃
{
m_snake[0].len ++ ; //蛇变长
m_food.isfood = false;
m_snake[m_snake[0].len].x = m_snake[m_snake[0].len - 1].x;
m_snake[m_snake[0].len].y = m_snake[m_snake[0].len - 1].y;
}
if(m_food.isfood == false) //没有食物就随机生成
{
srand(time(NULL));
m_food.x = rand()%40;
m_food.y = rand()%40;
while(m_food.x * 20 <= 100 || m_food.y * 20 <= 100 || m_food.x * 20 >= 780 || m_food.y * 20 >= 780)
{//随机食物产生的边界值
for(int fod = m_snake[0].len - 1; fod >= 0; fod--)
if(m_snake[0].x * 20 == m_snake[fod].x * 20 && m_snake[0].y * 20 == m_snake[fod].y * 20)
{
m_food.x = rand()%40;
m_food.y = rand()%40;
}
}
CBrush brush, *pOldBrush;
brush.CreateSolidBrush(RGB(60,179,113)); //食物颜色
pOldBrush = pDC->SelectObject(&brush);
pDC->Ellipse(m_food.x * 20, m_food.y * 20, (m_food.x + 1) * 20, (m_food.y + 1) * 20); //圆形食物
brush.DeleteObject();
pDC->SelectObject(pOldBrush);
m_food.isfood = true;
}
}
16、游戏难度设定,这里简单处理,即随着蛇的变长,速度会越来越快,代码如下。
void CGluttonousSnakeView::SpeedSetting()
{
if(m_snake[0].len / 5 == 0) //len = 2,3,4
SetTimer(1,300,NULL);
if(m_snake[0].len / 5 == 1) //len = 5,6,…,9
SetTimer(1,200,NULL);
if(m_snake[0].len / 5 == 2) //len = 10,11,…,14
SetTimer(1,100,NULL);
if(m_snake[0].len / 5 == 3) //len = 15,16,…,19
SetTimer(1,80,NULL);
if(m_snake[0].len / 5 > 3) //len = 20,21,……
SetTimer(1,50,NULL);
}
17、判断运动方向,代码如下。
void CGluttonousSnakeView::JudgeDirection(CDC *pDC)
{
if(m_snake[0].direct == 37)
m_snake[0].x--; //向左
if(m_snake[0].direct == 38)
m_snake[0].y--; //向上
if(m_snake[0].direct == 39)
m_snake[0].x++; //向右
if(m_snake[0].direct == 40)
m_snake[0].y++; //向下
}
18、上面的模块化之后,在OnTimer(UINT nIDEvent)里就可以调用啦。
void CGluttonousSnakeView::OnTimer(UINT nIDEvent)
{
// TODO: Add your message handler code here and/or call default
CDC *pDC = GetDC();
SpeedSetting(); //游戏难度设置
JudgeHit(pDC); //撞边界和撞蛇身判断
RandomFood(pDC); //如果食物被吃就随机生成
CView::OnTimer(nIDEvent);
}
19、可用双缓存技术做一些小小的改进,如下图所示。
代码如下:
BOOL CGluttonousSnakeView::OnEraseBkgnd(CDC* pDC)
{
// TODO: Add your message handler code here and/or call default
return TRUE;
return CView::OnEraseBkgnd(pDC);
}
20、此时,在OnDraw()中的最终代码如下。
void CGluttonousSnakeView::OnDraw(CDC* pDC)
{
CGluttonousSnakeDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
CDC MemDC; //定义内存DC
int width,height; //定义屏幕宽度、高度
CRect rect; //建立rect对象
CBitmap MemBitmap; //缓冲的内存位图
GetWindowRect(&rect); //获取当前视图的大小
width = rect.Width();
height = rect.Height(); //记录当前屏幕大小
MemDC.CreateCompatibleDC(NULL); //建立兼容内存DC(设备上下文)
MemBitmap.CreateCompatibleBitmap(pDC,width,height);
CBitmap *pOldBit = MemDC.SelectObject(&MemBitmap); //保存之前的内存位图
MemDC.FillSolidRect(0,0,width,height,RGB(192,192,192)); //设置背景颜色
MemDC.SetBkMode(TRANSPARENT); //设置缓冲DC参数,为双缓存机制做准备
DrawSnake(&MemDC);
pDC->BitBlt(0,0,width,height,&MemDC,0,0,SRCCOPY);
MemBitmap.DeleteObject();
MemDC.DeleteDC();
}
五、运行结果
以上内容是MFC的基础入门,适合刚刚学习MFC的朋友。作品仅做基础展示,感兴趣的可在此基础上添加更多的功能,比如设置不同形状的食物,设置对话框调用功能模式选项,设置背景音乐等等……