学习编程快一年了,偶然看到网上有做贪吃蛇的视频,于是便一时兴起,用两天时间跟着视频做了起来,在制作过程中也遇到不少问题和陌生的知识,因此通过想博客与大家分享,讨论。(游戏虽然做出来了,但笔者自己也有很多问题一知半解,如有解释不当,欢迎各位解答指正!)
语言:C语言 工具:Visual Studio 2019
注:整个游戏包含7个头文件(3个.h文件,4个.c文件)
.h文件 中一般放的是同名.c文件中定义的变量、数组、函数的声明,声明后.c文件可以直接使用。.c文件一般放的是变量、数组、函数,无需再重复定义。使用.h文件和.c文件的原因主要是为了解决文件编译时重复声明的问题,相对来说使代码结构更清晰(改bug也更快)。前面只放了源代码没有解释,该部分主要针对上面的代码值得推敲的地方,再重新拿出来分析。同时在这部分代码分析中有些地方笔者解释的不够透彻,所以放上链接帮助大家深入了解学习。
首先,本游戏要对线程进程有一定了解。 关于进程和线程的概念,可参照: 链接: C语言多线程编程-进程和线程的基本概念.
在贪吃蛇游戏里,也采用了多线程编程,首先由main函数开启一个进程,然后在一个进程里再分出两个线程。 线程1:蛇的移动。 线程2:通过键盘控制蛇的方向 注:线程也不能多个同时执行,多线程程序也是由cpu轮流执行的,cpu在各线程里来回执行的速度很快,从用户角度上像是几个线程同时执行。
目的:确定蛇的坐标,为蛇在屏幕的显示和移动做准备。
核心代码:
//一定要导入该头文件! #include <Windows.h> void GotoXY(HANDLE hout,int x,int y) { COORD pos; pos.X = x; pos.Y = y; SetConsoleCursorPosition(hout,pos); } void PrintChar(int x, int y, char key) { HANDLE ss = GetStdHandle(STD_OUTPUT_HANDLE); GotoXY(ss, x, y); printf("%c", key); }代码分析:首先介绍两个结构体HANDLE和COORD
COORD是描述位置的结构体,包含x, y坐标 typedef struct _COORD { SHORT X; SHORT Y; } COORD, *PCOORD;HANDLE HANDLE是一种指向结构体的指针,同时也是处理对象的接口,也称句柄,你可以通过句柄来操作程序所涉及的对象。
SetConsoleCursorPosition函数 功能:设置控制台光标坐标实现定位 参数:第一个参数类型为HANDLE,第二个参数类型为COORD
GetStdHandle函数 功能:获取指定的标准设备的句柄 参数:有三种可能的参数取值 STD_INPUT_HANDLE—标准输入句柄
STD_OUTPUT_HANDLE—标准输出句柄
STD_ERROR_HANDLE—标准错误句柄
注:GetStdHandle函数的返回值必须由HANDLE类型的变量接收。且标准输出均是通过屏幕
综上小结 PrintChar函数: 参数:打印字符的x坐标、y坐标、打印的字符。 过程:PrintChar函数先定义了HANDLE类型的ss接收GetStdHandle函数的标准输出句柄。再调用GotoXY函数传入参数x、y坐标和HANDLE指针,GotoXY函数再调用SetConsoleCursorPosition函数确定打印位置坐标,就实现了蛇在屏幕上的打印。
相关知识的学习来源:控制台API函数----HANDLE、SetConsoleCursorPosition、SetConsoleTextAttribute
整个蛇的创建和移动都是通过链表实现的。
//蛇由一个个结点组成,这里先定义结点 struct Point { int x; int y; struct Point* next; }; //蛇头的坐标,这个决定了游戏是否继续,非常重要!!!!! int x; int y; //蛇头指针 struct Point* pHead; 先将蛇打印在屏幕上 添加结点的函数上面有源代码就不列出了 //初始化蛇 void InitSnake() { pHead = NULL; direction = right; //数组的坐标 AddPoint(5,3); AddPoint(5,4); AddPoint(5,5); AddPoint(5,6); } 实现蛇的移动 实现蛇的的移动是在主函数中不断调用创建和销毁结点来实现 1.创建结点 //添加结点 void AddPoint(int x, int y) { //创建新的结点 struct Point* NewPoint = (struct Point*)malloc(sizeof(struct Point)); if (NewPoint == NULL) { //判断结点是否创建成功 return; } //新结点赋值对应的x,y坐标 NewPoint->x = x; NewPoint->y = y; NewPoint->next = NULL; if (pHead != NULL) { //pHead != NULL则为老蛇头存在,将其改变为身体 SetWall(pHead->x, pHead->y, '='); PrintChar(pHead->y * 2, pHead->x, '='); } //NewPoint作为新的蛇头pHead,老蛇头被NewPoint—>next指向 NewPoint->next = pHead; pHead = NewPoint; //数组的坐标为y为横坐标,x为纵坐标,往下x增加,往右是y增加 //光标的坐标为x为横坐标,y为纵坐标,往下y增加,往右是x增加 SetWall(pHead->x, pHead->y, '@'); PrintChar(pHead->y * 2, pHead->x, '@'); }2.删除结点
//删除结点 void DelPoint() { //两个结点以上才能删除 if (pHead == NULL || pHead->next == NULL) { return; } struct Point* pre = pHead; struct Point* cur = pHead->next; while (cur->next!=NULL) { pre = pre->next; cur = pre->next; } SetWall(cur->x, cur->y, ' '); PrintChar(cur->y * 2, cur->x, ' '); free(cur); cur = NULL; pre->next = NULL; } 主函数中用调用添加和删除即可实现移动效果 //添加一个新结点并在尾部结点删除一个即可实现 AddPoint(x, y); DelPoint();注:该部分为数据结构的链表知识
目的:通过键盘输入字符控制贪吃蛇的运动方向
核心代码:
考虑到输入模块只有上下左右键,因此定义为枚举类型,名为EDiection(snake.h) //在snake.h中定义枚举类型的上下左右 enum EDiection { up='w',down='s',left='a',right='d' }; //枚举类型的变量命名direction enum EDiection direction; 接下来在主函数中开启控制蛇方向的线程 //主函数文件要引入<process.h>文件 //开启线程 HANDLE h; h = (HANDLE)_beginthread(MainLop,0,NULL); _beginthread参数分别为:函数的入口地址,栈大小, 参数列表。接下来编写入口函数MainLop: (至于栈大小,参数列表为何是0和NULL,笔者还没查到,知道的朋友欢迎留言) void MainLop() { //prekey是记录上一次蛇运动的方向 char prekey = NULL; while (1) { ch =_getch(); //如果是蛇没有运动 if (ch == NULL && prekey == NULL) { continue; } //如果输入方向恰和蛇运动方向相反 if((ch == up && prekey == down)||(ch == down && prekey == up) ||(ch==left&&prekey==right)||(ch == right && prekey == left)) { ch = prekey; } else { prekey = ch; } //switch语句是通过键盘输入改变运动方向 switch (ch) { case 'w':direction = up; break; case 's':direction = down; break; case 'a':direction = left; break; case 'd':direction = right; break; default:break; } } }注:由于调试过程中存在bug:在蛇运动时按与蛇运动方向相同的键会结束游戏,而且刚开始游戏时蛇就自动跑了起来,最好添加个开始键,因此加入if-else语句,作用是将上一次运动的方向缓存在prekey指针里,和本次按键判断,具体可参照上述代码。
这里引入了一个随机数的生成器来随机设定x,y的坐标。 也可以参见这篇文章帮助理解。 链接: srand((unsigned)time(NULL))详解.
为了额外给游戏制造难度,关卡:“速度随蛇长增加而增加”。 引入了Sleep函数, 功能: 执行挂起一段时间,也就是等待一段时间在继续执行。主函数里每次调用Sleep即通过改变结点函数的速度控制蛇速度的作用。 注:使用Sleep函数需要引入windows.h头文件 核心代码:
//初始化分数 int score = 0; //初始化速度 int vspace = 200; void main() { InitWall(); DrawWall(); SetFood(); InitSnake(); //开启线程 HANDLE h; h = (HANDLE)_beginthread(MainLop,0,NULL); while (1) { //开始蛇不会移动,按任意键启动游戏 if (ch == NULL) { continue; } //通过判断分数提升关卡难度 switch (score) { case 5:vspace = 100; break; case 10:vspace = 70; break; default: break; } //这里分数越高,vspace越高,速度越快 Sleep(vspace);对Sleep函数的详细解释: 链接: 【C/C++】Sleep函数的用法.
以上就是贪吃蛇游戏的全部实现过程了,大家有什么问题或者发现什么错误都欢迎评论区留言,如果喜欢就分享收藏点赞,如果能您能打赏,真的对我非常非常有帮助!感谢支持!
