计算机软件实验2-基于MFC的贪吃蛇游戏制作

课前预习

课程要求

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

题目难点

  1. 要实现随机生成豆子(豆子不能和蛇头出现在同一位置)。
  2. 蛇身要自主增长(蛇头吃到豆子以后变长,不能直接在蛇尾增加长度,这个很可能撞墙,要在蛇头增加长度,并且更新蛇长)。
  3. 界面设计,对于游戏的不同状况要对进行分类,出现不同的提示。

贪吃蛇基本原理

我们先看一下制作贪吃蛇我们需要哪些部分:
对于用户界面设计:

afx_msg void OnBnClickedButtonStop();//stop按键
afx_msg void OnBnClickedButtonStart();//start按键
afx_msg void OnTimer(UINT_PTR nIDEvent);//界面初始化设计
CComboBox Box;//在屏幕上中修改游戏速度选项
CEdit EDIT_Score;//在屏幕展现游戏分数
virtual BOOL PreTranslateMessage(MSG* pMsg);//传送消息

对于游戏区设计:

afx_msg void OnCbnSelchangeCombospeed();//游戏界面蛇的运动速度改变
afx_msg void OnWindowPosChanging(WINDOWPOS* lpwndpos);//游戏界面改变豆子的位置
CRect m_map[30][38];//确定游戏地图大小
POINT pos = { 0,0 };//定义豆子的位置的指针
CPen pen1;//画笔1,用于画出游戏界面的格子
void gameOver();//游戏结束
int speed = 150; // 设置速度
int score = 0;//计分
bool INIT = true;//初始化是否成功
bool START = false;//判断游戏是否开始
bool DEAD = false;//判断蛇是否死亡
bool setBean();//随机生成豆子

豆子的生成要求为:一颗豆子被吃掉后立刻生成另一颗豆子,并且豆子不能与蛇头重合。
我们选择随机生成函数进行随机生成豆子。由于豆子是时刻展示在屏幕上的,需要画刷随时将其绘制出来。

对于蛇的设定:

CSnake my_snake;//定义蛇
void SnackInit();//蛇的初始化
void drawSnake();//画蛇增加长度
void MvUp();//上移
void MvDown();//下移
void MvLeft();//左移
void MvRight();右移
void eatBean();//吃豆子
bool checkLive();//检测蛇是否活着

蛇身的生长是贪吃蛇游戏的一个难点,当蛇吃到豆子之后,我们选择for循环时刻更新蛇的身体,并通过画刷展示在屏幕界面上。(另一种做法:通过链表的方式修改蛇身,通过修改指针来修改蛇身)。

整个过程的设计:
在这里插入图片描述

基于MFC制作贪吃蛇(环境VS2019)

利用MFC制作界面

用户界面及游戏界面的设计:

首先我们像实验1一样添加按钮,并修改按键的ID和按键的显示名称。(修改按键的ID是为了方便后面设计程序时找到相对应的按键)在这里插入图片描述
然后在界面上添加一个BOX,这个是用来更换游戏速度的下拉选择框,同样的修改按键ID。在这里插入图片描述
BOX下拉框是用来修改游戏速度,因此需要在属性界面设定数据,具体内容如图所示。(图片上显示的就是将该游戏设定为四个速度,分别为1,2,3,4)
在这里插入图片描述
编辑框的作用是显示用户游戏得分,同样的在属性界面修改 ID及相关信息。
在这里插入图片描述

下面展示的是在用户界面上文字提示,同样的在属性界面修改相对应的内容。
在这里插入图片描述
最后一部分展示的是游戏区,用的是编辑框,同样也进行修改相关内容。
在这里插入图片描述
确定界面的设计:
该界面的设计主要是负责蛇死亡之后的界面展示,判断是否要继续游戏。
在这里插入图片描述

功能实现

下面我们展示一下个部分功能的实现的代码:

蛇的主要功能展现:

  1. 蛇身的定义:
    蛇身的定义是专门添加的一个类Snake来写的,这个类辅助其他的类的运行(其他部分的内容都是在主程序中)。
include "pch.h"
#include "framework.h"
#include "MfcSnake.h"
#include "MfcSnakeDlg.h"
#include "Snack.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

CSnake::CSnake()//构造函数
{
	// 初始化蛇的长度为1, 向右运动, 头的位置为(10, 10)
	len = 1;
	direc = 4; // 上下左右1,2,3,4
	body[0].x = 10;
	body[0].y = 10;
}

CSnake::~CSnake()//析构函数
{
}
//设定蛇的运动
void CSnake::UpMove()
{
	for (int i = len - 1; i > 0; --i) {
		body[i].x = body[i - 1].x;
		body[i].y = body[i - 1].y;
	}
	body[0].x--;
	direc = 1;
}

