目录
实现贪吃蛇我们要知道哪些?
Easyx图形库
Easyx的安装
游戏思路
游戏实现
头文件的使用
编辑和食物以及控制方向的初始化
对于坐标的实现:
食物颜色的实现:
游戏数据的初始化
加载音乐
图形窗口的设置:
蛇身节数的初始化:
食物的初始化
绘制游戏元素
移动蛇
贪吃蛇的穿墙实现
通过键盘移动蛇和暂停游戏
_kbbit()函数作用
蛇的移动判定
暂停游戏
判断蛇吃到食物
蛇吃到食物后的增长
在main函数中调用各个函数
一些操作问题
键盘控制不了蛇的移动:
实现贪吃蛇我们要知道哪些?
Easyx图形库
我们需要安装Easyx配置到vs2022中,使游戏的背景颜色、蛇的颜色、食物的颜色可供我们随意设置,这样就不必再以前那个黑乎乎的终端命令窗口打开了
Easyx的安装
官网链接:https://easyx.cn/
在这个官网里不仅有简单的按照步骤还有关于一些库函数的介绍,教程通俗易懂,仅仅需要一点函数的知识。
游戏思路
我们需要知道蛇的移动是靠坐标的变换来实现的,食物的出现也是这样,这里的坐标系和我们日常数学中的有些区别(y轴的方向相反):
另外我们还需要知道:
是怎么移动的
是怎么吃东西的
吃到食物后节数怎么变化
如何通过键盘来实现蛇的移动
游戏实现
头文件的使用
#include<stdio.h>
#include<graphics.h>//图形库函数的头文件,下面的库函数是我们对背景、颜色、图形的生成等
#include<conio.h>//
#include<stdlib.h>
#include<windows.h>
#include<mmsystem.h>
对于这些头文件,下面使用包含其中的库函数时会讲到
和食物以及控制方向的初始化
#define SNAKE_NUM 500 //蛇的最大节数
//蛇的移动方向
enum DIR
{UP,DOWN,LEFT,RIGHT,
};
//蛇的结构
struct Snake
{int size;//蛇的节数int dir;//蛇的方向int speed;//蛇的速度POINT coor[SNAKE_NUM];//坐标
}snake;
//食物结构
struct Food
{int x;int y;int r;//食物大小bool flag;//食物是否被吃DWORD color;//食物颜色
}food;
对于坐标的实现:
选中POINT后转到定义查看
食物颜色的实现:
与查看坐标的实现方法是一样的,大家理解一下就行
游戏数据的初始化
//数据的初始化
void GameInit()
{//播放背景音乐mciSendString("open./res/mmm.mp3 alias BGM", NULL, 0, NULL);mciSendString("play BGM repeat", NULL, 0, NULL);//inint 初始化 graph 图形窗口 SHOWCONSOLE显示控制台initgraph(640, 480 /*SHOWCONSOLE*/);//蛇的初始化,起始有三节snake.size = 3;snake.speed = 10;snake.dir = RIGHT;for (int i = 0; i < snake.size; i++){snake.coor[i].x = 40 - 10 * i;snake.coor[i].y = 10;printf("?%d %d", snake.coor[i].x, snake.coor[i].y);}//设置随机数种子 GetTickCount获取系统开机经过的毫秒数srand(GetTickCount());//初始化食物 rand()随机产生一个整数,需要设置一个生产随机数的种子food.x = rand() % 640;food.y = rand() % 480;food.color = RGB(rand() % 256, rand() % 256, rand() % 256);food.r = rand() % 10 + 5;food.flag = true;
}
加载音乐
#include<mmsystem.h>
#pragma comment(lib, "winmm.lib")
//播放背景音乐
mciSendString("open./res/mmm.mp3 alias BGM", NULL, 0, NULL);
mciSendString("play BGM repeat", NULL, 0, NULL);
这位博主关于mciSendString函数介绍的很详细:
https://blog.csdn.net/m0_73633088/article/details/128371136
图形窗口的设置:
initgraph(640, 480 /*SHOWCONSOLE*/);
initgraph(int x, int y, /*SHOWCONSOLE*/), /*SHOWCONSOLE*/用来显示终端窗口
蛇身节数的初始化:
蛇头肯定是在蛇身前的,在初始化蛇身节数时节数要加在头后面,而各个节数之间其实是两个半径为5的圆形相切而成,下面这段代码就是对上面这句话的实现:
snake.coor[i].x = 40 - 10 * i;snake.coor[i].y = 10;
食物的初始化
由于食物是随机出现的,我们需要一个随机数来生成食物出现的坐标与半径,sand()是随机生成一个整数,但是此后这个整数不再变化,所以我们还需要srand(随机数)生成一个伪随机数(种子)。
对于如何判断食物是否被吃掉,我们使用布尔型的flag,定义ture的值为1(食物存在),定义false的值为0(食物消失)
绘制游戏元素
void GameDraw()
{//双缓冲绘图BeginBatchDraw();//设置背景颜色setbkcolor(RGB(28, 115, 119));cleardevice();//绘制蛇setfillcolor(GREEN);for (int i = 0; i < snake.size; i++){solidcircle(snake.coor[i].x, snake.coor[i].y, 5);}//绘制食物if (food.flag){solidcircle(food.x, food.y, food.r);}EndBatchDraw();
}
这段代码中的库函数都包含在#include<graphics.h>头文件中
这段代码,就是对上面布尔型数据的应用,如果food.flag = ture,则进行食物的绘制
移动蛇
//移动蛇
void snakeMove()
{//让身体跟着蛇头移动for (int i = snake.size - 1; i > 0; i--){snake.coor[i] = snake.coor[i - 1];}//移动是坐标发生改变switch (snake.dir){case UP:snake.coor[0].y -= snake.speed;if (snake.coor[0].y + 10 <= 0)snake.coor[0].y = 480;break;case DOWN:snake.coor[0].y += snake.speed;if (snake.coor[0].y - 10 >= 480)snake.coor[0].y = 0;break;case LEFT:snake.coor[0].x -= snake.speed;if (snake.coor[0].x + 10 <= 0)snake.coor[0].x = 640;break;case RIGHT:snake.coor[0].x += snake.speed;if (snake.coor[0].x - 10 >= 640)snake.coor[0].x = 0;break;}}
我们在移动蛇时,肯定不能只让蛇头去移动,蛇的身子也必须跟着移动,所以我们要把上一节的坐标赋值给下一节,使它们连贯,实现如下:
下面我们想一下,蛇移动是什么发生改变?是坐标!
那么蛇的移动速度呢?是在你设置的单位时间内移动的距离
在坐标系内,向上移动是y值做减法,向下移动是y值做加法,向右移动是x值做加法,向左移动是做减法,实现如下:
贪吃蛇的穿墙实现
穿墙,本质上也是坐标的变化,比如向右走到有边界,右边界的坐标是(640,y),只有当蛇的尾部即最身子尾部的圆的左侧与其边界相切时,判断穿过,这时我们要把头部的坐标赋值为(0,y),实现如下:
通过键盘移动蛇和暂停游戏
//通过按键改变蛇的移动方向
void keyControl()
{//判断有没有按键,有按键返回真if (_kbhit()){//72 80 75 77 上下左右键值switch (char ch = _getch()){case'w':case'W':case 72:if (snake.dir != UP){snake.dir = UP;}break;case's':case'S':case 80:if (snake.dir != UP){snake.dir = DOWN;}break;case'a':case'A':case 75:if (snake.dir != RIGHT){snake.dir = LEFT;}break;case'd':case'D':case 77:if (snake.dir != LEFT){snake.dir = RIGHT;}break;case ' ': system("pause");}}}
_kbbit()函数作用
_kbbit()函数的作用是检查控制台窗口的按键是否被按下。其格式为
int _kbhit( void );
如果在调用该函数时,有按键被按下,则返回一个非零值,否则该函数的返回值是0。需要注意的是,该函数是一个非阻塞函数,不管有没有按键被按下,该函数都会立即返回。_khbit()函数一般与_getch()函数与getche()函数组合使用获取按键信息。
参考自:https://blog.csdn.net/hou09tian/article/details/86668083
蛇的移动判定
如果在向下移动蛇的过程,我们能让蛇不拐弯直接换个方向向上移动吗?显然是不行的。
当我们向右地动时,按“A”向左移动就不能让程序判定蛇向左移动,代码实现如下:
在这里我们也可以看到枚举类型的奇妙应用,是不是很有意思。
暂停游戏
只需要点击空格就可以暂停游戏,其中system()包含在windows<stdio.h>头文件中
判断蛇吃到食物
//判断蛇吃到食物
void EatFood()
{if (food.flag && snake.coor[0].x >= food.x - food.r && snake.coor[0].x <= food.x + food.r && snake.coor[0].y >= food.y - food.r&& snake.coor[0].y <= food.y + food.r){food.flag = false;snake.size++;}//如果食物消失,重新生成if (!food.flag){//设置随机数种子 GetTickCount获取系统开机经过的毫秒数srand(GetTickCount());//初始化食物 rand()随机产生一个整数,需要设置一个生产随机数的种子food.x = rand() % 640;food.y = rand() % 480;food.color = RGB(rand() % 256, rand() % 256, rand() % 256);food.r = rand() % 10 + 5;food.flag = true;}}
蛇吃到食物后的增长
蛇可以从上下左右四个方向吃到食物,并且是当蛇的头部碰到食物时判定吃到,即蛇的头部的(圆)与食物(圆)相切。实现如下:
在main函数中调用各个函数
int main()
{GameInit();GameDraw();while (1){GameDraw();snakeMove();keyControl();EatFood();Sleep(200);}return 0;
}
对于一些函数而言需要再while(1)中,防止一次就运行结束,得不到持续操作的效果。
一些操作问题
有时候即便写出程序也不能正常玩耍,解决方法在下面:
键盘控制不了蛇的移动:
打开 win11 的系统设置 -> 隐私和安全性 -> 开发者选项,找到“终端”项,里面有三项:“让 Windows 决定(默认)、Windows 控制台主机、Windows 终端”,改为“Windows 控制台主机”即可
然后键盘改为英文输入,鼠标点击终端命令窗口,确保是选中状态
制作不易,请点个呗!