大一新生c语言练手游戏——贪吃蛇
写游戏是每个男生记忆中存在的想法,兴趣使然写一个贪吃蛇也算对大一上学期的一个总结。
编译器:vs
图形库:easyx(下载链接)
easyx拥有相当强大的图像封装函数,使得短短几百行编写一个简单游戏成为可能;
1.创建一个初始窗口
一个游戏当然不能仅仅在小黑框框实现,拥有一个自定义的界面是一个良好的开端
(1)首先需要改变小黑框的大小与颜色,我们可以运用initgraph与setbkcolor函数参数分别为(长,宽) (三原色比例),具体颜色可以调用画板来看
(2)做完第一步运行会发现,屏幕一闪而过,然后便无事发生,这时候我们还需要清屏处理(cleardevice)以及_getch()等待用户输入一个按键;
此刻第一步初始界面就完成了
接着开始第二步,也算是贪吃蛇真正的开始;
我们首先要弄清楚这个游戏所拥有的元素,蛇,食物;
那么蛇又拥有哪些要素,我们该如何从无到有创造出蛇,并让蛇动起来,再是吃掉食物后的增长长度;以及食物被吃掉后的随机生成;蛇的死亡;这便是贪吃蛇游戏的简单框架,知道框架后我们应该逐一实现,继续分析蛇,食物这两个主要对象。
我们将界面分为x,y轴
蛇:
1坐标(x,y)
2运动方向
3长度
食物
1 坐标
2 存在与否
3 分值
既然是简易版,就将就用矩形框将就代表他们了;
分析完对象,我们可以使用结构体来封装他们;
先定义一个x,y坐标结构题方便后续调用,接着由于蛇的长度会变化,也就是蛇的坐标为多个矩形框,这里用一个数组来存放,其余的方向等,依次定义即可。
接着该怎么样让蛇出现在界面当中呢,初始定义完结构体后,我们仅仅需要一些简单的操作就可以实现了;
初始化蛇,我们将刚开始的蛇视为三个矩形,头 ,尾,身,由于数组下标从0开始,我们将数组下标0开始认为是头,可以初始化认为每一个矩形的长度为10,接着长度是3,运动方向为右(此刻可以枚举四个方向),同样的设置自己喜欢的颜色;
做完上述一系列操作之后,我们只需要将蛇打印出来即可。分为三个步骤
1 改变矩形边框颜色(参数为大写英文)
2填充矩形边框(参数为三原色)
3打印(参数为起始坐标与终止坐标)
分别用easyx的三个函数即可解决
成功的初始化蛇,并且打印出了蛇,我们还需要让蛇动起来;
,想让蛇动起来只需要让蛇的坐标变化就好了,这里讲一个个人的理解,我们需要将一个游戏看成一帧帧拼接而成,也就是每一个时刻都会有不同的情况,所以只需要将每一时刻和下一时刻的情况列举出来,然后利用循环即可达成我们想要的结果。
我们可以注意蛇的连贯性,我们可以将除蛇头外,后续的身子坐标变为前一段的坐标,例如一开始蛇头坐标为(3,0),蛇身坐标为(2,0),蛇尾(1,0)那么下一秒,蛇身坐标变为蛇头坐标变为(3,0),蛇尾坐标(1,0)变为前一节蛇身坐标(2,0),而蛇头变为下一时刻坐标根据方向变化,这里可以写一个循环,循环次数小于蛇长,因为下标【0】为蛇头,从snake.num-1蛇尾开始,变为前一段的坐标,。然后我们仅仅需要考虑蛇头方向的变化来考虑坐标变化,及上下左右的情况;上即为y-10,下即为y+10同理分析左右的情况。
然后在主函数中用一个循环输出,别忘了变化一次坐标画一次蛇
然后需要清屏处理,否则前面打印的蛇还会存在;
但是由于计算机实在太太太太太太太快了,所以我们需要将它延迟一下,用sleep(100)
头文件<time.h>;;这里可以感受一下有无sleep的区别,以及()内不同的区别;
看着小蛇丝滑的蠕动在屏幕感觉很快乐,接下来,我们需要控制它的运动。
同样的写一个函数,通过键盘输入的不同来改变方向,这里注意一下不能反方向运动例如向左不能立刻向右这应该比较好理解不然会发生一些奇奇怪怪的事情
接着就可以控制蛇的行动了;
现在我们对于蛇的大致操作已经完成了,贪吃蛇还有一个很重要的对象,就是食物;
食物有一个不一样的地方就是它需要随机生成;于是我们需要一个随机生成函数
如图随机生成种子,再使用rand即可;首先和蛇一样我们需要将食物初始化,然后打印出食物。
这个时候会发现食物四处乱弹;所以我们需要加入一个控制环节,当食物存在时便不要生成
然后就是变长的过程,其实变长就是使得蛇头位置和食物位置相同让两者x,y相同,将蛇长+1,食物flag=0的过程
然后再主函数中添加测试,这时候就能实现基本的贪吃蛇功能了;
接着是蛇死亡的过程,也就是游戏失败
1 撞墙 (越界)2 咬到自己;
同样的,转换为坐标,也就是超过界面范围,以及蛇头碰到自身任意一个地方;
我们用返回值1代表死亡,返回值0代表未死亡,所以这个地方记得int一个函数
最后一个及其简易的贪吃蛇就结束啦。
附上框架和主函数情况
总结:
第一次尝试总是会遇到许多的困难,但是也有十足的收获,虽然只有简单的几个功能,但如果没有一个清晰的逻辑也是难以完成这份任务,通过这个小游戏的编写,更能理解函数的作用,分块测试,将大问题一步步分解为小问题,每编写完一个模块立刻验证自己的程序,及时的修改,而不是一次性完成所有代码来验证,那样也许会被各种奇怪的问题逼疯,其次函数的命名以及变量的命名都是一个程序的可读性重要体现,这里也多亏了结构体让思路更加清晰,否则难以想象数个坐标聚集在一起用各种变量来体现,恐怕自己也会绕晕吧。
代码:
#include<stdio.h>#include<graphics.h>//easy x 图像库头文件#include<conio.h>;//_getch 头文件#include<time.h>//时间//坐标属性typedef struct point{int x, y;}MYPOINT;//蛇属性struct Snake{MYPOINT XY[100];//每一个矩形框都是蛇一个坐标所以用数组储存int position;//运动方向int num;//长度}snake;//食物属性struct{MYPOINT fdxy;//食物位置int flag;//标记是否存在int grade;// 分数}food;//枚举方向enum positon {up, down, left, right };//初始化蛇void initSnake(){snake.XY[2].x = 0;snake.XY[2].y = 0;snake.XY[1].x = 10;snake.XY[1].y = 0;snake.XY[0].x = 20;snake.XY[0].y = 0;snake.position = right;snake.num = 3;}//将蛇画出来void drawSnake(){for (int i = 0; i < snake.num; i++){setlinecolor(BLACK);//将边框设置为黑色setfillcolor(RGB(200, 230, 119));//填充颜色fillrectangle(snake.XY[i].x, snake.XY[i].y, snake.XY[i].x + 10, snake.XY[i].y + 10);//打印范围}}//让蛇动起来void moveSnake(){//蛇身移动for (int i = snake.num - 1; i > 0; i--){snake.XY[i].x = snake.XY[i - 1].x;snake.XY[i].y = snake.XY[i - 1].y;}switch (snake.position){case up:snake.XY[0].y -= 10;break;case down:snake.XY[0].y += 10;break;case left:snake.XY[0].x -= 10;break;case right:snake.XY[0].x += 10;}}//控制蛇void contrlSnake(){char keydown = _getch();switch (keydown){case'w':case'W':if (snake.position != down)snake.position = up;break;case's':case'S':if (snake.position != up)snake.position = down;break;case'a':case'A':if (snake.position != right)snake.position = left;break;case'd':case'D':if (snake.position != left)snake.position = right;break;}}//初始化食物void initFood(){food.fdxy.x = rand() % 80 * 10;//防止越界food.fdxy.y = rand() % 60 * 10;food.flag = 1;//1表示存在食物//食物不能生成在蛇身上for (int i = 0; i < snake.num; i++){if (food.fdxy.x == snake.XY[i].x && food.fdxy.x == snake.XY[i].y){food.fdxy.x = rand() % 80 * 10;food.fdxy.y = rand() % 60 * 10;}}}//画食物void drawFood(){setlinecolor(BLACK);//将边框设置为黑色setfillcolor(RGB(110, 210, 119));//填充颜色fillrectangle(food.fdxy.x, food.fdxy.y, food.fdxy.x + 10, food.fdxy.y + 10);//打印范围}//吃食物void eat(){if (snake.XY[0].x == food.fdxy.x && snake.XY[0].y == food.fdxy.y){snake.num++;food.flag = 0;}}//死亡int snakedie(){if (snake.XY[0].x > 800 || snake.XY[0].y > 600 || snake.XY[0].x < 0 || snake.XY[0].y < 0){return 1;}for (int i = 1; i < snake.num; i++){if (snake.XY[0].x == snake.XY[i].x && snake.XY[0].y == snake.XY[i].y)return 1;}return 0;}int main(){//1.创建初始窗口srand((unsigned int)time(NULL));//随机生成种子initgraph(800, 600);//长800 宽600;(闪屏)setbkcolor(RGB(110, 200, 500));//三原色设置背景颜色cleardevice();//清屏initSnake();drawSnake();while (1){cleardevice();//清屏BeginBatchDraw();//与末尾EndBatchDraw为easy x封装的双缓冲函数//主要用来防止闪屏moveSnake();//蛇动函数drawSnake();//画蛇if (food.flag == 0)//判断食物是否存在{initFood();//不存在变化食物坐标}if (_kbhit())//如果有输入{contrlSnake();//控制蛇}if (snakedie()) break;drawFood();//根据当前食物坐标,画食物eat();//判断是否吃掉食物Sleep(50);//延时EndBatchDraw();}_getch();//等待用户输入防止闪屏closegraph();//关闭地图return 0;}
后续:完成了一个简陋的框架,后续便是增添功能了目前的想法有
:
1.随着时间增加难度不断上升
2.添加bgm以及各类音效
3.地图的改造