实验项目二:贪吃蛇游戏开发

一、前言

设计一个简单的贪吃蛇游戏,实现其基本功能:

  1. 屏幕上随机出现一个“食物”,称为豆子,上下左右控制“蛇”的移动,吃到“豆子”以后“蛇”的身体加长一点。
  2. “蛇”碰到边界或蛇头与蛇身相撞,蛇死亡,游戏结束。
  3. 为游戏设计友好的交互界面;例如欢迎界面,游戏界面,游戏结束界面。要有开始键、暂停键和停止退出的选项。
  4. 对蛇吃到豆子进行分值计算,可以设置游戏速度,游戏音乐等拓展元素。

二、基本流程

在这里插入图片描述

三、游戏界面设计

1. 界面构造

这里我用到了MFC,加入了多个button控件来控制游戏的开始、停止与结束,以及贪吃蛇的上下左右移动;并加入了edit控件来显示当前等级与统计得分情况。如下图所示:
在这里插入图片描述
并在dlg文件中对界面的地图和单个格子的大小进行调试

#define LENGTH 10
#define SIZE_H 60
#define SIZE_V 60

在OnPaint函数中将背景颜色改为白色

		CClientDC dc(this);
		dc.Rectangle(0, 0, LENGTH * SIZE_H, LENGTH * SIZE_V);

调试程序得到界面
在这里插入图片描述
完成了游戏主页面才发现,初始欢迎界面还没有设置。
首先在资源列表中添加资源,命名为CWelcomeDlg,并添加一个对应的类;增加button控件跳转进入正式游戏界面。此时发现调试程序只能出现主界面无法出现欢迎界面;
在网上查找资料后,进入主界面的无Dlg后缀的同名文件,将

CMFCGreedySnakeDlg dlg;

修改为刚刚新增的界面

CWelcomeDlg dlg;

并在CWelcomeDlg的Button控件中加入触发响应函数

void CWelcomeDlg::OnBnClickedWelcome()
{
	CDialogEx::OnOK(); //关闭主界面
	CMFCGreedySnakeDlg dlg;
	dlg.DoModal();
}

