C++基于EasyX制作贪吃蛇游戏(一)文档
本文首发于我的个人博客www.colourso.top,欢迎来访。
原文链接:http://www.colourso.top/c-snake-pro0/
原文写于2019-06-21,本次重新整理。
前提:EasyX
EasyX 是针对 C++ 的图形库,可以帮助 C 语言初学者快速上手图形和游戏编程。
官网链接:https://easyx.cn/
制作流程
先开始写文档,分析要实现哪些功能,然后对功能进行细分,梳理操作。以下80%的内容都是当时写贪吃蛇的文档,后期整理一下发博客记录。
草稿:想要实现的功能
贪吃蛇游戏的简单版本实现
- 贪吃蛇吃食物身体加长
- 食物被吃后随机出现(不能够出现在蛇的身上)
- 无操作时贪吃蛇自动向蛇头方向前进
- 上下左右移动位置
- 撞到身体或者墙壁死亡
- 显示时间与分数
功能梳理
蛇的操作
- 上下左右四个按键控制蛇头的朝向。当按键方向与蛇头方向相同或者相反时无反应。
- 不受操作时蛇自动按照蛇头方向前行
- 蛇头吃到食物之后,身体加长
- 蛇头撞到自身或者墙壁后死亡
- 蛇头控制方向,其余结点均是复制前一个结点的坐标。
食物的操作
- 初始时刻食物出现在某个固定位置
- 食物被吃掉后随机出现在其他地方,但是不能够出现在蛇的身上。
界面显示
- 分为游戏区与功能区,游戏区展现蛇与食物,功能区展示时间、分数、玩法介绍以及作者信息
- 蛇使用方块表示,蛇头与蛇身容易区分。食物使用圆形表示。
- 游戏结束后,显示“GAME OVER”,然后显示游戏时长以及游戏分数。
界面大小设置
- 坐标系。这是一个纯2D游戏,一般来说坐标都是在最左上角,为了便于计算(个人习惯),将坐标原点放置在左下角,x轴向右,y轴向上。
实际上游戏的坐标都是采用原点在左上角,这里因为我设置了坐标轴的原因导致了后面文字摆放无法放置正确,也是因为这个原因,要重写了第二版代码。
- 整体界面大小为:长640px,宽480px。游戏区为 480 * 480的正方形区域,位居界面左侧。功能区为 160 * 480的区域,位居界面右侧。
- 游戏区全部分为20 * 20 px的小正方形区域,便于蛇与食物的放置即坐标计算。蛇使用方块,食物使用圆形。采取图形中心点代表整个图形的方式,即(10,10)表示一个图形。为了防止边界的图像颜色干扰,设置蛇的方块中心点到四边的距离为9px,食物的半径为8px。
变量结构设置
食物结构体设置
对于食物而言,一次只会出现一个食物,只需要保存它的坐标位置,以及存在状态即可。
struct Food //食物结构体
{
int x; //横坐标
int y; //纵坐标
bool exist; //是否存在,1表示存在
};
蛇结构体设置
对于蛇而言,蛇是由一个个节点构成的,每吃一个食物就会增加一个节点。并且所有节点都连接起来,采用链表是一个不错的选择。
结构体设置模仿自严蔚敏老师的数据结构链表的设置。定义蛇节点结构体,然后定义蛇的结构体。
本来采用的单向链表,但后续处理蛇的移动算法时发现需要从后向前复制节点的操作,于是改成了双向链表。
这里也可以采用数组啦、STL的vector容器啦来处理蛇的结构体。采用链表复习复习数据结构。
typedef struct Node //蛇的节点
{
int x; //横坐标
int y; //纵坐标
struct Node * next; //指向下一个节点的指针
struct Node * pre; //指向前一个节点的指针
}* LinkNode;
struct Snake //蛇的结构体
{
LinkNode head; //指向头节点的指针
LinkNode tail; //指向尾节点的指针
int direction; //蛇头方向
int num; //节点数目
};
游戏核心算法
蛇身移动算法
贪吃蛇的移动完全是复制头部节点,蛇头控制方向与前进,其他节点均是重复前一个节点的行为。
而在处理蛇移动的时候,先要用后面节点的保存了它的前一个节点的坐标信息。故蛇身移动的算法,是从最后一个节点(tail)向前复制前一个节点的坐标,达成移动效果。
想到这里就把蛇的结构体改为双向链表。便于我的操作。
LinkNode linknode = snake.tail;
while (linknode != snake.head)
{
linknode->x = linknode->pre->x;
linknode->y = linknode->pre->y;
linknode = linknode->pre;
}
食物生成算法
食物生成要具有随机性,并且要求食物不能够生成在蛇的身体上。
随机数函数rand()产生随机数,rand()函数需要的头文件是:<stdlib.h>。
rand()会返回一个范围在0到RAND_MAX(32767)之间的伪随机数(整数)。
在调用rand()函数之前,可以使用srand()函数设置随机数种子,如果没有设置随机数种子,rand()函数在调用时,自动设计随机数种子为1。随机种子相同,每次产生的随机数也会相同。
srand( usigned int seed)函数用来设置rand()产生随机数时的随机数种子。参数seed是整数,通常可以利用time(0)的返回值作为seed。srand()函数需要的头文件是:<stdlib.h>
例如:生成0-6之间的任意一个随机数
srand(time(0));
int num = (rand()%7)//模求余
食物不能够在蛇的身体上,即生成坐标之后遍历蛇的节点进行匹配,若有对应的就直接重新生成一次。
int x;
int y;
while (1)
{
srand(time(0));
x = (rand() % 24) * 20 + 10;
y = (rand() % 24) * 20 + 10;
//食物位置检测算法
LinkNode linknode = snake.head;
bool cont = true;
while (linknode != snake.tail->next)//从头遍历到尾巴
{
if (linknode->x == x && linknode->y == y)
{
cont = false;
break;
}
linknode = linknode->next;
}
if (cont)
{
break; //如果食物不在蛇的身体上,就break退出循环
}
}
food.x = x;
food.y = y;
food.exist = true;
按键处理问题
_khbit()
函数用来检测是否有键盘输入。如果有按键被按下,会返回一个1,否则返回值为0.它是一个非阻塞函数,无论有没有按键被按下,他都会立即返回结果。可以用来做循环的条件判断是否有按键,或者等待输入。
_getch()
函数作用是从控制台获取输入的字符,并返回获取到的字符值。而且这个函数是阻塞性函数,必须要获取输入字符后才会返回。
_kbhit()
与_getch()
都是位于conio.h
头文件中。(console IO)
int keys = 0;
int key;
while (1)
{
if (keys = _kbhit())
{
key = _getch();
cout << "按键的值key:" << key << endl;
}
}
由此可以测的我们按键对应的键值是多少。
方向键↑ 对应值为72。
方向键↓ 对应值为80。
方向键← 对应值为75。
方向键→ 对应值为77。
但是相信执行这一段代码会发现按一次键会有两个key被打印出来。但是写switch语句(代码中的按键处理模块)时却完全和第一个key值无关。这个问题暂时还没有解决!路过的大佬欢迎留言~
这个方法我是从某潭某州的公开广告课上学到的。
其他参考链接:C++中_kbhit()函数与_getch()函数
伪流程
int main()
{
初始化资源;
绘制界面;
开始游戏:
while(1)
{
if(!食物存在)
{
刷新食物并显示
}
if(按键)
{
相应操作:蛇头方向:上下左右
}
向蛇头方向前进1格。绘制蛇头
(switch case结构)
消除蛇尾图像。
judge(蛇头吃食物)
{
蛇结点+1,尾巴加长并显示出来
分数增加
食物状态刷新
}
judge(撞墙or撞自己)
{
死亡
game over
break
}
sleep(200)
}
结束 回收资源;
}
详细的第一版代码实现:C++基于EasyX制作贪吃蛇游戏(二)第一版代码与程序