贪吃蛇小游戏(C++)

首先我们需要下载EasyX(具体的方法在EasyX专栏中有提到)

easyX下载和绘制简单基本图形_小梁今天敲代码了吗的博客-CSDN博客

贪吃蛇这个游戏我们一定都玩过,玩家使用方向键操控一条“蛇”,蛇会朝着一个方向不断移动,玩家可以通过上下左右键改变其运动方向。同时屏幕上随机会出现各种“食物”,玩家要控制蛇去吃掉这些食物,每吃掉一个,蛇的身体就会增长一节。

想要实现这个游戏,我们主要得考虑蛇和食物

蛇:

蛇的移动方向如何实现

蛇的身体长度如何实现增长

食物:

食物如何随机生成

下面我将代码部分拆解做一个简单的说明,后附源码

首先是播放背景音乐:

将音乐的mp3格式与代码放在一个文件夹中,用以下函数调用:

#include<mmsystem.h>//头文件
#pragma comment(lib,"winmm.lib")//库文件
	//播放音乐 mci media control interface(多媒体设备接口)
	//Send 发送 String字符串
//音乐需要放在程序同一文件目录中
mciSendString("open 音乐.mp3",0,0,0);
mciSendString("play 音乐.mp3", 0, 0, 0);

 创建“精灵类”,它是“蛇类”和“食物类”的父类

这里难理解的地方是:碰撞检测(我们如何去判定蛇能吃到食物?这里采用的方法是当蛇头的左上方x,y坐标完全与食物的左上方x,y坐标相等时,我们认为蛇吃到了食物)

这个函数用于设置当前设备填充颜色。

void setfillcolor(COLORREF color);

 

这个函数用于画有边框的填充矩形。

void fillrectangle(
	int left,
	int top,
	int right,
	int bottom
);

 

//精灵类
class Sprite
{
public:
	Sprite():Sprite(0,0) {};
	Sprite(int x, int y) :m_x(x), m_y(y),m_color(RED) {};
	//绘制精灵
	virtual void draw()
	{
		//设置填充颜色
		setfillcolor(m_color);
		//绘制矩形
		fillrectangle(m_x, m_y, m_x + 10, m_y + 10);
	}
	//移动
	void moveBy(int dx, int dy)
	{
		m_x += dx;
		m_y += dy;
	}
	//碰撞检测
	bool collision(const Sprite& other)
	{
		return m_x == other.m_x && m_y == other.m_y;
	}
protected:
	int m_x;
	int m_y;
	COLORREF m_color;//颜色
};

蛇类这里移动时,将上一个格子坐标赋值给下一个格子,实现移动

push_back()函数的用法

函数将一个新的元素加到vector的最后面,位置为当前最后一个元素的下一个元素

//蛇类
class Snake : public Sprite
{
public:
	Snake():Snake(0,0) {}
	Snake(int x,int y):Sprite(x,y),dir(VK_RIGHT)
	{
		//初始化三节蛇
		nodes.push_back(Sprite(20, 0));
		nodes.push_back(Sprite(10, 0));
		nodes.push_back(Sprite(0, 0));
	}
	void draw() override
	{
		for (int i = 0; i < nodes.size(); i++)
		{
			nodes[i].draw();
		}
	}
	//蛇的身体移动
	void bodyMove()
	{
		//身体跟着蛇头移动
		for (size_t i = nodes.size()-1; i >0; i--)
		{
			nodes[i] = nodes[i - 1];
		}
		//移动蛇头
		switch (dir)
		{
		case VK_UP:
			nodes[0].moveBy(0,-10);
			break;
		case VK_DOWN:
			nodes[0].moveBy(0,10);
			break;
		case VK_LEFT:
			nodes[0].moveBy(-10, 0);
			break;
		case VK_RIGHT:
			nodes[0].moveBy(10, 0);
			break;
		}
		
	}
	bool collision(const Sprite& other)
	{
		return nodes[0].collision(other);
	}
	//蛇增加一节
	void incrment()
	{
		nodes.push_back(Sprite());
	}
private:
	//蛇只有一节吗?
	std::vector<Sprite> nodes;//蛇的所有节点

public:
	int dir;//蛇的方向
};

 食物:

为了使程序在每次执行时都能生成一个新序列的随机值,我们通常通过为随机数生成器提供一粒新的随机种子。函数srand()可以为随机数生成器播散种子。随机生成食物时,我们需要它的坐标为10的整数倍,因为蛇的身体我们设定的占10个像素,所以只能吃到坐标为10的整数倍的食物