void CSnake::DownMove()
{
	for (int i = len - 1; i > 0; --i) {
		body[i].x = body[i - 1].x;
		body[i].y = body[i - 1].y;
	}
	body[0].x++;
	direc = 2;
}

void CSnake::LeftMove()
{
	for (int i = len - 1; i > 0; --i) {
		body[i].x = body[i - 1].x;
		body[i].y = body[i - 1].y;
	}
	body[0].y--;
	direc = 3;
}

void CSnake::RightMove()
{
	for (int i = len - 1; i > 0; --i) {
		body[i].x = body[i - 1].x;
		body[i].y = body[i - 1].y;
	}
	body[0].y++;
	direc = 4;
}

void CSnake::init()
{
	body[0].x = 10;
	body[0].y = 10;
	len = 1;
	direc = 4;
}

  1. 蛇的初始化:
    蛇的初始化这部分内容是初始化整个游戏,相当于重新开始游戏。
void CMfcSnakeDlg::SnackInit()
{
	// 游戏区
	CDC* pdc = GetDlgItem(IDC_game)->GetWindowDC();

	// 棋盘初始化
	CBrush* pOldBrs = pdc->SelectObject(&m_brush[3]);
	CPen* pOldPen = pdc->SelectObject(&pen1);
	for (int i = 0; i < 30; i++) {
		for (int j = 0; j < 38; j++) {
			m_map[i][j].left = 0 + j * 20;//map是一个矩形框
			m_map[i][j].right = 20 + j * 20;
			m_map[i][j].top = 0 + i * 20;
			m_map[i][j].bottom = 20 + i * 20;
			pdc->SelectObject(&m_brush[3]);
			pdc->Rectangle(m_map[i][j]);//让画刷把框画满
			//pdc->SelectObject(&pen1);//用画笔把画框的周围上色,避免周围没上色
			//pdc->Rectangle(m_map[i][j]);
		}
	}
	pdc->SelectObject(&pOldBrs);//切换回原来的画刷

	// 蛇初始化
	my_snake.init();
	score = 0;
	START = false;
	DEAD = false;
	srand((unsigned)time(NULL));
	drawSnake();
	setBean();//设置豆子位置


	// 控件初始化
	EDIT_Score.SetWindowTextW(_T("0"));
	SetDlgItemText(IDC_BUTTON_Start, _T("start"));
}
  1. 蛇身增长:
    蛇身增长主要是通过画刷和循环来完成(并且要保证蛇身不能出边框),并实时展现在屏幕上。
void CMfcSnakeDlg::drawSnake(){
	CDC* pdc = GetDlgItem(IDC_game)->GetWindowDC();//修改内容展示在游戏区屏幕上
	CBrush* pOldBrs = pdc->SelectObject(&m_brush[0]);
	for (int i = 1; i < my_snake.len; i++) {//通过循环来增长蛇身
		pdc->SelectObject(&m_brush[0]);
		pdc->Rectangle(m_map[my_snake.body[i].x][my_snake.body[i].y]);
		pdc->SelectObject(&pen1);
		pdc->Rectangle(m_map[my_snake.body[i].x][my_snake.body[i].y]);
	}
	pdc->SelectObject(&m_brush[1]);
	pdc->Rectangle(m_map[my_snake.body[0].x][my_snake.body[0].y]);
	pdc->SelectObject(&pen1);
	pdc->Rectangle(m_map[my_snake.body[0].x][my_snake.body[0].y]);
	pdc->SelectObject(&pOldBrs);
	pdc->DeleteDC();//画刷用完删除
}
  1. 蛇吃豆子:
    蛇吃完豆子之后蛇身要增长,同时还要立刻生成一个新的豆子,并且还要更新游戏界面上的分数。
void CMfcSnakeDlg::eatBean()
{
	if (pos.x == my_snake.body[0].x && pos.y == my_snake.body[0].y) {//当头和豆子的位置是一样的
		my_snake.len++;//蛇的长度加加
		setBean();//重新画一个豆子
		score = score + (abs(pos.x - my_snake.body[0].x) + abs(pos.y - my_snake.body[0].y));
		CString str;
		str.Format(_T("%d"), score);
		EDIT_Score.SetWindowTextW(str);//在编辑框中更新分数
	}
}
  1. 蛇的移动:
    蛇身的移动包括上下左右,由于是蛇的移动,因此主程序还要调用类Snake的内容。主程序的作用主要是将该操作展现在游戏界面,真正动作执行是在Snake类。
void CMfcSnakeDlg::MvUp()//上移
{
	CDC* pdc = GetDlgItem(IDC_game)->GetWindowDC();
	// 将最后一个恢复背景色
	CBrush* pOldBrs = pdc->SelectObject(&m_brush[3]);
	pdc->Rectangle(m_map[my_snake.body[my_snake.len - 1].x][my_snake.body[my_snake.len - 1].y]);
	pdc->SelectObject(&pen1);
	pdc->Rectangle(m_map[my_snake.body[my_snake.len - 1].x][my_snake.body[my_snake.len - 1].y]);
	pdc->SelectObject(pOldBrs);
	my_snake.UpMove();
	drawSnake();
}

