IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 小白友好——C++基于EeayX简单开发的豪华版贪吃蛇[单人模式+双蛇对战+闯关模式(地图跳转)+排行榜+音乐] -> 正文阅读

[C++知识库]小白友好——C++基于EeayX简单开发的豪华版贪吃蛇[单人模式+双蛇对战+闯关模式(地图跳转)+排行榜+音乐]

注意:该项目源码开源,在本文章最底下链接处获取,源码中附有大量注释。

游戏展示

单人模式

在这里插入图片描述

斗蛇模式

在这里插入图片描述

闯关模式

闯关模式总地图界面:
在这里插入图片描述
闯关模式介绍信息:
在这里插入图片描述

地图C:
在这里插入图片描述
地图D:
在这里插入图片描述
地图B:在这里插入图片描述

地图A:在这里插入图片描述
由于该游戏是有点难度的(作者无法到达后续地图),所以本文章只展现四张游戏地图,后面还有四张地图,感兴趣的小伙伴可以试试通关哦!

项目代码解析

一、整体框架结构

在这里插入图片描述

二、界面代码解析

1.主程序

窗口参数为宏定义

int main() {
	initgraph(WIDTH, HEIGHT, SHOWCONSOLE);//设置窗口
	welcomeToGame();//进入主菜单
	return 0;
}

2.主菜单

主菜单界面

主菜单背景绘制

	IMAGE img;
	loadimage(&img, "./image/snake.jpeg", 1020, 770);//背景图片
	putimage(0, 0, &img);

状态码设置

按钮具体用哪一套、音乐播放哪一个都是根据界面的状态码进行判断的,按钮点亮是根据按钮状态码的改变而设置的

void welcomeToGame() {
	button_event = -1; //设置状态码
	ui_event = 1;
	...
}

音乐播放

根据音乐的状态码播放,播放前要先关闭之前的音乐,并且一次打开须对应一次关闭

	//播放音乐   
	if (music_status == 1)
	{
		mciSendString("close ./music/win.mp3", NULL, 0, NULL);//关闭游戏结束成功界面音乐
		mciSendString("close ./music/score.mp3", NULL, 0, NULL);//关闭游戏加分的音乐
		mciSendString("close ./music/button.mp3", NULL, 0, NULL);//关闭按钮界面音乐
		mciSendString("open ./music/bk.mp3", 0, 0, 0);//打开背景音乐
		mciSendString("play ./music/bk.mp3 repeat", 0, 0, 0); //播放背景音乐并循环
	}

按钮设置

将按钮的区域进行了封装,便与以后使用保证代码形式简洁。

