c++,easyx,vs,贪吃蛇(曼巴)游戏

前言:

 由于网上有很多贪吃蛇的框架,我们基本不用自己写,只需找到符合自己预期的框架,充分理解后复制粘贴过来为己所用就好了,我的框架来源:用C++实现经典的贪吃蛇游戏_c++贪吃蛇-CSDN博客

但是,我觉得这框架太简单了,虽然能玩,但也只是能玩的地步,而且这个游戏它没有主题,不行,我要赋予它一个主题,刚好我喜欢牢大,牢大又名黑曼巴,干脆就来个贪吃曼巴吧!(本人没有对曼巴的任何不尊敬,本文的曼巴仅指蛇,与篮球运动员科比并无干系)

首先我们要把框架复制粘贴进我们的项目里,并进行充分的理解,然后再加入我们自己的部分,大部分想理解都不难,我就简单说说比较难看懂的部分。

曼巴的移动

首先理清思路,在这个结合easyx的游戏中,蛇的移动是我们每时每刻都依据蛇的坐标把蛇画出来,下一刻再清理掉画面,更新坐标,再把蛇画出来,这样的循环,看起来就是蛇在移动,跟动画是一个原理。

SnakeNode tmp[MAXLEN];	//用另外一个数组来存储蛇原来的位置
 
//移动
void Snake::Move() {
	//将原来的蛇结点拷贝一份
	for (int i = 0; i < this->length; i++) {
		tmp[i].x = this->node[i].x;
		tmp[i].y = this->node[i].y;
	}
	int status = 0;//用来判断是否点击了转向按键
	if (this->dirt == RIGHT) {
		//判断是否转向
		if (GetAsyncKeyState('W') && status == 0) {
			//this->node[0].y -= SIZE;
			this->dirt = UP;
			status = 1;
		}
		else if (GetAsyncKeyState('S') && status == 0) {
			this->dirt = DOWN;
			status = 1;
		}
		else {
			this->node[0].x += SIZE;
		}
	}
	if (this->dirt == DOWN) {
		//判断是否转向
		if (GetAsyncKeyState('A') && status == 0) {
			//this->node[0].x -= SIZE;
			this->dirt = LEFT;
			status = 1;
		}
		else if (GetAsyncKeyState('D') && status == 0) {
			this->node[0].x += SIZE;
			this->dirt = RIGHT;
			status = 1;
		}
		else {
			this->node[0].y += SIZE;
		}
	}
	if (this->dirt == LEFT) {
		//判断是否转向
		if (GetAsyncKeyState('W') && status == 0) {
			//this->node[0].y -= SIZE;
			this->dirt = UP;
			status = 1;
		}
		else if (GetAsyncKeyState('S') && status == 0) {
			this->node[0].y += SIZE;
			this->dirt = DOWN;
			status = 1;
		}
		else {
			this->node[0].x -= SIZE;
		}
	}
	if (this->dirt == UP) {
		//判断是否转向
		if (GetAsyncKeyState('A') && status == 0) {
			this->node[0].x -= SIZE;
			this->dirt = LEFT;
			status = 1;
		}
		else if (GetAsyncKeyState('D') && status == 0) {
			this->node[0].x += SIZE;
			this->dirt = RIGHT;
			status = 1;
		}
		else {
			this->node[0].y -= SIZE;
		}
	}
	//移动
	for (int i = 1; i < this->length; i++) {
		this->node[i].x = tmp[i - 1].x;
		this->node[i].y = tmp[i - 1].y;
	}
	Sleep(speed);
}

问题1 (转向)

 在以上框架中,我产生了第一个疑惑,为什么同是检查是否按下转向按键,有的蛇头坐标更新,有的不更新呢?后来仔细一看发现,原作者用来判断按下按键的语句都是if语句,这意味着如果第一个if判断结束后仍会进入第二个if,如果按了转向,蛇头坐标改变,后面又会进入其他if判断语句,容易出现混乱,所以,在这里我做了一些修改如下。(把if改成else if)

void Snake::Move() {
	//将原来的蛇结点拷贝一份
	for (int i = 0; i < this->length; i++) {
		tmp[i].x = this->node[i].x;
		tmp[i].y = this->node[i].y;
	}
	int status = 0;//用来判断是否点击了转向按键
	int flag = 0;
	if (this->dirt == RIGHT) {
		//判断是否转向
		if (GetAsyncKeyState('W') && status == 0) {
			this->node[0].y -= 2 * SIZE;
			this->dirt = UP;
			status = 1;
		}
		else if (GetAsyncKeyState('S') && status == 0) {
			this->node[0].y += 2 * SIZE;
			this->dirt = DOWN;
			status = 1;
		}
		else {
			if (GetAsyncKeyState('A') && status == 0)
				flag -= 10;
			if (GetAsyncKeyState('D') && status == 0)
				flag += 10;
			this->node[0].x += 2 * SIZE;
		}
	}
	else if (this->dirt == DOWN) {
		//判断是否转向
		if (GetAsyncKeyState('A') && status == 0) {
			this->node[0].x -= 2 * SIZE;
			this->dirt = LEFT;
			status = 1;
		}
		else if (GetAsyncKeyState('D') && status == 0) {
			this->node[0].x += 2 * SIZE;
			this->dirt = RIGHT;
			status = 1;
		}
		else {
			if (GetAsyncKeyState('W') && status == 0)
				flag -= 10;
			if (GetAsyncKeyState('S') && status == 0)
				flag += 10;
			this->node[0].y += 2 * SIZE;
		}
	}
	else if (this->dirt == LEFT) {
		//判断是否转向
		if (GetAsyncKeyState('W') && status == 0) {
			this->node[0].y -= 2 * SIZE;
			this->dirt = UP;
			status = 1;
		}
		else if (GetAsyncKeyState('S') && status == 0) {
			this->node[0].y += 2 * SIZE;
			this->dirt = DOWN;
			status = 1;
		}
		else {
			if (GetAsyncKeyState('D') && status == 0)
				flag -= 10;
			if (GetAsyncKeyState('A') && status == 0)
				flag += 10;
			this->node[0].x -= 2 * SIZE;
		}
	}
	else if (this->dirt == UP) {
		//判断是否转向
		if (GetAsyncKeyState('A') && status == 0) {
			this->node[0].x -= 2 * SIZE;
			this->dirt = LEFT;
			status = 1;
		}
		else if (GetAsyncKeyState('D') && status == 0) {
			this->node[0].x += 2 * SIZE;
			this->dirt = RIGHT;
			status = 1;
		}
		else {
			if (GetAsyncKeyState('S') && status == 0)
				flag -= 10;
			if (GetAsyncKeyState('W') && status == 0)
				flag += 10;
			this->node[0].y -= 2 * SIZE;
		}
	}
	//移动
	for (int i = 1; i < this->length; i++) {
		this->node[i].x = tmp[i - 1].x;
		this->node[i].y = tmp[i - 1].y;
	}
	Sleep(speed - flag * 15);
}

曼巴的诞生

蛇的移动函数搞懂了,接下来就看蛇身的绘制,在原代码中,蛇身是正方形的,而且还会变色,但是黑曼巴应该是黑色的,而且我也不喜欢正方形的蛇身,所以我用把蛇身画成圆的,并且设置为黑色。

