C语言差不多学到尾端了,有了语法基础的我就想着做一个小游戏来将自己所学的展现出来。借鉴了一些博主的博客,最终我选择用C语言手打贪吃蛇。我的原始版本的贪吃蛇拥有界面化、可开关音乐、还有三种关卡模式。接下来让我们先简单体验一下我写的贪吃蛇吧!!!
由于我制作的 gif 动图没有声音,所以大家没有感受到音乐的妙处,但是程序本身是有的哦!!!接下来我会为大家介绍一下我用C语言手打贪吃蛇的思路。
整体框架
-
将界面菜单给做出来
- 我们可以使用 EasyX 图形库绘制一个界面(加入头文件
#include <graphics.h>
,而我自己是将一张图片绘制成了界面的背景。 - 在界面上增加我们想要点击的按钮,比如三种模式、开关音乐、退出等等。这里也只是将界面上绘制出我们想要拥有的按键,但是实现功能还在后面。
- 我们可以使用 EasyX 图形库绘制一个界面(加入头文件
-
将每个功能的按键实现
针对界面上的每个按键,我们可以使用 EasyX 图形库中获取鼠标信息的功能,通过点击按钮,通过判断鼠标在哪一块范围,针对不同的信息我们将其功能实现。比如点击退出游戏,我们再绘制一个新的界面,背景就显示再见。
-
先实现两个最简单的功能
- 播放和关闭音乐功能。
- 退出游戏功能。
-
实现三种关卡模式
其实三种关卡模式的逻辑是差不多的,只是我们在最基础的普通模式上面增加了一些限制或者解除了一些限制,所以才有了另外两种模式
-
无尽模式的实现
扫描二维码关注公众号,回复: 13171072 查看本文章- 当我们的鼠标移在普通模式按键的范围后,通过点击左键就开始实现我们的普通模式
- 首先我们要重新绘制一个游戏的界面,而初始界面的形成需要我们给出所有的参数,比如蛇的参数(如初始长度、初始坐标、每节的半径、初始方向、蛇的颜色)、食物的参数(如随机产生的食物的坐标、食物的半径、食物的颜色、食物有没有被吃掉的判断标志)。
- 当我们将这些参数初始化好了之后,我们就可以通过 EasyX 图形库的众多功能将我们这些参数给绘制出来。
- 接下来就是让小蛇动起来。大家可以先思考下小蛇在你不进行控制时怎样勇敢向前的移动呢?并且遇到边界后需要怎样变化?蛇头碰到身体会怎样?
- 之后我们看到一条在图形内移动的小蛇,我们需要通过按键控制小蛇的移动使它接近食物。
- 我们接着要判断有没有吃到食物,如果吃到了,食物的判断标志将变成亦吃掉,并将继续生成新的食物。
- 但是当我们走到这一步的时候,会发现好像已经没有下一步了,所以是结束了吗?当然不是,我们将往复的进行循环(绘制此时蛇的参数—>蛇按蛇头的方向移动—>控制蛇改变移动方向—>判断是否吃到食物),直到蛇头碰到蛇身就死亡。
- 死亡后我们就绘制一个新的界面,背景可以是小菜鸡的图片,哈哈。最终再回到开始菜单。
-
仿照普通模式改变其他关卡模式的功能
菜单界面绘制
菜单界面的绘制其实很简单,其实都是用的 EasyX 图形库里的函数。但是想要使用它还得去安装一下,这一步很简单的,大家可以百度。我就直接上我的代码啦!!!
void menuInit()
{
//创建一个width宽和heigth的界面
initgraph(width, heigth);
//用图1将这个界面填满
loadimage(&img1, "图1.png", width, heigth);
//图1的左上角的顶点为(0,0)
putimage(0, 0, &img1);
//设置填制背景为透明,这样填充文字时背景才是图片的颜色
setbkmode(TRANSPARENT);
//在左上角坐标为(x,y)处输出文字
outtextxy(260, 50, "贪吃蛇大作战");
outtextxy(155, 250, "普通模式");
outtextxy(395, 250, "障碍模式");
outtextxy(275, 250, "无尽模式");
outtextxy(155, 300, "继续游戏");
outtextxy(395, 300, "退出游戏");
outtextxy(155, 350, "打开音乐");
outtextxy(395, 350, "关闭音乐");
//在左上角(x1,y1)和右下角(x2,y2)范围画一个矩形,这个是设置的鼠标点击各按键时的范围
rectangle(150, 245, 225, 270);
rectangle(270, 245, 345, 270);
rectangle(390, 245, 465, 270);
rectangle(150, 295, 225, 320);
rectangle(390, 295, 465, 320);
rectangle(150, 345, 225, 370);
rectangle(390, 345, 465, 370);
}
控制音乐播放/关闭
- 首先我们要加上播放音乐的头文件
#include<mmstream.h>
和 多媒体设备接口#pragma comment(lib ,"winmm.lib")
。 - 再使用
mciSendString(TEXT("open 路径\\音乐名.mp3 alias XXX"), NULL, 0, NULL);
我们的程序就可以播放这个音乐了,并且该音乐使用时叫做 XXX - 播放音乐时我们就使用
mciSendString(TEXT("play XXX repeat"), NULL, 0, NULL);
,其中repeat 是可以删掉的,加它的含义就是让歌曲不断的播放不会结束。 - 关闭音乐时我们就使用
mciSendString(TEXT("close XXX"), NULL, 0, NULL);
实现退出游戏界面化
这个功能超级简单啦!我自己就是绘制了一个新界面,并用了一张可爱的图片表示再见。
上代码!!!
void gameExit()
{
initgraph(width, heigth);
loadimage(&img2, "图2.jpg", width, heigth);
putimage(0, 0, &img2);
}
普通模式参数初始化
-
首先我们要初始化一个新的界面
-
我们要初始化蛇的参数。先通过结构体定义蛇的各类参数,看看我的代码吧!
struct Snake { int size; //蛇的长度 int speed; //蛇的速度 int dir; //蛇的方向 POINT coor[SNAKE_MAX]; //蛇的最大长度 }snake;
其中 POINT 本身就是一个结构体,它里面包含了 x 和 y 坐标
-
我们再初始化食物的参数。
struct Food { int x; int y; int r; bool flag; //1 没有吃掉 0 吃掉了 DWORD color; }food; //typedef unsigned long DWORD; //DWORD 表示 32bit 无符号整数
-
接下来我们则对以上参数进行初始化,上代码!
void gameInit1() { initgraph(width, heigth); //小蛇初始化 snake.size = 3; snake.speed = 10; snake.dir = LEFT; for (int i = snake.size - 1; i >= 0; i--) { snake.coor[i].x = 10 * i + 400; snake.coor[i].y = 30; } //食物初始化 food.r = rand() % 16 + 5; //食物半径控制在5~15 food.x = rand() % width; //初始化x food.y = rand() % heigth; //初始化y food.color = RGB(rand() % 256, rand() % 256, rand() % 256); food.flag = true; }
普通模式游戏界面化
这也是很容易实现的部分,既然有了参数,我们只要对参数通过 EasyX 图形库进行绘制就行
void gameDraw1()
{
loadimage(&img4, "图4.png", width, heigth);
putimage(0, 0, &img4);
//设置绘图填充颜色
setfillcolor(RGB(61, 89, 171));
for (int i = 0; i < snake.size; i++)
{
//将每个坐标绘制成半径为5的园
solidcircle(snake.coor[i].x, snake.coor[i].y, 5);
}
//将绘图填充色修改成食物的颜色
setfillcolor(food.color);
//判断食物存在则在坐标处绘制出半径为食物半径的圆
if (food.flag)
solidcircle(food.x, food.y, food.r);
//结束批量绘制
EndBatchDraw();
}
普通模式小蛇移动
-
我们该怎么让小蛇移动呢?是不是蛇其实都是跟着蛇头走的,再具体一点,每一节都是跟着前一节移动。所以我们只要从最后一节到第二节开始循环,每节的坐标等于前一节就行,而蛇头坐标则要靠此时小蛇前进的方向进行改变。可以使用一个 switch 语句进行改变。
-
我们要思考当蛇头遇到边界了该怎么办。
-
如果蛇头的坐标等于蛇身的坐标,那也就失败了。我们直接上代码吧!
bool snakeMove1() { 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 <= 0) return false; break; case DOWN: snake.coor[0].y += snake.speed; if (snake.coor[0].y >= heigth) return false; break; case LEFT: snake.coor[0].x -= snake.speed; if (snake.coor[0].x <= 0) return false; break; case RIGHT: snake.coor[0].x += snake.speed; if (snake.coor[0].x >= width) return false; break; } for (int i = snake.size - 1; i > 0; i--) { if (snake.coor[0].x == snake.coor[i].x && snake.coor[0].y == snake.coor[i].y) return false; } return true; }
这里当蛇头遇到边界时,我直接在改变后进行边界判断。而蛇头碰到蛇身则是用一个循环进行判断,碰到了就返回 false。因为我各个功能都是模块化的,在使用这条函数时,用一个判断语句就行。所以我只要返回 false ,其实就会实现该局失败的函数,你们则会看到这张图
键盘控制小蛇改变方向
首先我们要先简单知道两个函数
-
kbhit() 函数
- 功能及返回值:检查当前是否有键盘输入,若有则返回一个非0值,否则返回0
- 包含头文件:
<conio.h>
-
getch() 函数
- 功能:从控制台读取一个字符,但不显示在屏幕上
- 包含头文件:
<conio.h>
所以首先要判断我们的键盘上是否有输入,没有就下一步,有就将从键盘上读取的字符进行判断,可以使用一个 switch 语句。上代码!
void keyControl()
{
if (_kbhit())
{
switch (_getch())
{
case 'W':
case 'w':
case 72:
if (snake.dir != DOWN)
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 ' ':
while (_getch() != ' '); //给读入的空格一个死循环,卡住程序
break;
}
}
}
其中我们会发现每个方向都加了一个 if 条件语句,理由很简单,小蛇本来是向左移动的,那我们能直接改变方向让他向右移动吗?程序是可以的,嘿嘿,但是逻辑上确不行,所以需要加一个判断语句。
判断食物状态
这里我们只要蛇挨着食物的范围就可以吃掉呗。如果吃了就重新生成新的食物的参数。就直接上代码啦!
void Eat()
{
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;
//根据食物的大小不同,蛇吃了增加到长度也将不同
switch (food.r / 5)
{
case 1:
snake.size += 1;
break;
case 2:
snake.size += 2;
break;
case 3:
snake.size += 3;
break;
default:
break;
}
}
if (!food.flag)
{
food.x = rand() % width;
food.y = rand() % heigth;
food.color = RGB(rand() % 256, rand() % 256, rand() % 256);
food.r = rand() % 15 + 5;
food.flag = true;
}
}
该局失败界面化
这就可以直接上代码啦!
void gameOver()
{
initgraph(width, heigth);
loadimage(&img3, "图3.jpg", width, heigth);
putimage(0, 0, &img3);
}
总结
-
以上就是大致框架以及普通模式的逻辑和代码了。其实很多地方还是有小问题的,就比如吃食物时会闪现一个点、边界问题没有做好、不能存档读档等等。后面如果有时间的化,我会继续优化,将越来越好的版本给分享出来。
-
然后虽然贪吃蛇是个简单的小游戏,但是当我第一次写出自己敲出的代码,还是成就感满满。对C语言知识的理解其实也是一个很好的巩固。
-
由于以上的代码不是完整的,所以有需要的朋友可以去 我的 github 上查看完整代码。希望大家喜欢咯!!!