//食物
class Food :public Sprite
{
public:
	Food() :Sprite(0, 0) 
	{
		changePos();
	}
	void draw()override
	{
		setfillcolor(m_color)
			;
		solidellipse(m_x, m_y, m_x + 10, m_y + 10);
	}
	//改变食物的坐标
	void changePos()
	{
		//随机生成坐标
		m_x = rand() % 64 * 10;
		m_y = rand() % 48 * 10;
	}
};

 游戏场景:

这里引用的函数是什么意思基本已经在代码中进行了标注

这里的switch代码是在蛇的移动过程中,让它不能掉头

switch (msg.vkcode)
			{
			case VK_UP:
				if (snake.dir != VK_DOWN)
					snake.dir = msg.vkcode;
				break;
			case VK_DOWN:
				if (snake.dir != VK_UP)
					snake.dir = msg.vkcode;
				break;
			case VK_LEFT:
				if (snake.dir != VK_RIGHT)
					snake.dir = msg.vkcode;
				break;
			case VK_RIGHT:
				if (snake.dir != VK_LEFT)
					snake.dir = msg.vkcode;
				break;
			}

 ExMessage这个结构体用于保存鼠标消息

WM_KEYDOWNEX_KEY按键按下消息

BeginBatchDraw()这个函数用于开始批量绘图。执行后,任何绘图操作都将暂时不输出到绘图窗口上,直到执行 FlushBatchDraw 或 EndBatchDraw 才将之前的绘图输出。

void BeginBatchDraw();

 

cleardevice()这个函数使用当前背景色清空绘图设备。

void cleardevice();
/*游戏场景*/
class GameScene
{
public:
	GameScene() 
	{

	};
	void run()
	{
		BeginBatchDraw();//双缓冲绘图 防止屏幕闪烁
		cleardevice();//清屏
		snake.draw();
		food.draw();
		EndBatchDraw();
		//移动蛇,改变蛇的坐标
		snake.bodyMove();
		snakeEatFood();


		//获取消息
		ExMessage msg = { 0 };
		while (peekmessage(&msg, EX_KEY))
		{
			onMsg(msg);
		}

	}
	//改变蛇的移动方向 获取键盘按键 _getch() 
	//响应消息:鼠标消息 键盘消息
	void onMsg(const ExMessage& msg)
	{
		//如果有键盘消息(有没有按键按下)
		if (msg.message == WM_KEYDOWN)
		{
			//判断具体的是哪个按键按下 virtual key code 虚拟键码

			switch (msg.vkcode)
			{
			case VK_UP:
				if (snake.dir != VK_DOWN)
					snake.dir = msg.vkcode;
				break;
			case VK_DOWN:
				if (snake.dir != VK_UP)
					snake.dir = msg.vkcode;
				break;
			case VK_LEFT:
				if (snake.dir != VK_RIGHT)
					snake.dir = msg.vkcode;
				break;
			case VK_RIGHT:
				if (snake.dir != VK_LEFT)
					snake.dir = msg.vkcode;
				break;
			}
			
			//std::cout << msg.vkcode << std::endl;
		}

	}
	//判断蛇能否吃到食物
	void snakeEatFood()
	{
		if (snake.collision(food))//如果蛇和食物产生了碰撞
		{
			//蛇的节数增加
			snake.incrment();
			//食物重新产生在别的地方
			food.changePos();
		}
	}
private:
	Snake snake;
	Food food;

};

 最后源码如下:

#include<iostream>//标准输入输出头文件
#include<graphics.h>
#include<mmsystem.h>
#pragma comment(lib,"winmm.lib")
#include<easyx.h>
#include<vector> //顺序表
#include<ctime>
//精灵类
class Sprite
{
public:
	Sprite():Sprite(0,0) {};
	Sprite(int x, int y) :m_x(x), m_y(y),m_color(RED) {};
	//绘制精灵
	virtual void draw()
	{
		//设置填充颜色
		setfillcolor(m_color);
		//绘制矩形
		fillrectangle(m_x, m_y, m_x + 10, m_y + 10);
	}
	//移动
	void moveBy(int dx, int dy)
	{
		m_x += dx;
		m_y += dy;
	}
	//碰撞检测
	bool collision(const Sprite& other)
	{
		return m_x == other.m_x && m_y == other.m_y;
	}
protected:
	int m_x;
	int m_y;
	COLORREF m_color;//颜色
};