//绘制蛇
void Snake::Draw() {
	cleardevice();//清空原先的绘图
	putimage(0, 0, &bk);
	settextcolor(BLACK);
	char  scorestr[2000], twostr[1000], threestr[1000], str[8];
	sprintf_s(twostr, "二分球:%2d", two);
	sprintf_s(threestr, "三分凉:%2d", three);
	sprintf_s(scorestr, "总得分:%2d", score);
	outtextxy(900, 90, scorestr);
	outtextxy(900, 70, threestr);
	outtextxy(900, 50, twostr);
	srand((unsigned)time(NULL));//设置随机数种子
	for (int i = 0; i < this->length; i++) {
		setfillcolor(BLACK);
		fillcircle(this->node[i].x, this->node[i].y, SIZE);
	}
}

曼巴碰撞判断

问题2(蛇身坐标)

但是圆形蛇身就意味这蛇的碰撞检测得重做了,因为对于正方形来说,左上角的点是这个正方形的坐标,由于食物也是正方形的,只需判断两者左上角的坐标是否重合即可,但是对于圆形蛇身和圆形食物来说,圆心才是坐标,如果简单判断坐标重合,就会出现吃到食物会有视觉延迟的情况。因此我们需要创造一个判断碰撞的函数coli(float x0,float y0) 

//创建食物的类
class Food
{
	friend class Snake;         //食物的友元为蛇
	friend class Block;
public:
	Food(Snake& snake, Block& block);			//食物初始化
	void Draw();				//绘制食物
	bool coli(float x0, float y0)//碰撞判断
	{
		if (x0 == x && y0 == y) return true;
		if (x0 == x + 2 * SIZE && y0 == y) return true;
		if (x0 == x + 4 * SIZE && y0 == y) return true;
		if (x0 == x && y0 == y + 2 * SIZE) return true;
		if (x0 == x && y0 == y + 4 * SIZE) return true;
		if (x0 == x + 2 * SIZE && y0 == y + 2 * SIZE) return true;
		if (x0 == x + 4 * SIZE && y0 == y + 2 * SIZE) return true;
		if (x0 == x + 2 * SIZE && y0 == y + 4 * SIZE) return true;
		if (x0 == x + 4 * SIZE && y0 == y + 4 * SIZE) return true;
		return false;
	}
private:
	int x, y;					//坐标
	int score;					//分数
	int type;
	IMAGE ima1, ima2, ima3, ima4;
};

函数传入蛇头的坐标x0,y0,只要蛇头碰到食物周围的八个点之一,就返回碰撞。

(ps:我试过直接用食物所在区间来判断,但是结果不理想,搞不清楚为啥。)

接下来我们在食物判定和死亡判定中用这个coli()函数来判断是否与食物(食物判断),自身,墙体(死亡判断),障碍物发生碰撞即可,但要注意,由于死亡判断是snake类中的函数,食物判断是food类中函数,所以我们要分别在两个类中都定义coli()函数,但是,考虑到后面我还会改变障碍物的形状,图案和数量等复杂操作,我就另起了一个障碍物类block,并在其中定义coli()函数。

bool coli(int x0, int y0)//碰撞判定
	{
		for (int i = 0; i < count; i++)
		{
			int x = vc[i][0];
			int y = vc[i][1];
			if (x0 == x && y0 == y) return true;
			if (x0 == x + 2 * SIZE && y0 == y) return true;
			if (x0 == x + 4 * SIZE && y0 == y) return true;
			if (x0 == x && y0 == y + 2 * SIZE) return true;
			if (x0 == x && y0 == y + 4 * SIZE) return true;
			if (x0 == x + 2 * SIZE && y0 == y + 2 * SIZE) return true;
			if (x0 == x + 4 * SIZE && y0 == y + 2 * SIZE) return true;
			if (x0 == x + 2 * SIZE && y0 == y + 4 * SIZE) return true;
			if (x0 == x + 4 * SIZE && y0 == y + 4 * SIZE) return true;
		}
		return false;
	}
//失败判定
bool Snake::Defeat(Block& block) {
	//1.碰到边界
	if (this->node[0].x < 0 || this->node[0].x >= WIDTH || this->node[0].y < 0 || this->node[0].y >= HEIGHT) {
		return true;
	}
	//2.碰到自己的身体
	for (int i = 1; i < this->length; i++) {
		if (this->node[0].x == this->node[i].x && this->node[0].y == this->node[i].y) {
			return true;
		}
	}
	//3.被肘击
	if (block.coli(node[0].x, node[0].y))
	{
		Sleep(1500);
		if (GetAsyncKeyState('1')) {
			wuditime = 5;
			return false;
		}
		return true;
	}
	return false;
}

曼巴的食物 

接下来是食物的生成,我觉得原来的食物太单调了,我要把他们改为冰红茶和篮球!

网上搜到的照片不符合预期,那就自己画!然后通过easyx的函数导入到程序。

食物(想要多少种都可以自行添加,这里只演示两种):

肘子(障碍物):

背景:(我知道我画的很好)

哦对了,各种图片都按如下操作放入文件夹就好了

问题3(图案遮挡背景)

这里有个小插曲,在导入自定义图案时,由于图片是正方形的,会导致图案四周的背景被遮挡,总体上就不美观,我在csdn上找到了解决方法(可能要用到照片剪辑软件):

关于Easyx如何显示透明无背景贴图_easyx图片背景透明-CSDN博客

问题4(吃食物无反应)

//食物的初始化
Food::Food(Snake& snake, Block& block)
{
	loadimage(&ima1, "basketball.png", 4 * SIZE, 4 * SIZE);
	loadimage(&ima2, "baskrv.png", 4 * SIZE, 4 * SIZE);
	loadimage(&ima3, "bhc.png", 4 * SIZE, 4 * SIZE);
	loadimage(&ima4, "rv.png", 4 * SIZE, 4 * SIZE);
table:
	srand((unsigned)time(0));
	do
	{
		x = (rand() % (WIDTH / SIZE));
		y = (rand() % (HEIGHT / SIZE));
	} while (x % 2 == 0 || y % 2 == 0 || x >= WIDTH / 10 - 4 || y >= HEIGHT / 10 - 4 || block.onblock(x, y));//一直生成随机坐标直到该坐标不与障碍物重叠并且为能被蛇头碰到
	this->x *= SIZE;
	this->y *= SIZE;
	this->type = rand() % 100;
	if (type >= 0 && type <= 80) {
		type = 1;
	}
	else {
		type = 2;
	}
	for (int i = 0; i < snake.length; i++) {
		if (snake.node[i].x == this->x && snake.node[i].y == this->y) {
			goto table;
		}
	}
}

为什么看起来吃到了食物,但是没有任何反应

(注意:代码中之所以要求食物能被蛇头碰到,是因为蛇头为圆形,半径为10,而每次移动都会往要移动的方向画一个同样的圆,因此,每次蛇头移动的距离是20,所以其坐标变化为1,3,5,7,9,……,如果食物的坐标是2,4,6,……,就没法判定吃到食物,尽管从视觉上看起来是吃到了,因此我们限制食物的坐标必须是1,3,5,7,……此类规律。而且,由于食物图案是以左上角为坐标点,不能生成在右下边界上,否则食物图案就刷新在视野之外了)

//绘制食物
void Food::Draw() {
	if (type == 1)//篮球
	{
		putimage(this->x, this->y, &ima2, SRCAND);
		putimage(x, y, &ima1, SRCPAINT);
	}
	else if (type == 2)//冰红茶
	{
		putimage(x, y, &ima4, SRCAND);
		putimage(x, y, &ima3, SRCPAINT);
	}
}

