您现在的位置是:首页 >技术教程 >贪吃蛇小游戏(C++)网站首页技术教程

贪吃蛇小游戏(C++)

小梁今天敲代码了吗 2023-06-17 12:00:02
简介贪吃蛇小游戏(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;
}

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

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。