//蛇类
class Snake : public Sprite
{
public:
	Snake():Snake(0,0) {}
	Snake(int x,int y):Sprite(x,y),dir(VK_RIGHT)
	{
		//初始化三节蛇
		nodes.push_back(Sprite(20, 0));
		nodes.push_back(Sprite(10, 0));
		nodes.push_back(Sprite(0, 0));
	}
	void draw() override
	{
		for (int i = 0; i < nodes.size(); i++)
		{
			nodes[i].draw();
		}
	}
	//蛇的身体移动
	void bodyMove()
	{
		//身体跟着蛇头移动
		for (size_t i = nodes.size()-1; i >0; i--)
		{
			nodes[i] = nodes[i - 1];
		}
		//移动蛇头
		switch (dir)
		{
		case VK_UP:
			nodes[0].moveBy(0,-10);
			break;
		case VK_DOWN:
			nodes[0].moveBy(0,10);
			break;
		case VK_LEFT:
			nodes[0].moveBy(-10, 0);
			break;
		case VK_RIGHT:
			nodes[0].moveBy(10, 0);
			break;
		}
		
	}
	bool collision(const Sprite& other)
	{
		return nodes[0].collision(other);
	}
	//蛇增加一节
	void incrment()
	{
		nodes.push_back(Sprite());
	}
private:
	//蛇只有一节吗?
	std::vector<Sprite> nodes;//蛇的所有节点

public:
	int dir;//蛇的方向
};
//食物
class Food :public Sprite
{
public:
	Food() :Sprite(0, 0) 
	{
		changePos();
	}
	void draw()override
	{
		setfillcolor(m_color)
			;
		solidellipse(m_x, m_y, m_x + 10, m_y + 10);
	}
	//改变食物的坐标
	void changePos()
	{
		//随机生成坐标
		m_x = rand() % 64 * 10;
		m_y = rand() % 48 * 10;
	}
};
/*游戏场景*/
class GameScene
{
public:
	GameScene() 
	{

	};
	void run()
	{
		BeginBatchDraw();//双缓冲绘图 防止屏幕闪烁
		cleardevice();//清屏
		snake.draw();
		food.draw();
		EndBatchDraw();
		//移动蛇,改变蛇的坐标
		snake.bodyMove();
		snakeEatFood();


		//获取消息
		ExMessage msg = { 0 };
		while (peekmessage(&msg, EX_KEY))
		{
			onMsg(msg);
		}

	}
	//改变蛇的移动方向 获取键盘按键 _getch() 
	//响应消息:鼠标消息 键盘消息
	void onMsg(const ExMessage& msg)
	{
		//如果有键盘消息(有没有按键按下)
		if (msg.message == WM_KEYDOWN)
		{
			//判断具体的是哪个按键按下 virtual key code 虚拟键码

			switch (msg.vkcode)
			{
			case VK_UP:
				if (snake.dir != VK_DOWN)
					snake.dir = msg.vkcode;
				break;
			case VK_DOWN:
				if (snake.dir != VK_UP)
					snake.dir = msg.vkcode;
				break;
			case VK_LEFT:
				if (snake.dir != VK_RIGHT)
					snake.dir = msg.vkcode;
				break;
			case VK_RIGHT:
				if (snake.dir != VK_LEFT)
					snake.dir = msg.vkcode;
				break;
			}
			
			//std::cout << msg.vkcode << std::endl;
		}

	}
	//判断蛇能否吃到食物
	void snakeEatFood()
	{
		if (snake.collision(food))//如果蛇和食物产生了碰撞
		{
			//蛇的节数增加
			snake.incrment();
			//食物重新产生在别的地方
			food.changePos();
		}
	}
private:
	Snake snake;
	Food food;

};
int main()
{
	//初始化窗口界面
	initgraph(640,480,EW_SHOWCONSOLE);
	GameScene scene;
	scene.run();
	//设置随机数种子
	srand(time(nullptr));
	//播放音乐 mci media control interface(多媒体设备接口)
	//Send 发送 String字符串
	mciSendString("open 音乐.mp3",0,0,0);
	mciSendString("play 音乐.mp3", 0, 0, 0);

	Snake snake;
	snake.draw();
	while (true)
	{
		scene.run();
		Sleep(100);
	}
	getchar();//防止程序闪退
	return 0;
}

至此我们就实现了简单的贪吃蛇游戏,它可以上下左右移动,并在吃到食物后增长一格身体

贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃蛇小游戏贪吃
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小梁今天敲代码了吗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值