如上,我们只需在对应的地方putimage就行。

//吃食物
int Snake::Eat(Food& food) {
	if (food.coli(node[0].x, node[0].y)) {
		if (food.type == 1) {
			two++;
			score += 2;
		}
		else if (food.type == 2) {
			three++;
			score += 3;
		}
		if (this->node[length - 1].x - this->node[length - 2].x == 0 && this->node[length - 1].y - this->node[length - 2].y == -20) {
			this->length++;
			this->node[length - 1].x = this->node[length - 2].x;
			this->node[length - 1].y = this->node[length - 2].y - 2 * SIZE;
		}
		if (this->node[length - 1].x - this->node[length - 2].x == 0 && this->node[length - 1].y - this->node[length - 2].y == 20) {
			this->length++;
			this->node[length - 1].x = this->node[length - 2].x;
			this->node[length - 1].y = this->node[length - 2].y + 2 * SIZE;
		}
		if (this->node[length - 1].x - this->node[length - 2].x == 20 && this->node[length - 1].y - this->node[length - 2].y == 0) {
			this->length++;
			this->node[length - 1].x = this->node[length - 2].x + 2 * SIZE;
			this->node[length - 1].y = this->node[length - 2].y;
		}
		if (this->node[length - 1].x - this->node[length - 2].x == -20 && this->node[length - 1].y - this->node[length - 2].y == 0) {
			this->length++;
			this->node[length - 1].x = this->node[length - 2].x - 2 * SIZE;
			this->node[length - 1].y = this->node[length - 2].y;
		}
		return true;
	}
	return false;
}

问题5(蛇尾长度增加)

对于这个部分,有的人可能开始会看不懂,首先我们判断蛇头碰撞到食物了,判断此时食物的种类,并更新相关的分数,对于下面四个又臭又长的if,看第一个,实际上就是通过蛇倒数两个节点来判断此时蛇尾部的总体走向,如果倒数第一和倒数第二个节点的x坐标相同,则可以知道蛇尾是在竖直方向,此时我们只需在判断y的坐标就知道它是向下运动还是向上运动,那我们也就知道新的尾节点坐标应该设在哪里了。(有时候看不懂代码为啥这么写,可以根据代码一行行模拟推测,整个过程完成后看起来就明朗多了。)

曼巴的手肘(障碍物) 

直接生成block类

class Block
{
	friend class Snake;
	friend class Food;
private:
	vector<vector<int>> vc;//用vector数组存放批量障碍物的坐标
	int x, y;
	int count;
	int difficulty;//难度越高,障碍物数量越多
	IMAGE b, bv;//障碍物的图案
public:
	Block(); //初始化8个障碍物
	void Draw(int num);//负责障碍物相关的绘画
	bool coli(int x0, int y0);//碰撞判定
	bool onblock(int x, int y);//判断一个东西是否与障碍物重叠
	void modify(int num);//改变障碍物数量
	void diff();//改变显示难度
};

问题6(障碍物生成在食物上) 

在游戏中,我们可以通过按h键增加障碍物的数量,但是由于要避免生成在食物上面,我原本想先生成食物再生成障碍物,但是这样一来就下一个食物就有可能生成在障碍物上,因为我是先初始化block类再初始化food类,并且将block类中障碍物的坐标传给食物类,让食物类来判断坐标是否重合,这样,每次重新生成食物时才能避免生成在障碍物上,因此我没法先生成食物再生成障碍物,能否让后面添加的障碍物不在食物上呢?可以,但是食物又必须在障碍物后面刷新,有了!每次我们按h提升难度,难度不是立马提升,而是等我们消灭掉现有的食物后,添加更多的障碍物再刷新食物,这样就能避免中途添加的障碍物生成在现有的食物上面了,这个办法也许不是最好的,肯定有更好的办法,但确实解决了眼前的问题,况且还算合理。

 问题7(障碍物生成在脸上)

问题真是一个接一个地跳出来,不过也好,我们正闲着没事干,击破几个问题涨涨士气。由上面视频可以看到,生成的障碍物离我们只有一步之遥,这样死了也太冤枉了吧!所以我索性开挂,吃到食物后曼巴直接无敌!设定无敌帧,其实就是曼巴在吃到食物时设定一个时间,在蛇的移动过程中递减,当时间殆尽时,恢复肉身。 这样吃到食物后,进入了金身,就算当面被肘击100次都没事!

if (snake.Eat(food)) {
				eat = 1;
				music.play_eatingsound();
				wuditime = 5;//无敌帧
				goto table2;
			}
snake.Move();
			wuditime--;

现在游戏已经大体完成了,看好了,接下来是画龙点睛,赋予灵魂之时。

曼巴之魂

music~

说到曼巴,遥远的天边仿佛飘来伤感的声音,我难以分辨究竟是天使的哭泣,还是恶魔的得意,于是我侧耳倾听,随着渺远的声音离我愈来愈近,我不禁心头一颤,接着我感到双腿无力,顿时坐到了地上。此时我才意识到,自始自终都是身体的求生本能,体内超量分布的肾上腺素在支撑着我早已被恶魔击穿的身体。我清楚地感受到它瞬间来到我的右后侧,贴在我的耳边,用低沉沙哑又未曾听闻的嗓音对我低语:see you again……此时我浑身颤抖地连连点头,我以为它走了,结果它右出现在我左后侧!“两倍速……”我第一次体会到如山一般的压力,压得我差点喘不过气来,迫于威压,我只得服从。。。

关于如何添加音频,我参考了这篇文章:

mciSendString函数简介(播放音乐以及录音相关操作)-CSDN博客

简单来说就是mcisendstring(“打开音乐”),然后再mcisendstring(“播放音乐”)。

后来朋友说打开的时候吓了一跳,音量太大了,我又添加了相应的音量调节功能。

void Music::bk_volumn()
{
	if (GetAsyncKeyState(38)) vol += 100;
	else if (GetAsyncKeyState(40)) vol -= 100;
	char com1[41];
	sprintf(com1, "setaudio BGM1 volume to %d", vol);
	mciSendString(com1, NULL, 0, NULL);
}
void Music::play_bkmusic()
{
	mciSendString("open syagp.mp3 alias BGM1", 0, 0, 0);//see you again加速版
	mciSendString("play BGM1 repeat", 0, 0, 0);
}

问题8(音量调节无响应) 

ps:我们需要先播放音乐再调节音量,调换顺序就无效了。’38‘和’40‘分别对应箭头上下键的ascii码,我们只需在Music类中初始化音量vol,然后循环执行音量调节函数,看是否检测到上下箭头键的输入,再调节vol就行了。

相同的道理也适用于吃到食物的音效和被肘击及死亡的音效。

void Music::play_eatingsound()
{
	mciSendString("close EAT", 0, 0, 0);
	mciSendString("open eatingsound.mp3 alias EAT", 0, 0, 0);
	mciSendString("play EAT", 0, 0, 0);
	if (GetAsyncKeyState(38)) vol += 100;
	else if (GetAsyncKeyState(40)) vol -= 100;
	char com2[41];
	sprintf(com2, "setaudio EAT volume to %d", vol);
	mciSendString(com2, NULL, 0, NULL);
	
}