页面文字设置:
1.文字背景设置透明,否则文字所在的矩形有个白底
2.居中显示

	//RECT来封装按钮区域,R1对应“开始游戏”按钮所在矩形方框
	RECT R1 = { r[0][0],r[0][1],r[0][2],r[0][3] };//R1对应“开始游戏”按钮所在的矩形方框
	RECT R2 = { r[1][0],r[1][1],r[1][2],r[1][3] };//R2对应“排行榜”按钮所在的矩形方框
	RECT R3 = { r[2][0],r[2][1],r[2][2],r[2][3] };//R3对应“游戏设置”按钮所在的矩形方框
	RECT R4 = { r[3][0],r[3][1],r[3][2],r[3][3] };//R4对应“帮助设置”按钮所在的矩形方框
	RECT R5 = { r[4][0],r[4][1],r[4][2],r[4][3] };//R5对应“退出设置”按钮所在的矩形方框

	IMAGE img;
	loadimage(&img, "./image/snake.jpeg", 1020, 770);
	putimage(0, 0, &img);

	//按钮
	setbkmode(TRANSPARENT);                //文本透明
	settextstyle(40, 20, _T("方正粗黑宋简体"));
	settextcolor(RGB(255, 128, 64));
	drawtext("开始游戏", &R1, DT_CENTER | DT_VCENTER | DT_SINGLELINE);//在矩形区域R1内输入文字,水平居中,垂直居中,单行显示
	settextcolor(RGB(0, 162, 232));
	drawtext("排行榜", &R2, DT_CENTER | DT_VCENTER | DT_SINGLELINE);//在矩形区域R2内输入文字,水平居中,垂直居中,单行显示
	settextcolor(RGB(63, 72, 204));
	drawtext("游戏设置", &R3, DT_CENTER | DT_VCENTER | DT_SINGLELINE);//在矩形区域R3内输入文字,水平居中,垂直居中,单行显示
	settextcolor(RGB(255, 0, 255));
	drawtext("帮助", &R4, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
	settextcolor(RGB(255, 242, 0));
	drawtext("退出游戏", &R5, DT_CENTER | DT_VCENTER | DT_SINGLELINE);

	//标题
	setbkmode(TRANSPARENT);                //文本透明
	settextcolor(RGB(185, 122, 87));
	settextstyle(40, 20, _T("方正粗黑宋简体"));
	outtextxy(418, 90, "蛇蛇大作战");

获取鼠标信息

鼠标信息的数据类型ExMessage
只要页面不跳转就会以制获取,死循环套上

	//获取鼠标信息
	ExMessage m;
	while (1) {
		m = getmessage();//获取一条鼠标信息
		...
	}

鼠标移动信息:

buttonJudge(m.x, m.y)对当前鼠标位置信息进行判断,返回鼠标所在哪一按钮区域或不在按钮上。
button_event记录当前按钮的状态,点亮与否

第一个if是判断鼠标是否在按钮上,若是则进行第二次if判断,判断是否是和上一轮为同一个按钮,如果同一个按钮不能再次点亮了,因为再次点亮会导致熄灭。如果不是和上一轮循环的同一个按钮,那就将当前按钮点亮。

另外,一旦鼠标离开了按钮,同时当前有某个按钮亮着,则要把按钮再次点亮即熄灭掉。执行一轮之后,刷新鼠标信息,再次判断。

case WM_MOUSEMOVE: //这一段点亮图标会用即可,看不懂也没关系
			setrop2(R2_XORPEN);
			setlinecolor(LIGHTRED);//线条颜色为亮青色
			setlinestyle(PS_SOLID, 3);//设置画线样式为实现,10磅
			setfillcolor(WHITE);//填充颜色为白色
			if (buttonJudge(m.x, m.y) != -1) {//如果鼠标在某一个按钮区域内
				if (button_event != buttonJudge(m.x, m.y)) {
					button_event = buttonJudge(m.x, m.y);
					fillrectangle(r[button_event][0], r[button_event][1], r[button_event][2], r[button_event][3]);//点亮按钮
				}
			}
			else if (button_event != -1) {
				fillrectangle(r[button_event][0], r[button_event][1], r[button_event][2], r[button_event][3]);//鼠标离开了按钮,要恢复按钮颜色
				button_event = -1;
			}
			flushmessage(EM_MOUSE);
			break;

鼠标左键信息

按下瞬间,用异或(画了存活20s就死)方式画环。
setrop2(R2_NOTXORPEN);//二元光栅——NOT(屏幕颜色 XOR 当前颜色)
for (int i = 0; i <= 10; i++)
{
	setlinecolor(RGB(25 * i, 25 * i, 25 * i));//设置圆颜色
	circle(m.x, m.y, 2 * i);
	Sleep(20);//停顿30ms
	circle(m.x, m.y, 2 * i);//抹去刚刚画的圆
}
注意事项:
  1. 播放某音乐之前一定要先关掉要播放的音乐,确保音乐可播放,这里先关一次,然后打开音乐->播放音乐
  2. 根据鼠标左键点击的位置跳转到相应的界面函数
    没点到按钮就清空鼠标信息再来

关键:音乐只有在处于关闭状态或从未打开过的状态下去打开才能正确播放,如果仅仅是一次播放完了,再次打开是不会播放的。另外对于模式难度的设置就是把速度值直接进行更改

排行榜、帮助、设置、选择游戏界面:

思路基本上和主菜单一致

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

退出按钮:

会调用```exit()```函数退出程序

游戏结束页面:

基本上也和上边一样,主要不同在于保存分数的设计

在这里插入图片描述

闯关模式界面和闯关模式里的详细信息界面、界面设置也都与前面设计一样

三、游戏代码解析

涉及到的对象:蛇,坐标,食物,障碍物,地图。

  1. 蛇类:
    这里用链表储存蛇的每一节的坐标
class Snake { //蛇类
public:
	Snake(int length,char direction = left, int player = 0, int grade = 0,bool is_dead=false) 
	:length(length),direction(direction), player(player), grade(grade),is_dead(is_dead) {};
	char direction;//蛇的方向
	std::list<coor*> xy; //链表存储蛇的身体信息
	int length;
	int player; //用来表示是玩家一还是玩家二(玩家一设置为1,玩家二设置为2)
	int grade;//蛇的游戏分数
	int is_dead;//死亡
};
  1. 食物类:用二维数组来表示位置
  2. 墙壁类:用二维数组表示位置
  3. 地图类:记录当前地图的下一地图属性
  4. 坐标类:两个整型记录坐标

涉及操作:

  1. 初始化蛇
    根据游戏模式状态码,对蛇初始位置及蛇长度进行初始化
void initSnake(Snake& s) //初始化蛇
{

	//if (!s.xy.empty())
	//	s.xy.clear();
	if (game_status == 1) {
		s.length = S_LEN;//赋予蛇蛇长
		for (int i = 0; i < s.length; ++i) {
			s.xy.push_back(new coor(p11[i][0], p11[i][1]));
			/*std::cout << c[i][0] << " " << c[i][1] << std::endl;*/
		}
	}
	else if (game_status == 3) {
		s.length = S_LEN_C;//赋予蛇蛇长
		for (int i = 0; i < S_LEN_C; ++i) {
			coor* c = new coor(p31[i][0], p31[i][1]);
			s.xy.push_back(c);
			/*s.xy.push_back(new coor(p21[i][0], p21[i][1]));*/
			/*std::cout << c[i][0] << " " << c[i][1] << std::endl;*/
		}
	}

}
//斗蛇模式初始化
void game2InitSnake(Snake& s1, Snake& s2) {
	if (!s1.xy.empty()) {
		s1.xy.clear();
	}
	if (!s2.xy.empty()) {
		s2.xy.clear();
	}
	for (int i = 0; i < S_LEN; ++i) {
		s1.xy.push_back(new coor(p21[i][0], p21[i][1]));
		s2.xy.push_back(new coor(p22[i][0], p22[i][1]));
		/*std::cout << c[i][0] << " " << c[i][1] << std::endl;*/
	}
}
  1. 初始化食物
    初始化食物坐标,并改变坐标状态。同时需要对坐标进行位置判断,如果是障碍物、蛇或食物需要重新生成。
void initFood(Snake& s, Item& m_item)
{
	//生成自由模式需要的食物生成
	if (game_status == 1) { //游戏模式为自由模式
		srand((unsigned)time(NULL));//随机数种子
		int x = (rand() % ((WIDTH-1) / 10)) * CUBE;
		int y = (rand() % ((HEIGHT-1) / 10)) * CUBE;
		for (auto it = s.xy.begin(); it != s.xy.end(); ++it) {
			//如果生成食物和蛇身体重合,或者此坐标已经分配给了一个食物。则重新为食物分配一个坐标
			if (x == (*it)->x || y == (*it)->y || item.food[x][y] == 1 || x == 0 || x == 1020 || y == 0 || y == 770) {
				it = s.xy.begin();
				 x = (rand() % ((WIDTH - 1) / 10)) * CUBE;
				 y = (rand() % ((HEIGHT - 1) / 10)) * CUBE;
			}
		}
		m_item.food[x][y] = 1;
	}
	else if (game_status == 3) { //如果游戏模式为闯关模式
	...
	}
}
  1. 画蛇
    蛇头和身体分开画,遍历就完事了
void initSnake(Snake& s) //初始化蛇
{
	if (game_status == 1) {
		s.length = S_LEN;//赋予蛇蛇长
		for (int i = 0; i < s.length; ++i) {
			s.xy.push_back(new coor(p11[i][0], p11[i][1]));
			/*std::cout << c[i][0] << " " << c[i][1] << std::endl;*/
		}
	}
	else if (game_status == 3) {
		s.length = S_LEN_C;//赋予蛇蛇长
		for (int i = 0; i < S_LEN_C; ++i) {
			coor* c = new coor(p31[i][0], p31[i][1]);
			s.xy.push_back(c);
			/*s.xy.push_back(new coor(p21[i][0], p21[i][1]));*/
			/*std::cout << c[i][0] << " " << c[i][1] << std::endl;*/
		}
	}
}
  1. 画食物
    遍历整个地图,如果当前状态有食物就画矩形。
//打印食物
void printFood(Item& m_item) {//打印食物
	for (int i = 1; i < 1020; i++) {
		for (int j = 1; j < 770; j++)
		{
			if (m_item.food[i][j] == 1) {
				setfillcolor(YELLOW);
				fillrectangle(i, j, i + CUBE, j + CUBE);
			}
		}
	}
}
  1. 蛇移动
    首先是由键盘按键改变蛇的方向,再由方向进行移动。同时需要对蛇的下一个位置是否有食物进行判断,若吃到食物则更新相应分数长度数据,并将蛇链表伸长。
//蛇的移动
void snakeMove(Snake& s, Item& m_item)//刷新蛇头的坐标位置
{
	int x, y;//声明蛇头坐标
	coor* head = new coor();
	switch (s.direction) {
	case up:
		x = s.xy.front()->x;//蛇头坐标
		y = s.xy.front()->y - CUBE;
		if (m_item.food[x][y] == 1) {
			//蛇的坐标的x与食物的x相同不代表吃到食物,要x与y同时一直才代表与食物重叠
			head->x = x;
			head->y = y;//给蛇头赋值
			s.xy.push_front(head);
			m_item.food[x][y] = 0;
			s.length++;
			if(game_status!=2)
			s.grade++;
		}
		else {//没有遇到食物
			head->x = x;
			head->y = y;
			s.xy.emplace_front(head);
			s.xy.pop_back();
		}
		break;
	case down:
		...
		break;
	case left:
		...
		break;
	case right:
		...
		break;
	}
}
  1. 死亡判断
    超越边界、撞墙:坐标判断
    沿着墙走的地图:坐标判断
    吃到自己:蛇头对蛇身进行一一比对,遍历看坐标

  2. 终点判断
    坐标判断,并对界面进行切换:利用地图类切换地图,改变当前地图状态码。定方向和初始位置。

  3. 食物判断有无
    对地图进行遍历,然后看该位置食物的状态。

//判断场上是否有食物
bool ifHaveFood(Item& m_item)
{
	for (int i = 0; i < 1021; i++) {
		for (int j = 0; j < 771; j++) {
			if (m_item.food[i][j] == 1) {
				return true;
			}
		}
	}
	return false;

}
  1. 斗蛇胜负判断
    和死亡判断一样,有一个死后,本轮结束,对分数进行更新。
    直到有一个人分数超过5,整个游戏结束。
  2. 地图绘制
    封装画图函数,然后根据传入坐标,画出对应的矩形圆形斜线
    同时画之后要把相应坐标下的状态更改,以便于死亡判断和食物生存。
    然后将画的所有地图封装到一个函数里。

四、游戏模式流程

1、不同游戏模式要设置模式的状态码,以便于根据状态码进行初始化,画图等操作。因为把接口都给统一了,需要通过状态码进行判断。
2、主体就是while循环加上sleep和不断的界面刷新实现游戏运行也称之为GameLoop(游戏循环)

项目源码下载:

源码下载

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-10-07 13:40:26  更:2021-10-07 13:41:40 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/29 18:37:29-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计