如此,就实现了点击欢迎界面进入游戏主界面。
(可以插张图

2. 蛇的构造

创建一个snake类
Snake.h

#pragma once
class Snake
{
public:
	Snake();
private:
	CList<CPoint> snake_body;
	CPoint snake_direction;
public:
	void DrawSnake();
	CList<CPoint>* GetBody();
};

Snake.cpp

#include "pch.h"
#include "Snake.h"

Snake::Snake()
{
	//初始长度为3,放在正中间
	snake_body.AddTail(CPoint(30, 29));
	snake_body.AddTail(CPoint(30, 30));
	snake_body.AddTail(CPoint(30, 31));
	//初始化方向向上
	snake_direction.SetPoint(0, -1);
}

void Snake::DrawSnake()
{
	
}

CList<CPoint>* Snake::GetBody()
{
	return &snake_body;
}

在Dlg文件中调用

void CMFCGreedySnakeDlg::DrawSnake()
{
	CClientDC dc(this);
	CBrush red(RGB(255, 0, 0));
	dc.SelectObject(red);
	CList<CPoint>* pBody = m_snake.GetBody();
	//遍历整个蛇身,画出整条蛇
	POSITION p = pBody->GetHeadPosition();
	while (p)
	{
		CPoint point = pBody->GetNext(p);
		dc.Rectangle(point.x * LENGTH, point.y * LENGTH,
			(point.x + 1) * LENGTH, (point.y + 1) * LENGTH);
	}
}

并将开始游戏的Button绑定创建蛇身

void CMFCGreedySnakeDlg::OnBnClickedStart()
{
	DrawSnake();
}

此时,进入主界面点击开始游戏,则出现以下页面
在这里插入图片描述

3. 食物构造

在Dlg中加入setBean函数。考虑到随机生成的食物不能与蛇身重合,还要加入一层循环检测。

在Snake类中新增函数

bool Snake::IsInBody(CPoint point) {
	POSITION p = snake_body.GetHeadPosition();
	while (p)
	{
		if (point == snake_body.GetNext(p))
			return true;
	}
	return false;
}

用以判断豆子是否落在蛇身上

void CMFCGreedySnakeDlg::setBean()
{
	srand(time(NULL));  //初始化rand函数
	m_ptFood.x = rand() % 58 + 1;//随机产生x坐标
	m_ptFood.y = rand() % 58 + 1;
	while (m_snake.IsInBody(m_ptFood)) {
		m_ptFood.x = rand() % 58 + 1;//随机产生x坐标
		m_ptFood.y = rand() % 58 + 1;
	}

	CClientDC dc(this);
	CBrush blue(RGB(0, 0, 255));
	dc.SelectObject(blue);
	dc.Rectangle(m_ptFood.x * LENGTH, m_ptFood.y * LENGTH,
		(m_ptFood.x + 1) * LENGTH, (m_ptFood.y + 1) * LENGTH);
}

并通过“开始游戏”触发响应

void CMFCGreedySnakeDlg::OnBnClickedStart()
{
	DrawSnake();
	setBean();
}

调试游戏界面,开始游戏后
在这里插入图片描述

四、游戏过程

1. 蛇的移动

在类向导中添加定时器消息的响应函数WM_TIMER,并在Dlg中新建StartMove函数,添加在OnTimer函数中,让蛇每隔1000毫秒运动一次,并添加在“开始游戏”控件中。

void CMFCGreedySnakeDlg::StartMove()
{
	setBean();
	SetTimer(1, 1000, NULL);
}

顺便增加了游戏暂停的控件触发

void CMFCGreedySnakeDlg::OnBnClickedStop()
{
	KillTimer(1);
}

为Button控件的上下左右设置触发

void CMFCGreedySnakeDlg::OnBnClickedUp()
{
	// 上
	m_snake.DirectionChange(CPoint(0, -1));
}


void CMFCGreedySnakeDlg::OnBnClickedDown()
{
	// 下
	m_snake.DirectionChange(CPoint(0, 1));
}


void CMFCGreedySnakeDlg::OnBnClickedLeft()
{
	// 左
	m_snake.DirectionChange(CPoint(-1, 0));
}


void CMFCGreedySnakeDlg::OnBnClickedRight()
{
	// 右
	m_snake.DirectionChange(CPoint(1, 0));
}

2. 蛇吃食物与碰撞检测

使用链表来表示蛇的移动,这样只需要改变蛇头与蛇尾的位置,更加简便。
在这里插入图片描述

bool Snake::MoveSnake(CPoint food)
{
	CPoint newHead = snake_body.GetHead() + snake_direction;//新的蛇头位置
	if (IsInBody(newHead))//撞到自己
		return false;
	else if (newHead.x < 0 || newHead.x>60 || newHead.y < 0 || newHead.y>60)
		return false;
	if (newHead != food) //没吃到豆子
	{
		snake_body.RemoveTail();//删掉蛇尾
	}
	snake_body.AddHead(newHead);//新的蛇头在最前面
	return true;
}

在Dlg中加入SnakeMove函数

void CMFCGreedySnakeDlg::SnakeMove()
{
	CClientDC dc(this);
	CPoint tail = m_snake.GetBody()->GetTail();//获取之前的蛇尾
	if (m_snake.Move(m_ptFood))//移动成功
	{
		CPoint head = m_snake.GetBody()->GetHead();//获取新的蛇头
		CBrush red(RGB(255, 0, 0));
		CBrush* old = dc.SelectObject(&red);
		dc.Rectangle(head.x * LENGTH, head.y * LENGTH,
			(head.x + 1) * LENGTH, (head.y + 1) * LENGTH);
		dc.SelectObject(old);
		if (head == m_ptFood)//如果吃到豆子,则产生新豆子
		{
			setBean();
		}
		else//否则删去蛇尾
		{
			CPen white(PS_SOLID, 1, RGB(255, 255, 255));
			//CBrush white(RGB(255, 255, 255));
			dc.SelectObject(white);
			dc.Rectangle(tail.x * LENGTH, tail.y * LENGTH,
				(tail.x + 1) * LENGTH, (tail.y + 1) * LENGTH);
		}
	}
}

五、游戏结束

1. 结束界面

如果游戏途中蛇头碰到蛇身,或者蛇头碰到边框,则会出现以下界面:

在这里插入图片描述
点击退出游戏窗口,则提示游戏结束,并自动退出程序。

void CMFCGreedySnakeDlg::OnBnClickedExit()
{
	KillTimer(1);
	MessageBox(_T("游戏结束!"));
	EndDialog(0);
}

六、扩展功能

1. 移动速度

在键盘输入数字,点击等级选择,就可改变移动速度

void CMFCGreedySnakeDlg::OnBnClickedLevel()
{
	CString str;
	m_level.GetWindowText(str);
	int num;
	num = _ttoi(str);
	switch (num)
	{
	case 1:
		SetTimer(1, 1000, NULL);
		break;
	case 2:
		SetTimer(1, 900, NULL);
		break;
	case 3:
		SetTimer(1, 800, NULL);
		break;
	case 4:
		SetTimer(1, 700, NULL);
		break;
	case 5:
		SetTimer(1, 600, NULL);
		break;
	case 6:
		SetTimer(1, 500, NULL);
		break;
	case 7:
		SetTimer(1, 400, NULL);
		break;
	case 8:
		SetTimer(1, 300, NULL);
		break;
	case 9:
		SetTimer(1, 200, NULL);
		break;
	default:
		SetTimer(1, 1000, NULL);
		m_level.SetWindowText(_T("默认1"));
		break;
	}
}

2. 得分记录

首先为得分的edit框绑定一个mScore参数,随后在函数中加入参数的屏幕显示即可。并且在Dlg中新增eat_bean的变量记录吃到豆子的个数。

CString b;
if (head == m_ptFood)//如果吃到豆子,则产生新豆子
		{
			setBean();
			eat_bean++;
			b.Format(_T("% d"), eat_bean);
			mScore.SetWindowText(b);
		}

此时吃到四颗豆子之后如图
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值