void Music::play_colisound()
{
	mciSendString("close BGM1", 0, 0, 0);
	mciSendString("close BGM2", 0, 0, 0);
	mciSendString("open ah.mp3 alias BGM2", 0, 0, 0);//man!
	mciSendString("play BGM2", 0, 0, 0);
	if (GetAsyncKeyState(38)) vol += 100;
	else if (GetAsyncKeyState(40)) vol -= 100;
	char com3[41];
	sprintf(com3, "setaudio BGM2 volume to %d", vol);
	mciSendString(com3, NULL, 0, NULL);
}

void Music::play_endmusic()
{
	mciSendString("close BGM1", 0, 0, 0);
	mciSendString("open mambaout.mp3 alias END", 0, 0, 0);//mambaout
	mciSendString("play END", 0, 0, 0);
	if (GetAsyncKeyState(38)) vol += 100;
	else if (GetAsyncKeyState(40)) vol -= 100;
	char com4[41];
	sprintf(com4, "setaudio END volume to %d", vol);
	mciSendString(com4, NULL, 0, NULL);
}

问题9(播放音频无响应) 

注意:音乐不能只加载一次,而是每次都必须加载再播放,因为他们感情太好了,而且如果要播放下一个音频,如果不想冲突,或者想隔一段时间播放同一个音频,就必须把上一个音频关掉,要新建一段关系总得先分手嘛·—·  

退役

有的人不同意,说曼巴不会死亡只会受伤,所以我们就假定它每次都是受伤暂时退役,那就必须要有相应的动画来致敬,如下:

 左下角是个人臭美签名,因为不知道空白处放啥图片合适。

(如果你也想放自己的照片,可以利用一些照片剪辑软件,先在软件里调节各照片的大小和坐标,这样方便直接添加,否则每次都要运行程序看效果再调整照片大小坐标挺麻烦的)

菜单

有了结束画面,感觉没有菜单太简陋了,那就来做一个菜单Menu

菜单类的声明

class Menu
{
private:
	IMAGE menu_bk, detail_bk, history_bk, control_bk, respect_bk;
	MOUSEMSG m;
public:
	int goon = 1;
	Menu();
	void window();
	void showmenu();
	void showhistory();
	void showdetail();
	void showcontrol();
	void showrespect();
	bool goback();
	void showreturn();
};

绘制菜单和选项并判断鼠标信息

void Menu::showmenu()
{
menubegin:
	window();
	setbkcolor(WHITE);
	cleardevice();
	putimage(0, 0, &menu_bk);
	setfillcolor(WHITE);
	fillrectangle(200, 140, 600, 160);
	fillrectangle(200, 200, 600, 220);
	fillrectangle(200, 260, 600, 280);
	fillrectangle(200, 320, 600, 340);
	fillrectangle(200, 380, 600, 400);
	fillrectangle(200, 440, 600, 460);
	fillrectangle(200, 500, 600, 520);
	setbkmode(TRANSPARENT);
	settextcolor(BLACK);
	outtextxy(250, 142, "开始游戏");//goto begin
	outtextxy(250, 202, "历史得分榜");//goto history
	outtextxy(250, 262, "操作");//goto control
	outtextxy(250, 322, "游戏说明");//goto detail
	outtextxy(250, 382, "作者好帅");//就喜欢你这种有眼力的玩家
	outtextxy(250, 442, "作者好丑");//游戏崩溃
	outtextxy(250, 502, "mamba out");//退出游戏
	settextcolor(WHITE);
	while (1) {
		m = GetMouseMsg();
		if (m.x >= 200 && m.x <= 600 && m.y >= 140 && m.y <= 160) {//检测鼠标的位置 是否满足条件
			setlinecolor(YELLOW);//满足后 设置新的边框为红色
			rectangle(190, 135, 610, 165);//画新的边框
			if (m.uMsg == WM_LBUTTONDOWN) {
				break;
			}
		}
		else if (m.x >= 200 && m.x <= 600 && m.y >= 200 && m.y <= 220) {
			setlinecolor(YELLOW);
			rectangle(190, 195, 610, 225);
			if (m.uMsg == WM_LBUTTONDOWN) {
				showhistory();
				goto menubegin;
			}
		}
		else if (m.x >= 200 && m.x <= 600 && m.y >= 260 && m.y <= 280) {
			setlinecolor(YELLOW);
			rectangle(190, 255, 610, 285);
			if (m.uMsg == WM_LBUTTONDOWN) {
				showcontrol();
				goto menubegin;
			}
		}
		else if (m.x >= 200 && m.x <= 600 && m.y >= 320 && m.y <= 340) {
			setlinecolor(YELLOW);
			rectangle(190, 315, 610, 345);
			if (m.uMsg == WM_LBUTTONDOWN) {
				showdetail();
				goto menubegin;
			}
		}
		//
		else if (m.x >= 200 && m.x <= 600 && m.y >= 380 && m.y <= 400) {
			setlinecolor(YELLOW);
			rectangle(190, 375, 610, 405);
			if (m.uMsg == WM_LBUTTONDOWN) {
				showrespect();
				goto menubegin;
			}
		}
		else if (m.x >= 200 && m.x <= 600 && m.y >= 440 && m.y <= 460) {
			setlinecolor(YELLOW);
			rectangle(190, 435, 610, 465);
			if (m.uMsg == WM_LBUTTONDOWN) {
				goon = 0;
				cout << "谎言不会伤人,\n真相才是快刀。\n别玩了,一点人情世故都不懂-_-"<<endl<<endl;
				break;
			}
		}
		else if (m.x >= 200 && m.x <= 600 && m.y >= 500 && m.y <= 520) {
			setlinecolor(YELLOW);
			rectangle(190, 495, 610, 525);
			if (m.uMsg == WM_LBUTTONDOWN) {
				goon = 0;
				break;
			}
		}
		else {
			setlinecolor(WHITE);
			rectangle(190, 135, 610, 165);
			rectangle(190, 195, 610, 225);
			rectangle(190, 255, 610, 285);
			rectangle(190, 315, 610, 345);
			rectangle(190, 375, 610, 405);
			rectangle(190, 435, 610, 465);
			rectangle(190, 495, 610, 525);
		}
	}
}

其中MOUSEMSG定义了一个鼠标变量m,菜单肯定用鼠标嘛,m用来接收鼠标是按了左键还是右键,再返回信息,此时我们再做出相应反应就好了。

问题10(窗口乱跑)

在初始化菜单窗口或者游戏窗口时,有时候窗口会跑到屏幕以外去,我也不知道具体因为啥,然后我通过这个函数解决了