void CMfcSnakeDlg::MvDown()//下移
{
	CDC* pdc = GetDlgItem(IDC_game)->GetWindowDC();
	CBrush* pOldBrs = pdc->SelectObject(&m_brush[3]);
	pdc->Rectangle(m_map[my_snake.body[my_snake.len - 1].x][my_snake.body[my_snake.len - 1].y]);
	pdc->SelectObject(&pen1);
	pdc->Rectangle(m_map[my_snake.body[my_snake.len - 1].x][my_snake.body[my_snake.len - 1].y]);
	pdc->SelectObject(pOldBrs);
	my_snake.DownMove();
	drawSnake();
}

void CMfcSnakeDlg::MvLeft()//左移
{
	CDC* pdc = GetDlgItem(IDC_game)->GetWindowDC();
	CBrush* pOldBrs = pdc->SelectObject(&m_brush[3]);
	pdc->Rectangle(m_map[my_snake.body[my_snake.len - 1].x][my_snake.body[my_snake.len - 1].y]);
	pdc->SelectObject(&pen1);
	pdc->Rectangle(m_map[my_snake.body[my_snake.len - 1].x][my_snake.body[my_snake.len - 1].y]);
	pdc->SelectObject(pOldBrs);
	my_snake.LeftMove();
	drawSnake();
}

void CMfcSnakeDlg::MvRight()//右移
{
	CDC* pdc = GetDlgItem(IDC_game)->GetWindowDC();
	CBrush* pOldBrs = pdc->SelectObject(&m_brush[3]);
	pdc->Rectangle(m_map[my_snake.body[my_snake.len - 1].x][my_snake.body[my_snake.len - 1].y]);
	pdc->SelectObject(&pen1);
	pdc->Rectangle(m_map[my_snake.body[my_snake.len - 1].x][my_snake.body[my_snake.len - 1].y]);
	pdc->SelectObject(pOldBrs);
	my_snake.RightMove();
	drawSnake();
}
  1. 蛇是否死亡
bool CMfcSnakeDlg::checkLive()
{
	// 判断是否出界
	if (my_snake.body[0].x < 0 || my_snake.body[0].x >= 30 || my_snake.body[0].y < 0 || my_snake.body[0].y >= 38) return false;
	// 判断是否撞到了自己
	for (int i = 1; i < my_snake.len; i++) {
		if (my_snake.body[0].x == my_snake.body[i].x && my_snake.body[0].y == my_snake.body[i].y) return false;
	}
	return true;
}

用户界面的功能展示:

  1. Stop按键
    点击完Stop按键之后,游戏直接结束。
void CMfcSnakeDlg::OnBnClickedButtonStop()
{
	// TODO: 在此添加控件通知处理程序代码
	// 重新开始
	gameOver();
	SnackInit();
	this->GetDlgItem(IDC_game)->SetFocus();
}
  1. Start按键
    Start按键处除了右开始游戏的功能,还有终止游戏的功能,因此,点击该按键要判断是蛇死之后重新开始游戏。如果是游戏中点击,则会出现pause。
void CMfcSnakeDlg::OnBnClickedButtonStart()
{
	// TODO: 在此添加控件通知处理程序代码
	CString start, pause;
	start = "start";
	pause = "pause";
	if (DEAD == false) {
		if (START) {
			START = false;//如果已经开始了,则关掉
			KillTimer(1);//关掉计时器
			SetDlgItemText(IDC_BUTTON_Start, start);
		}
		else {
			if (INIT) {//判断是否为第一次
				INIT = false;
				SnackInit();//初始化为第一次(这个是为了界面面框的一个美观)
			}
			START = true;//重新开始
			SetTimer(1, speed, NULL);
			SetDlgItemText(IDC_BUTTON_Start, pause);
		}
		this->GetDlgItem(IDC_game)->SetFocus();
	}
}
  1. 游戏结束
    关闭计时器,结束游戏。
void CMfcSnakeDlg::gameOver()
{
	KillTimer(1);//关闭计时器
	START = false;//START不在运行
}

成果展示

本次实验实现了贪食蛇的一下功能:

  1. 屏幕上随机出现一个豆子, 上下左右控制 “蛇 ”的移动吃到 豆子 ”以后 “蛇 ”的身体加长一点。
  2. “蛇 ”碰到边界 或蛇头与蛇身相撞 蛇死亡, 游戏结束。
  3. 界面展示有游戏界面,游戏结束界面。有开始键、暂停键和停止退出的选项。
  4. 对蛇吃到豆子进行分值计算,可以设置游戏速度。
    视频展示:
    在这里插入图片描述
    本次学习参考视频:添加链接描述
  • 7
    点赞
  • 82
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值