小白友好——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(游戏循环)

项目源码下载:

源码下载

提取码:3npu

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值