void Menu::window()
{
	initgraph(WIDTH, HEIGHT);
	// 确定窗口的初始位置,使其位于屏幕中央
	int windowPosX = 0;
	int windowPosY = 0;

	// 获取当前窗口句柄
	HWND hwnd = GetHWnd();

	// 设置窗口位置
	SetWindowPos(hwnd, HWND_TOP, windowPosX, windowPosY, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
}

简单来说就是不用最基础的initgraph,而是自己写一个初始化窗口的函数,在里面调用initgraph后再手动设置窗口的位置,保持窗口不跑到屏幕外面(这里我把窗口的左上角放在(0,0)点上)。

当然,菜单也可以有它的专属图片和音乐


Menu::Menu()
{
	loadimage(&menu_bk, "menubk.png", WIDTH, HEIGHT);
	loadimage(&detail_bk, "detailbk.jpg", WIDTH, HEIGHT);
	loadimage(&history_bk, "historybk.jpg", WIDTH, HEIGHT);
	loadimage(&control_bk, "controlbk.jpg", WIDTH, HEIGHT);
}

图片放在构造函数就行了,因为只需要加载一次,这里我没加音乐,你们可以自己加。

做完的效果是这样的:

图片是我乱翻的一张游戏对马岛之魂(这游戏挺好玩的,期末周让我玩上了,期末都没复习)的截图。 

关于历史得分榜,这是文件的读取和写入方面的基础知识,没啥需要讲的。

可以看看:C++对txt文件的写入读取操作_c++读取txt-CSDN博客

打包分享装b (适当的自我激励)

 问题11(我打包的游戏别人运行不了或者图片音频消失了)

如果做了一个游戏只有自己玩的了别人都玩不了,谈何乐趣?!当时我发给好几个朋友,他们的问题都不尽相同,有的少dll,有的少easyx,有的就是直接的运行错误,我一边下载dll一边打包给他们,一边让他们安装vs和easyx,试了好几次都失败了,那叫一个苦恼,如果你也有一样的苦恼,来,照我这样来!

右击解决方案,打开所在文件夹,把里面全部东西都选中打开。(包括那个x64文件夹) 

选择新建的setup,右击,选择生成,接着右击解决方案如下图,选择打开文件夹再返回上一级就能看见setup了, 此时直接打开安装即可,到时候会在桌面看见游戏,当然也可以设置游戏的图标,自己去搜。

源代码 

 好了,难点大概就这些,以下是所有的源代码,分为4个头文件和1个cpp文件:

block类头文件

#include <iostream>
#include <stdio.h>
#include <graphics.h>
#ifndef BLOCK_H
#define BLOCK_H
#pragma once
#define WIDTH 1500
#define HEIGHT 800
//定义食物以及蛇的大小
#define SIZE  10
using namespace std;

class Block
{
	friend class Snake;
	friend class Food;
private:
	vector<vector<int>> vc;//用vector数组存放批量障碍物的坐标
	int x, y;
	int count;
	int difficulty;//难度越高,障碍物数量越多
	IMAGE b, bv;//障碍物的图案
public:
	Block(); //初始化8个障碍物
	void Draw(int num);//负责障碍物相关的绘画
	bool coli(int x0, int y0);//碰撞判定
	bool onblock(int x, int y);//判断一个东西是否与障碍物重叠
	void modify(int num);//改变障碍物数量
	void diff();//改变显示难度
};

Block::Block():vc(100, vector<int>(2)), count(8), difficulty(1)
{
	srand(time(0));//改变随机数种子
	for (int i = 0; i < 100; i++)
	{
		do
		{
			x = rand() % (WIDTH / SIZE);
			y = rand() % (HEIGHT / SIZE);
		} while (x % 2 == 0 || y % 2 == 0 || x >= WIDTH / 10 - 5 || y >= HEIGHT / 10 - 5);
		x *= 10;
		y *= 10;
		vc[i][0] = x;
		vc[i][1] = y;
	}
	loadimage(&b, "block.png", 5 * SIZE, 5 * SIZE);//初始化图案
	loadimage(&bv, "blockrv.png", 5 * SIZE, 5 * SIZE);
}

void Block::Draw(int num)
{
	char difficultystr[10];
	settextcolor(BLACK);
	sprintf_s(difficultystr, "难度:%2d", difficulty);
	outtextxy(900, 20, difficultystr);
	for (int i = 0; i < num; i++)
	{
		putimage(vc[i][0], vc[i][1], &bv, SRCAND);
		putimage(vc[i][0], vc[i][1], &b, SRCPAINT);
	}
}

bool Block::coli(int x0,int y0)
{
	for (int i = 0; i < count; i++)
	{
		int x = vc[i][0];
		int y = vc[i][1];//循环每个障碍物,判断蛇头坐标x0,y0是否碰到障碍物
		if (x0 == x && y0 == y) return true;
		if (x0 == x + 2 * SIZE && y0 == y) return true;
		if (x0 == x + 4 * SIZE && y0 == y) return true;
		if (x0 == x && y0 == y + 2 * SIZE) return true;
		if (x0 == x && y0 == y + 4 * SIZE) return true;
		if (x0 == x + 2 * SIZE && y0 == y + 2 * SIZE) return true;
		if (x0 == x + 4 * SIZE && y0 == y + 2 * SIZE) return true;
		if (x0 == x + 2 * SIZE && y0 == y + 4 * SIZE) return true;
		if (x0 == x + 4 * SIZE && y0 == y + 4 * SIZE) return true;
	}
	return false;
}

bool Block::onblock(int x,int y)
{
	x *= 10;
	y *= 10;
	for (int i = 0; i < count; i++)
	{
		if (x >= vc[i][0] && x <= vc[i][0] + 4 * SIZE && y >= vc[i][1] && y <= vc[i][1] + 4 * SIZE) return true;
	}return false;
}

void Block::modify(int num)
{
	count = num;
}

void Block::diff()
{
	difficulty++;
}

#endif

end类头文件

#include <iostream>
#include <stdio.h>
#include <graphics.h>
#ifndef END_H
#define END_H
#pragma once
#define WIDTH 1500
#define HEIGHT 800
//定义食物以及蛇的大小
#define SIZE  10
using namespace std;

class End
{
private:
	IMAGE kobe1, kobe2, kobe3, champ;
public:
	End()
	{
		loadimage(&kobe1, "ending.jpg", 552, 551, 0);
		loadimage(&kobe2, "hurt2.jpg", 539, 839, 0);
		loadimage(&kobe3, "hurt.jpg", 570, 856, 0);
		loadimage(&champ, "champ.png", 520, 359, 0);
	}
	void draw()
	{
		cleardevice();
		putimage(19, 497, &champ);
		putimage(0, 0, &kobe1);
		putimage(552, 0, &kobe2);
		putimage(1010, 0, &kobe3);
		Sleep(9000);
	}
};

#endif

menu类头文件

#include <iostream>
#include <stdio.h>
#include <graphics.h>
#ifndef MENU_H
#define MENU_H
#pragma once
#define WIDTH 1500
#define HEIGHT 800
//定义食物以及蛇的大小
#define SIZE  10
using namespace std;

class Menu
{
private:
	IMAGE menu_bk, detail_bk, history_bk, control_bk, respect_bk;
	MOUSEMSG m;
public:
	int goon = 1;
	Menu();
	void window();
	void showmenu();
	void showhistory();
	void showdetail();
	void showcontrol();
	void showrespect();
	bool goback();
	void showreturn();
};

void Menu::window()
{
	initgraph(WIDTH, HEIGHT);
	// 确定窗口的初始位置,使其位于屏幕中央
	int windowPosX = 0;
	int windowPosY = 0;

	// 获取当前窗口句柄
	HWND hwnd = GetHWnd();

	// 设置窗口位置
	SetWindowPos(hwnd, HWND_TOP, windowPosX, windowPosY, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
}

Menu::Menu()
{
	loadimage(&menu_bk, "menubk.png", WIDTH, HEIGHT);
	loadimage(&detail_bk, "detailbk.jpg", WIDTH, HEIGHT);
	loadimage(&history_bk, "historybk.jpg", WIDTH, HEIGHT);
	loadimage(&control_bk, "controlbk.jpg", WIDTH, HEIGHT);
}

void Menu::showmenu()
{
menubegin:
	window();
	setbkcolor(WHITE);
	cleardevice();
	putimage(0, 0, &menu_bk);
	setfillcolor(WHITE);
	fillrectangle(200, 140, 600, 160);
	fillrectangle(200, 200, 600, 220);
	fillrectangle(200, 260, 600, 280);
	fillrectangle(200, 320, 600, 340);
	fillrectangle(200, 380, 600, 400);
	fillrectangle(200, 440, 600, 460);
	fillrectangle(200, 500, 600, 520);
	setbkmode(TRANSPARENT);
	settextcolor(BLACK);
	outtextxy(250, 142, "开始游戏");//goto begin
	outtextxy(250, 202, "历史得分榜");//goto history
	outtextxy(250, 262, "操作");//goto control
	outtextxy(250, 322, "游戏说明");//goto detail
	outtextxy(250, 382, "作者好帅");//就喜欢你这种有眼力的玩家
	outtextxy(250, 442, "作者好丑");//游戏崩溃
	outtextxy(250, 502, "mamba out");//退出游戏
	settextcolor(WHITE);
	while (1) {
		m = GetMouseMsg();
		if (m.x >= 200 && m.x <= 600 && m.y >= 140 && m.y <= 160) {//检测鼠标的位置 是否满足条件
			setlinecolor(YELLOW);//满足后 设置新的边框为红色
			rectangle(190, 135, 610, 165);//画新的边框
			if (m.uMsg == WM_LBUTTONDOWN) {
				break;
			}
		}
		else if (m.x >= 200 && m.x <= 600 && m.y >= 200 && m.y <= 220) {
			setlinecolor(YELLOW);
			rectangle(190, 195, 610, 225);
			if (m.uMsg == WM_LBUTTONDOWN) {
				showhistory();
				goto menubegin;
			}
		}
		else if (m.x >= 200 && m.x <= 600 && m.y >= 260 && m.y <= 280) {
			setlinecolor(YELLOW);
			rectangle(190, 255, 610, 285);
			if (m.uMsg == WM_LBUTTONDOWN) {
				showcontrol();
				goto menubegin;
			}
		}
		else if (m.x >= 200 && m.x <= 600 && m.y >= 320 && m.y <= 340) {
			setlinecolor(YELLOW);
			rectangle(190, 315, 610, 345);
			if (m.uMsg == WM_LBUTTONDOWN) {
				showdetail();
				goto menubegin;
			}
		}
		//
		else if (m.x >= 200 && m.x <= 600 && m.y >= 380 && m.y <= 400) {
			setlinecolor(YELLOW);
			rectangle(190, 375, 610, 405);
			if (m.uMsg == WM_LBUTTONDOWN) {
				showrespect();
				goto menubegin;
			}
		}
		else if (m.x >= 200 && m.x <= 600 && m.y >= 440 && m.y <= 460) {
			setlinecolor(YELLOW);
			rectangle(190, 435, 610, 465);
			if (m.uMsg == WM_LBUTTONDOWN) {
				goon = 0;
				cout << "谎言不会伤人,\n真相才是快刀。\n别玩了,一点人情世故都不懂-_-"<<endl<<endl;
				break;
			}
		}
		else if (m.x >= 200 && m.x <= 600 && m.y >= 500 && m.y <= 520) {
			setlinecolor(YELLOW);
			rectangle(190, 495, 610, 525);
			if (m.uMsg == WM_LBUTTONDOWN) {
				goon = 0;
				break;
			}
		}
		else {
			setlinecolor(WHITE);
			rectangle(190, 135, 610, 165);
			rectangle(190, 195, 610, 225);
			rectangle(190, 255, 610, 285);
			rectangle(190, 315, 610, 345);
			rectangle(190, 375, 610, 405);
			rectangle(190, 435, 610, 465);
			rectangle(190, 495, 610, 525);
		}
	}
}

void Menu::showdetail()
{
	setbkcolor(WHITE);
	cleardevice();
	/*putimage(0, 0, &detail_bk);*/
	char line1[1000] = "游戏说明:";
	char line2[1000] = "1. 游戏压缩包包没有病毒的,请放心使用,如果游玩发现bug,请转告给世界上最帅的人,欢迎各位转告给我。";
	char line3[1000] = "2. 长按前进方向加速突破,反向减速慢节奏。";
	char line4[1000] = "3. 曼巴具有剧毒性,咬到自己会马上死亡。";
	char line5[1000] = "4. 曼巴速度很快,动能太大,撞墙马上死亡。";
	char line6[1000] = "5. 游戏中的肘子应尽量避免,如被肘击,在1.5秒内扣1可以复活劳大,此时劳大会艰难爬起来前进。";
	char line7[1000] = "6. 如果闲游戏太简单,按h增加难度,难度会在吃到下一个食物后刷新。";
	char line8[1000] = "7. 游戏为本人制作,欢迎转载或用于商业行为。(神头鬼脸)";
	char line9[1000] = "8. 游戏达到81分有彩蛋;";
	char line10[1000] ="9. 游戏后续会更新完善,3.0版本仅为第三代版本,望理解,敬请期待4.0版本。";
	while (1)
	{
		outtextxy(100, 50, line1);
		outtextxy(100, 80, line2);
		outtextxy(100, 110, line3);
		outtextxy(100, 140, line4);
		outtextxy(100, 170, line5);
		outtextxy(100, 200, line6);
		outtextxy(100, 230, line7);
		outtextxy(100, 260, line8);
		outtextxy(100, 290, line9);
		outtextxy(100, 320, line10);
		showreturn();
		if (goback()) break;
	}
}

void Menu::showhistory()
{
	setbkcolor(WHITE);
	cleardevice();
	/*putimage(0, 0, &history_bk);*/
	setfillcolor(WHITE);
	while (1) //文件输出
	{
		showreturn();
		if (goback()) break;
	}
}

void Menu::showcontrol()
{
	setbkcolor(WHITE);
	cleardevice();
	/*putimage(0, 0, &control_bk);*/
	char line1[100] = "WASD : 上下左右,长按加减速";
	char line2[100] = "H : 增加难度";
	char line3[100] = "扣1 : 复活牢大";
	char line4[100] = "空格 : 暂停";
	char line5[100] = "详见游戏说明";
	while (1)
	{
		outtextxy(200, 30, line1);
		outtextxy(200, 60, line2);
		outtextxy(200, 90, line3);
		outtextxy(200, 120, line4);
		outtextxy(200, 150, line5);
		showreturn();
		if (goback()) break;
	}
}

void Menu::showrespect()
{
	setbkcolor(WHITE);
	cleardevice();
	/*putimage(0, 0, &respect_bk);*/
	while (1)
	{
		char line1[100] = "就喜欢爱说真话的人>_<";
		outtextxy(200, 30, line1);
		showreturn();
		if (goback()) break;
	}
	cout << endl;
}


bool Menu::goback()
{
	m = GetMouseMsg();
	if (m.x >= 0 && m.x <= 1800 && m.y >= 0 && m.y <= 800)
		if (m.uMsg == WM_RBUTTONDOWN) {
			return 1;
		}
	return 0;
}

void Menu::showreturn()
{
	settextcolor(BLACK);
	outtextxy(900, 202, "右");
	outtextxy(900, 222, "击");
	outtextxy(900, 242, "鼠");
	outtextxy(900, 262, "标");
	outtextxy(900, 282, "返");
	outtextxy(900, 302, "回");
}
// 
// int main()
// {
// 	Menu m;
// 	m.showmenu();
// 	return 0;
// }


#endif

music类头文件

#include <iostream>
#include <stdio.h>
#include <graphics.h>
#include <conio.h>
#ifndef MUSIC_H
#define MUSIC_H
#pragma once
#define WIDTH 1500
#define HEIGHT 800
//定义食物以及蛇的大小
#define SIZE  10
using namespace std;

class Music
{
private:
	int vol;
public:
	Music() :vol(100) {};
	void bk_volumn();
	void play_bkmusic();
	void play_eatingsound();
	void play_colisound();
	void play_endmusic();
	void play_menumusic();
	void play_detailmusic();
	void play_controlmusic();
	void play_respectmusic();//你干嘛~~哎呦
	void play_norespectmusic();
	void play_historymusic();//三十年河西三十年河东,莫欺少年穷!
};

void Music::bk_volumn()
{
	if (GetAsyncKeyState(38)) vol += 100;
	else if (GetAsyncKeyState(40)) vol -= 100;
	char com1[41];
	sprintf(com1, "setaudio BGM1 volume to %d", vol);
	mciSendString(com1, NULL, 0, NULL);
}
void Music::play_bkmusic()
{
	mciSendString("open syagp.mp3 alias BGM1", 0, 0, 0);//see you again加速版
	mciSendString("play BGM1 repeat", 0, 0, 0);
}

void Music::play_eatingsound()
{
	mciSendString("close EAT", 0, 0, 0);
	mciSendString("open eatingsound.mp3 alias EAT", 0, 0, 0);
	mciSendString("play EAT", 0, 0, 0);
	if (GetAsyncKeyState(38)) vol += 100;
	else if (GetAsyncKeyState(40)) vol -= 100;
	char com2[41];
	sprintf(com2, "setaudio EAT volume to %d", vol);
	mciSendString(com2, NULL, 0, NULL);
	
}

void Music::play_colisound()
{
	mciSendString("close BGM1", 0, 0, 0);
	mciSendString("close BGM2", 0, 0, 0);
	mciSendString("open ah.mp3 alias BGM2", 0, 0, 0);//man!
	mciSendString("play BGM2", 0, 0, 0);
	if (GetAsyncKeyState(38)) vol += 100;
	else if (GetAsyncKeyState(40)) vol -= 100;
	char com3[41];
	sprintf(com3, "setaudio BGM2 volume to %d", vol);
	mciSendString(com3, NULL, 0, NULL);
}

void Music::play_endmusic()
{
	mciSendString("close BGM1", 0, 0, 0);
	mciSendString("open mambaout.mp3 alias END", 0, 0, 0);//mambaout
	mciSendString("play END", 0, 0, 0);
	if (GetAsyncKeyState(38)) vol += 100;
	else if (GetAsyncKeyState(40)) vol -= 100;
	char com4[41];
	sprintf(com4, "setaudio END volume to %d", vol);
	mciSendString(com4, NULL, 0, NULL);
}


#endif

源文件

#include <iostream>
#include <easyx.h>
#include<time.h>
#include <stdlib.h>
#include <vector>
#include"menu.h"
#include"music.h"
#include "end.h"
#include "block.h"
#pragma comment(lib,"winmm.lib")
#include <mmsystem.h>
using namespace std;

//定义场景大小
#define WIDTH 1500
#define HEIGHT 800
//定义食物以及蛇的大小
#define SIZE  10
//定义蛇的朝向
#define UP 1
#define DOWN -1
#define LEFT 2
#define RIGHT -2
int speed = 175;		//蛇的速度(用在睡眠函数里面)
#define MAXLEN 1600             //蛇的最大长度
int wuditime = 0;//无敌帧
typedef struct {
	int x;
	int y;
}SnakeNode;

//创建蛇的类
class Snake
{
	friend class Food;          //蛇的友元为食物
	friend class Block;
public:
	Snake()//初始化
	{
		this->dirt = RIGHT;
		this->length = 3;
		score = 0;
		two = three = 0;
		//下标是0的位置为蛇的头部
		for (int i = 0; i < length; i++) {
			this->node[i].x = 70 - ((i + 1) * 2 * SIZE);
			this->node[i].y = SIZE;
		}
		loadimage(&bk, "bk.png", WIDTH, HEIGHT, 1);
	};
	void Move();				//移动
	void Draw();				//绘制蛇
	int Eat(Food& food);	    //吃食物
	bool Defeat(Block& block);				//失败判定
private:
	int dirt;					//朝向
	int length;					//长度
	int score; //总得分
	int two;//两分球个数
	int three;//三分球个数
	SnakeNode node[MAXLEN];		//蛇的结点     
	IMAGE bk;
};

//创建食物的类
class Food
{
	friend class Snake;         //食物的友元为蛇
	friend class Block;
public:
	Food(Snake& snake, Block& block);			//食物初始化
	void Draw();				//绘制食物
	bool coli(float x0, float y0)//碰撞判断
	{
		if (x0 == x && y0 == y) return true;
		if (x0 == x + 2 * SIZE && y0 == y) return true;
		if (x0 == x + 4 * SIZE && y0 == y) return true;
		if (x0 == x && y0 == y + 2 * SIZE) return true;
		if (x0 == x && y0 == y + 4 * SIZE) return true;
		if (x0 == x + 2 * SIZE && y0 == y + 2 * SIZE) return true;
		if (x0 == x + 4 * SIZE && y0 == y + 2 * SIZE) return true;
		if (x0 == x + 2 * SIZE && y0 == y + 4 * SIZE) return true;
		if (x0 == x + 4 * SIZE && y0 == y + 4 * SIZE) return true;
		return false;
	}
private:
	int x, y;					//坐标
	int score;					//分数
	int type;
	IMAGE ima1, ima2, ima3, ima4;
};

SnakeNode tmp[MAXLEN];	//用另外一个数组来存储蛇原来的位置
//移动

void Snake::Move() {
	//将原来的蛇结点拷贝一份
	for (int i = 0; i < this->length; i++) {
		tmp[i].x = this->node[i].x;
		tmp[i].y = this->node[i].y;
	}
	int status = 0;//用来判断是否点击了转向按键
	int flag = 0;
	if (this->dirt == RIGHT) {
		//判断是否转向
		if (GetAsyncKeyState('W') && status == 0) {
			this->node[0].y -= 2 * SIZE;
			this->dirt = UP;
			status = 1;
		}
		else if (GetAsyncKeyState('S') && status == 0) {
			this->node[0].y += 2 * SIZE;
			this->dirt = DOWN;
			status = 1;
		}
		else {
			if (GetAsyncKeyState('A') && status == 0)
				flag -= 10;
			if (GetAsyncKeyState('D') && status == 0)
				flag += 10;
			this->node[0].x += 2 * SIZE;
		}
	}
	else if (this->dirt == DOWN) {
		//判断是否转向
		if (GetAsyncKeyState('A') && status == 0) {
			this->node[0].x -= 2 * SIZE;
			this->dirt = LEFT;
			status = 1;
		}
		else if (GetAsyncKeyState('D') && status == 0) {
			this->node[0].x += 2 * SIZE;
			this->dirt = RIGHT;
			status = 1;
		}
		else {
			if (GetAsyncKeyState('W') && status == 0)
				flag -= 10;
			if (GetAsyncKeyState('S') && status == 0)
				flag += 10;
			this->node[0].y += 2 * SIZE;
		}
	}
	else if (this->dirt == LEFT) {
		//判断是否转向
		if (GetAsyncKeyState('W') && status == 0) {
			this->node[0].y -= 2 * SIZE;
			this->dirt = UP;
			status = 1;
		}
		else if (GetAsyncKeyState('S') && status == 0) {
			this->node[0].y += 2 * SIZE;
			this->dirt = DOWN;
			status = 1;
		}
		else {
			if (GetAsyncKeyState('D') && status == 0)
				flag -= 10;
			if (GetAsyncKeyState('A') && status == 0)
				flag += 10;
			this->node[0].x -= 2 * SIZE;
		}
	}
	else if (this->dirt == UP) {
		//判断是否转向
		if (GetAsyncKeyState('A') && status == 0) {
			this->node[0].x -= 2 * SIZE;
			this->dirt = LEFT;
			status = 1;
		}
		else if (GetAsyncKeyState('D') && status == 0) {
			this->node[0].x += 2 * SIZE;
			this->dirt = RIGHT;
			status = 1;
		}
		else {
			if (GetAsyncKeyState('S') && status == 0)
				flag -= 10;
			if (GetAsyncKeyState('W') && status == 0)
				flag += 10;
			this->node[0].y -= 2 * SIZE;
		}
	}
	//移动
	for (int i = 1; i < this->length; i++) {
		this->node[i].x = tmp[i - 1].x;
		this->node[i].y = tmp[i - 1].y;
	}
	Sleep(speed - flag * 15);
}


//绘制蛇
void Snake::Draw() {
	cleardevice();//清空原先的绘图
	putimage(0, 0, &bk);//先画出背景
	settextcolor(BLACK);
	char  scorestr[2000], twostr[1000], threestr[1000], str[8];
	sprintf_s(twostr, "二分球:%2d", two);
	sprintf_s(threestr, "三分凉:%2d", three);
	sprintf_s(scorestr, "总得分:%2d", score);//画出当前战绩
	outtextxy(900, 90, scorestr);
	outtextxy(900, 70, threestr);
	outtextxy(900, 50, twostr);
	srand((unsigned)time(NULL));//设置随机数种子
	for (int i = 0; i < this->length; i++) {
		setfillcolor(BLACK);//“黑”曼巴
		fillcircle(this->node[i].x, this->node[i].y, SIZE);
	}
}


//食物的初始化
Food::Food(Snake& snake, Block& block)
{
	loadimage(&ima1, "basketball.png", 4 * SIZE, 4 * SIZE);
	loadimage(&ima2, "baskrv.png", 4 * SIZE, 4 * SIZE);
	loadimage(&ima3, "bhc.png", 4 * SIZE, 4 * SIZE);
	loadimage(&ima4, "rv.png", 4 * SIZE, 4 * SIZE);
table:
	srand((unsigned)time(0));
	do
	{
		x = (rand() % (WIDTH / SIZE));
		y = (rand() % (HEIGHT / SIZE));
	} while (x % 2 == 0 || y % 2 == 0 || x >= WIDTH / 10 - 4 || y >= HEIGHT / 10 - 4 || block.onblock(x, y));//一直生成随机坐标直到该坐标不与障碍物重叠并且为能被蛇头碰到
	this->x *= SIZE;
	this->y *= SIZE;
	this->type = rand() % 100;
	if (type >= 0 && type <= 80) {
		type = 1;
	}
	else {
		type = 2;
	}
	for (int i = 0; i < snake.length; i++) {
		if (snake.node[i].x == this->x && snake.node[i].y == this->y) {
			goto table;
		}
	}
}

//绘制食物
void Food::Draw() {
	if (type == 1)//篮球
	{
		putimage(this->x, this->y, &ima2, SRCAND);
		putimage(x, y, &ima1, SRCPAINT);
	}
	else if (type == 2)//冰红茶
	{
		putimage(x, y, &ima4, SRCAND);
		putimage(x, y, &ima3, SRCPAINT);
	}
}

//吃食物
int Snake::Eat(Food& food) {
	if (food.coli(node[0].x, node[0].y)) {
		if (food.type == 1) {
			two++;
			score += 2;
		}
		else if (food.type == 2) {
			three++;
			score += 3;
		}
		if (this->node[length - 1].x - this->node[length - 2].x == 0 && this->node[length - 1].y - this->node[length - 2].y == -20) {
			this->length++;
			this->node[length - 1].x = this->node[length - 2].x;
			this->node[length - 1].y = this->node[length - 2].y - 2 * SIZE;
		}
		if (this->node[length - 1].x - this->node[length - 2].x == 0 && this->node[length - 1].y - this->node[length - 2].y == 20) {
			this->length++;
			this->node[length - 1].x = this->node[length - 2].x;
			this->node[length - 1].y = this->node[length - 2].y + 2 * SIZE;
		}
		if (this->node[length - 1].x - this->node[length - 2].x == 20 && this->node[length - 1].y - this->node[length - 2].y == 0) {
			this->length++;
			this->node[length - 1].x = this->node[length - 2].x + 2 * SIZE;
			this->node[length - 1].y = this->node[length - 2].y;
		}
		if (this->node[length - 1].x - this->node[length - 2].x == -20 && this->node[length - 1].y - this->node[length - 2].y == 0) {
			this->length++;
			this->node[length - 1].x = this->node[length - 2].x - 2 * SIZE;
			this->node[length - 1].y = this->node[length - 2].y;
		}
		return true;
	}
	return false;
}

//失败判定
bool Snake::Defeat(Block& block) {
	//1.碰到边界
	if (this->node[0].x < 0 || this->node[0].x >= WIDTH || this->node[0].y < 0 || this->node[0].y >= HEIGHT) {
		return true;
	}
	//2.碰到自己的身体
	for (int i = 1; i < this->length; i++) {
		if (this->node[0].x == this->node[i].x && this->node[0].y == this->node[i].y) {
			return true;
		}
	}
	//3.被肘击
	if (block.coli(node[0].x, node[0].y))
	{
		Sleep(1500);
		if (GetAsyncKeyState('1')) {
			wuditime = 5;
			return false;
		}
		return true;
	}
	return false;
}

int main() {
	Menu menu;
	Music music;
	End ending;
begin:
	menu.showmenu();
	if (menu.goon)
	{
		menu.window();
	table1:
		setbkcolor(WHITE);
		cleardevice();
		music.play_bkmusic();
		Block block;
		Snake snake;
		int num = 8;
		int newnum = 8;
		int eat = 0;
	table2:
		Food food(snake, block);
		while (1) {
			BeginBatchDraw();
			FlushBatchDraw();
			music.bk_volumn();
			snake.Draw();
			if (eat)
			{
				block.modify(newnum);
				block.Draw(newnum);
				num = newnum;
			}
			else block.Draw(num);
			if (GetAsyncKeyState('H'))
			{
				newnum += 16;
				block.diff();
			}
			if (GetAsyncKeyState(' ')) system("Pause");
			food.Draw();
			FlushBatchDraw();
			EndBatchDraw();//双缓冲,防止屏幕一闪一闪的
			if (snake.Eat(food)) {
				eat = 1;
				music.play_eatingsound();
				wuditime = 5;//无敌帧
				goto table2;
			}
			if (wuditime<=0 &&snake.Defeat(block) ) {
				music.play_colisound();
				break;
			}
			snake.Move();
			wuditime--;
			eat = 0;
		}
		HWND window = GetHWnd();
		SetWindowText(window, "死亡");
		int end = MessageBox(window, "mamba out,see you again?", "提示", MB_OKCANCEL);
		if (end == IDOK) goto table1;
		else if (end==IDCANCEL)
		{
			music.play_endmusic();
			ending.draw();
			goto begin;
		}
	}
	return 0;
}

  • 15
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值