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

贪吃蛇游戏

一、整体思路

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

评论 23
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值