这个版本使用了EasyX图形库,使动画效果更加丰富,更加接近真实的小游戏。当然这个“小游戏”并不是真正意义上的小游戏,只是个人对C语言的一些理解与应用。本人水平不高,通过博客来分享自己的学习成果,也算是一种复习。
如果大家不知道什么是EasyX图形库,或者说不知道怎么安装、怎么使用,大家可以去网络上搜索一下。安装过程非常简单,使用起来仅仅针对于贪吃蛇这个项目而言,只需要引头文件,和使用头文件里面的库函数。与#include
需要注意的是,EasyX图形库之适用于C++,但我们可以用C语言的语法。意思就是说,我们需要创建的文件必须是cpp的格式,但我们可以用C的语法。
2.游戏效果在1.0版本中,我们是在控制台窗口下运行的。并且运用到数组。通过数组的状态来描绘空地、食物、蛇的。并且黑框框影响美观、并不好看。并且因为用到了system("cls")清屏,所以屏幕会一卡一卡抽搐,非常影响视觉。
但是在2.0版本中,我们运用了EasyX图形库,这就使得我们不通过控制台那个黑框框来输出图像,而是用EasyX图形库里面的库函数。上下两个GIF动图都是60FPS,但是不难发现2.0版本无论是动画、还是灵敏度,都要优于1.0。
3.设计思路 3.1界面设置我们先将需要的头文件写在head.h头文件底下。
#define _CRT_SECURE_NO_WARNINGS 1
#include //EasyX图形库头文件
#include
随后我们在主函数main.cpp底下 *** 作。
#include "head.h"
int main()
{
initgraph(640, 480);//初始化窗口大小,现在为640*480分辨率
setbkcolor(RGB(255, 148, 209));//设置背景色
cleardevice();//使用背景色清屏
//防闪退
getchar();
closegraph();
return 0;
}
那么我们就得到:
3.2绘制一条蛇出来在绘制蛇之前先讲一个知识点。
这个窗口是有 x、y轴的,并且值增加的方向跟箭头同向。意思就是说,越往右边,x越大;越往下面,y越大。
由此分析,蛇不能够像1.0版本那样用数组来描绘状态了,需要用坐标来描绘蛇。
我们在头文件head.h下定义蛇的结构体,因为需要使用坐标,也同时定义一个坐标结构体:
#define _CRT_SECURE_NO_WARNINGS 1
#include //EasyX图形库头文件
#include
#define NUM 200//默认蛇有200个坐标
//坐标的结构体
struct Coor
{
int x;
int y;
};
//蛇的结构体
struct Snake
{
//蛇结构包括
int len;//长度
int direc;//方向
Coor cr[NUM];//坐标
//一条蛇有N节,每节都有自己的坐标,所以定义一个坐标数组
};
现在蛇的结构已经弄好,接下来就要初始化,也就是把蛇放在哪个位置。
我们在源文件mian.cpp中进入初始化:
#include "head.h"
int main()
{
initgraph(640, 480);//初始化窗口大小,现在为640*480分辨率
setbkcolor(RGB(255, 148, 209));//设置背景色
cleardevice();//使用背景色清屏
Init_Game();//初始化
//防闪退
getchar();
closegraph();
return 0;
}
在头文件head.h中的部分函数声明与宏定义:
#define _CRT_SECURE_NO_WARNINGS 1
#include //EasyX图形库头文件
#include
#define NUM 200//默认蛇有200个坐标
//宏定义各个方向。与1.0不同的是,本版本使用方向键控制的
#define UP 72
#define DOWN 80
#define LEFT 75
#define RIGHT 77
//坐标的结构体
struct Coor
{
int x;
int y;
};
//蛇的结构体
struct Snake
{
//蛇结构包括
int len;//长度
int direc;//方向
Coor cr[NUM];//坐标
//一条蛇有N节,每节都有自己的坐标,所以定义一个坐标数组
};
void Init_Game();//初始化函数声明
现在需要在源文件function.cpp中配置初始化函数:
#include "head.h"
Snake snake;//创建蛇的结构体变量
void Init_Game()
{
snake.len = 3;//初始化长度
snake.direc = RIGHT;//初始化方向
//初始化蛇头坐标
snake.cr[0].x = 100;
snake.cr[0].y = 100;
//蛇身坐标
snake.cr[1].x = 90;
snake.cr[1].y = 100;
snake.cr[2].x = 80;
snake.cr[2].y = 100;
}
现在有了蛇的详细信息,就要开始着手在窗口把蛇绘制出来了,我们从主函数进入绘制函数:
#include "head.h"
int main()
{
initgraph(640, 480);//初始化窗口大小,现在为640*480分辨率
setbkcolor(RGB(255, 148, 209));//设置背景色
cleardevice();//使用背景色清屏
Init_Game();//初始化
Draw_Game();//绘制
//防闪退
getchar();
closegraph();
return 0;
}
要在头文件中进行函数声明,但是这里省略。
现在需要在function.cpp中配置绘制函数:
void Draw_Game()
{
for (int i = 0; i < snake.len; i++)
{
if (i == 0)//如果是蛇头
{
setfillcolor(RED);//填充颜色为红色
//绘制实心矩形,填充颜色为红色
fillrectangle(snake.cr[i].x, snake.cr[i].y, snake.cr[i].x + SIZE, snake.cr[i].y + SIZE);//这里的SIZE我在头文件head.h中宏定义为10了
//四个参数为: x坐标 y坐标 宽 高
}
else
rectangle(snake.cr[i].x, snake.cr[i].y, snake.cr[i].x + SIZE, snake.cr[i].y + SIZE);//绘制非实心矩形
}
}
那么绘制出来的效果就是这样:
3.3蛇移动的实现我们在初始化中,让蛇的移动方向为右。
现在我们需要分析蛇是如何移动的。
我们现在需要通过源文件main.cpp进入移动函数。注意:移动与绘制,是一个循环过程。
#include "head.h"
int main()
{
initgraph(640, 480);//初始化窗口大小,现在为640*480分辨率
setbkcolor(RGB(255, 148, 209));//设置背景色
cleardevice();//使用背景色清屏
Init_Game();//初始化
while (1)
{
Move_Snake();//移动蛇
Draw_Game();//绘制
Sleep(100);
}
//防闪退
getchar();
closegraph();
return 0;
}
在头文件head.h中要引用Sleep函数的头文件,以及移动蛇的函数声明。这里省略。
最后在源文件function.cpp中配置移动蛇函数:
void Move_Snake()
{
cleardevice();//以当前背景色清屏
for (int i = snake.len-1; i > 0; i--)
{
snake.cr[i].x = snake.cr[i - 1].x;
snake.cr[i].y = snake.cr[i - 1].y;
}//数组下标是从 0~n-1 的,所以从i=snake.len-1开始。为什么i要>0而不是>=0
//是因为在循环里面的语句中,有i-1的 *** 作。最后一次是把snake.cr[0]的坐标给snake.cr[1],如果写>=0,就会产生把 snake.cr[-1]的坐标给snake.cr[0]
//蛇头的坐标没有了,现在要产生新的蛇头坐标
switch (snake.direc)
{
case UP:
snake.cr[0].y -= SIZE;
break;
case DOWN:
snake.cr[0].y += SIZE;
break;
case LEFT:
snake.cr[0].x -= SIZE;
break;
case RIGHT:
snake.cr[0].x += SIZE;
break;
}
}
现在实现了蛇向右移动的功能,那么效果是这样的:
现在我们需要控制蛇的方向。我们从源文件main.cpp进入控制蛇的函数:
#include "head.h"
int main()
{
initgraph(640, 480);//初始化窗口大小,现在为640*480分辨率
setbkcolor(RGB(255, 148, 209));//设置背景色
cleardevice();//使用背景色清屏
Init_Game();//初始化
while (1)
{
if (_kbhit())//如果键盘被敲击就进入控制方向函数
Change_Move();//控制蛇
else
{
Move_Snake();//移动蛇
Draw_Game();//绘制
Sleep(100);
}
}
//防闪退
getchar();
closegraph();
return 0;
}
我们需要在头文件head.h中定义_kbhit函数的头文件以及控制蛇的函数声明。这里省略。
现在要在源文件function.cpp中配置控制蛇函数:
void Change_Move()
{
int key = 0;
key = _getch();//接收键盘值
switch (key)
{
case UP:
if (snake.direc != DOWN)//如果蛇正在往下,就不能改变方向。
snake.direc = UP;
break;
case DOWN:
if (snake.direc != UP)//如果蛇正在往上,就不能改变方向。
snake.direc = DOWN;
break;
case LEFT:
if (snake.direc != RIGHT)//如果蛇正在往右,就不能改变方向
snake.direc = LEFT;
break;
case RIGHT:
if (snake.direc != LEFT)//如果蛇正在往左,就不能改变防线
snake.direc = RIGHT;
break;
}
}
那么实现的效果是这样的:
3.4食物的生成食物的生成很简单:第一次初始化生成,我们可以在初始化函数中完成。后续就是蛇吃掉食物后再随机生成。
需要注意的是:我们的蛇是以10为单位移动的,并且每一节的大小都是10*10。我们的食物也要匹配蛇的大小。
判断食物被是否被吃掉非常简单,只要判断蛇头与食物的坐标是否重合就行。
但是我们难就难在定义食物。
在1.0版本中,我们用数组描绘食物的状态。
但是在本版本中,需要使用结构体,因为食物也包含坐标。
所以这里在头文件head.h中定义食物的结构体(包含上面省略的头文件和函数声明):
#define _CRT_SECURE_NO_WARNINGS 1
#include //EasyX图形库头文件
#include
#include //Sleep头文件
#include //kbhit、getch头文件
#define NUM 200//默认蛇有200个坐标
//宏定义各个方向。与1.0不同的是,本版本使用方向键控制的
#define UP 72
#define DOWN 80
#define LEFT 75
#define RIGHT 77
#define SIZE 10
//坐标的结构体
struct Coor
{
int x;
int y;
};
//蛇的结构体
struct Snake
{
//蛇结构包括
int len;//长度
int direc;//方向
Coor cr[NUM];//坐标
//一条蛇有N节,每节都有自己的坐标,所以定义一个坐标数组
};
//食物的结构体
struct Food
{
//因为食物它永远只有一个,所以不需要数组
int x;
int y;
int eat;//确定是否被吃
};
void Init_Game();//初始化函数声明
void Draw_Game();//绘制蛇函数声明
void Move_Snake();//蛇移动函数声明
void Change_Move();//控制蛇函数声明
我们要在源文件function.cpp中对食物初始化:
#include "head.h"
Snake snake;//创建蛇的结构体变量
Food food;//创建食物的结构体变量
void Init_Game()
{
snake.len = 3;//初始化长度
snake.direc = RIGHT;//初始化方向
//初始化蛇头坐标
snake.cr[0].x = 100;
snake.cr[0].y = 100;
//蛇身坐标
snake.cr[1].x = 90;
snake.cr[1].y = 100;
snake.cr[2].x = 80;
snake.cr[2].y = 100;
food.eat = 1;//初始化食物是被吃的
}
在绘制之前我们需要定义一下食物的坐标在哪,也就是需要在源文件main.cpp中进入创建食物函数:
定义好了食物的坐标,现在又要回到绘制函数把食物绘制出来:
void Draw_Game()
{
for (int i = 0; i < snake.len; i++)
{
if (i == 0)//如果是蛇头
{
setfillcolor(RED);//填充颜色为红色
//绘制实心矩形,填充颜色为红色
fillrectangle(snake.cr[i].x, snake.cr[i].y, snake.cr[i].x + SIZE, snake.cr[i].y + SIZE);//这里的SIZE我在头文件head.h中宏定义为10了
//四个参数为: x坐标 y坐标 宽 高
}
else
rectangle(snake.cr[i].x, snake.cr[i].y, snake.cr[i].x + SIZE, snake.cr[i].y + SIZE);//绘制非实心矩形
}
//绘制食物
setfillcolor(GREEN);//填充颜色为绿色
fillroundrect(food.x, food.y, food.x + SIZE, food.y + SIZE, 5, 5);//圆角矩形
//6个参数为: x坐标 y坐标 宽 高 这两个是圆角的程度
}
此时的运行效果就是这样的:
3.5吃食物以及蛇身加长这个部分非常简单。
我们先从源文件main.cpp中进入判断是否吃到食物函数:
#include "head.h"
int main()
{
initgraph(640, 480);//初始化窗口大小,现在为640*480分辨率
setbkcolor(RGB(255, 148, 209));//设置背景色
cleardevice();//使用背景色清屏
Init_Game();//初始化
srand((unsigned int)time(NULL));
while (1)
{
if (_kbhit())//如果键盘被敲击就进入控制方向函数
Change_Move();//控制蛇
else
{
Produce_Food();//创建食物
Eat_Food();//判断食物是否被吃
Move_Snake();//移动蛇
Draw_Game();//绘制
Sleep(100);
}
}
//防闪退
getchar();
closegraph();
return 0;
}
我们需要在头文件head.h下进行函数声明。这里省略。
随后在源文件function.cpp中配置是否被吃函数:
void Eat_Food()
{
if (snake.cr[0].x == food.x && snake.cr[0].y == food.y)//如果蛇头的位置是食物
{
food.eat = 1;//食物的状态更新为被吃
snake.len += 1;//蛇身长度+1
}
}
3.6判断蛇头是否撞墙或自己
这个模块与1.0版本一摸一样,判断函数都需要一个变量来接收返回值。
我们在源文件main.cpp中观察逻辑:
#include "head.h"
int main()
{
initgraph(640, 480);//初始化窗口大小,现在为640*480分辨率
setbkcolor(RGB(255, 148, 209));//设置背景色
cleardevice();//使用背景色清屏
Init_Game();//初始化
srand((unsigned int)time(NULL));
int n = 0;
while (1)
{
if (_kbhit())//如果键盘被敲击就进入控制方向函数
Change_Move();//控制蛇
else
{
Produce_Food();//创建食物
Eat_Food();//判断食物是否被吃
n = GameOver();//判断蛇头是否撞墙或自己
if (n == 1)//返回1则撞墙或自己
{
closegraph();//关闭窗口
printf("游戏结束\n");//控制台打印
break;//结束循环
}
else
{
Move_Snake();//移动蛇
Draw_Game();//绘制
Sleep(100);
}
}
}
//防闪退
getchar();
closegraph();
return 0;
}
我们需要在头文件head.h下进行函数声明。这里省略。
然后就是在源文件funtion.cpp中配置函数:
int GameOver()
{
if (snake.cr[0].x < 0 || snake.cr[0].y < 0 || snake.cr[0].x>640 || snake.cr[0].y>480)//如果撞墙
{
return 1;//返回1
}
for (int i = 1; i < snake.len; i++)
{
if (snake.cr[0].x == snake.cr[i].x && snake.cr[0].y == snake.cr[i].y)//如果撞自己
return 1;//返回1
}
return 0;//否则返回0
}
4.完整代码
4.1头文件head.h
#define _CRT_SECURE_NO_WARNINGS 1
#include //EasyX图形库头文件
#include
#include //Sleep头文件
#include //kbhit、getch头文件
//srand函数需要的头文件
#include
#include
#define NUM 200//默认蛇有200个坐标
//宏定义各个方向。与1.0不同的是,本版本使用方向键控制的
#define UP 72
#define DOWN 80
#define LEFT 75
#define RIGHT 77
#define SIZE 10
//坐标的结构体
struct Coor
{
int x;
int y;
};
//蛇的结构体
struct Snake
{
//蛇结构包括
int len;//长度
int direc;//方向
Coor cr[NUM];//坐标
//一条蛇有N节,每节都有自己的坐标,所以定义一个坐标数组
};
//食物的结构体
struct Food
{
//因为食物它永远只有一个,所以不需要数组
int x;
int y;
int eat;//确定是否被吃
};
void Init_Game();//初始化函数声明
void Draw_Game();//绘制蛇函数声明
void Move_Snake();//蛇移动函数声明
void Change_Move();//控制蛇函数声明
void Produce_Food();//创建食物的函数声明
void Eat_Food();//食物是否被吃的函数声明
int GameOver();//判断是否撞墙或自己的函
4.2源文件main.cpp
#include "head.h"
int main()
{
initgraph(640, 480);//初始化窗口大小,现在为640*480分辨率
setbkcolor(RGB(255, 148, 209));//设置背景色
cleardevice();//使用背景色清屏
Init_Game();//初始化
srand((unsigned int)time(NULL));
int n = 0;
while (1)
{
if (_kbhit())//如果键盘被敲击就进入控制方向函数
Change_Move();//控制蛇
else
{
Produce_Food();//创建食物
Eat_Food();//判断食物是否被吃
n = GameOver();//判断蛇头是否撞墙或自己
if (n == 1)//返回1则撞墙或自己
{
closegraph();//关闭窗口
printf("游戏结束\n");//控制台打印
break;//结束循环
}
else
{
Move_Snake();//移动蛇
Draw_Game();//绘制
Sleep(100);
}
}
}
//防闪退
getchar();
closegraph();
return 0;
}
4.3源文件function.cpp
#include "head.h"
Snake snake;//创建蛇的结构体变量
Food food;//创建食物的结构体变量
void Init_Game()
{
snake.len = 3;//初始化长度
snake.direc = RIGHT;//初始化方向
//初始化蛇头坐标
snake.cr[0].x = 100;
snake.cr[0].y = 100;
//蛇身坐标
snake.cr[1].x = 90;
snake.cr[1].y = 100;
snake.cr[2].x = 80;
snake.cr[2].y = 100;
food.eat = 1;//初始化食物是被吃的
}
void Draw_Game()
{
for (int i = 0; i < snake.len; i++)
{
if (i == 0)//如果是蛇头
{
setfillcolor(RED);//填充颜色为红色
//绘制实心矩形,填充颜色为红色
fillrectangle(snake.cr[i].x, snake.cr[i].y, snake.cr[i].x + SIZE, snake.cr[i].y + SIZE);//这里的SIZE我在头文件head.h中宏定义为10了
//四个参数为: x坐标 y坐标 宽 高
}
else
rectangle(snake.cr[i].x, snake.cr[i].y, snake.cr[i].x + SIZE, snake.cr[i].y + SIZE);//绘制非实心矩形
}
//绘制食物
setfillcolor(GREEN);//填充颜色为绿色
fillroundrect(food.x, food.y, food.x + SIZE, food.y + SIZE, 5, 5);//圆角矩形
//6个参数为: x坐标 y坐标 宽 高 这两个是圆角的程度
}
void Move_Snake()
{
cleardevice();//以当前背景色清屏
for (int i = snake.len-1; i > 0; i--)
{
snake.cr[i].x = snake.cr[i - 1].x;
snake.cr[i].y = snake.cr[i - 1].y;
}//数组下标是从 0~n-1 的,所以从i=snake.len-1开始。为什么i要>0而不是>=0
//是因为在循环里面的语句中,有i-1的 *** 作。最后一次是把snake.cr[0]的坐标给snake.cr[1],如果写>=0,就会产生把 snake.cr[-1]的坐标给snake.cr[0]
//蛇头的坐标没有了,现在要产生新的蛇头坐标
switch (snake.direc)
{
case UP:
snake.cr[0].y -= SIZE;
break;
case DOWN:
snake.cr[0].y += SIZE;
break;
case LEFT:
snake.cr[0].x -= SIZE;
break;
case RIGHT:
snake.cr[0].x += SIZE;
break;
}
}
void Change_Move()
{
int key = 0;
key = _getch();//接收键盘值
switch (key)
{
case UP:
if (snake.direc != DOWN)//如果蛇正在往下,就不能改变方向。
snake.direc = UP;
break;
case DOWN:
if (snake.direc != UP)//如果蛇正在往上,就不能改变方向。
snake.direc = DOWN;
break;
case LEFT:
if (snake.direc != RIGHT)//如果蛇正在往右,就不能改变方向
snake.direc = LEFT;
break;
case RIGHT:
if (snake.direc != LEFT)//如果蛇正在往左,就不能改变防线
snake.direc = RIGHT;
break;
}
}
void Produce_Food()
{
int func = 0;//用来判断食物的坐标是否与蛇的坐标重合
if (food.eat == 1)
{
while (1)
{
food.x = rand() % 64 * 10;//rand()%64的区间在0~63,再*10可实现坐标以10为间隔生成坐标 0 10 20...
food.y = rand() % 48 * 10;//rand()%48的区间在0~46,再*10可实现坐标以10为间隔生成坐标 0 10 20...
for (int i = 0; i < snake.len; i++)
{
if (food.x == snake.cr[i].x && food.y == snake.cr[i].y)
{
func = 1;//如果但凡有坐标重合,func改变为1,表重合
break;//退出for循环
}
}
if (func == 0)//如果没有重合
{
food.eat = 0;//食物就要变为未被吃的状态
break;
}
}
}
}
void Eat_Food()
{
if (snake.cr[0].x == food.x && snake.cr[0].y == food.y)//如果蛇头的位置是食物
{
food.eat = 1;//食物的状态更新为被吃
snake.len += 1;//蛇身长度+1
}
}
int GameOver()
{
if (snake.cr[0].x < 0 || snake.cr[0].y < 0 || snake.cr[0].x>640 || snake.cr[0].y>480)//如果撞墙
{
return 1;//返回1
}
for (int i = 1; i < snake.len; i++)
{
if (snake.cr[0].x == snake.cr[i].x && snake.cr[0].y == snake.cr[i].y)//如果撞自己
return 1;//返回1
}
return 0;//否则返回0
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)