VC6.0MFC单文档贪吃蛇游戏基础入门

    技术2026-02-20  12

    贪吃蛇游戏

    一、整体思路

    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的朋友。作品仅做基础展示,感兴趣的可在此基础上添加更多的功能,比如设置不同形状的食物,设置对话框调用功能模式选项,设置背景音乐等等……

    Processed: 0.027, SQL: 9