首先我们需要下载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_KEYDOWN | EX_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;
}
至此我们就实现了简单的贪吃蛇游戏,它可以上下左右移动,并在吃到食物后增长一格身体