C++基于EasyX制作贪吃蛇游戏(三)第二版文档

本文最后更新于:2020年5月29日 晚上

填补一下原来留下来的坑,上接 C++基于EasyX制作贪吃蛇游戏(一)文档 继续更新制作贪吃蛇游戏的一些相关设计。

游戏界面设置

游戏界面分为四个区域:

  • 游戏区:贪吃蛇游戏、动画展示的区域。
  • 玩法介绍区:主要描述游戏控制相关内容。
  • 数据展示区:展示游戏的分数、蛇身长度以及速度信息。
  • 游戏状态区:显示游戏当前状态,运行、暂停还是游戏结束。

游戏展示画面的思路

整个程序是基于Easyx图形库进行制作的,所以界面的全部内容都是调用相关函数绘制上去的。

提到绘制,最初的想法就是在整个程序运行的大循环之中,每次循环开始之时重新绘制整个界面的内容,这就需要保存蛇的节点以及食物、还有其他三个区域的信息,每次重新进行绘制。

但是这样感觉效率可能会是问题,所以在设计之初,就采用了局部绘制擦除的方式。

  • 局部绘制:就是单独绘制界面需要更新的位置,而不需要重新绘制整个界面。
  • 擦除方式:这里采用了使用背景色黑色进行覆盖的方式,整个界面背景色都是黑色,不需要的部分直接使用黑色矩阵进行覆盖掉,那么在外观上看来就是擦除的效果。

例如蛇身体的绘制就是采用的局部绘制:

正常情况下蛇前进一格,就是蛇头向某个方向行走一格,然后蛇头后的其他节点都是依次复制前一个节点的位置,然后最后一个节点空余出来。这时候只需要单独绘制一个蛇的头部节点,然后擦除掉蛇的尾部节点走过的地方即可。

当然当数据展示区以及游戏状态区的内容需要更新的时候也是采用擦除然后局部绘制的方式。

当然这样会有一些不妥,如果你想把背景设置为一张图片,这种做法就会失效,当想在图片上绘制内容或者修改内容时,整个显示界面都需要重新进行绘制。

显示蛇头

第一版程序之中没有区分蛇头与蛇身,无论时观察还是游玩都有些不太明显。

因为蛇是一个链表的数据结构,蛇头就是链表的头节点。只需要额外对头节点绘制特殊的图形,然后擦除掉原先头节点即可。

1
2
3
4
//在蛇头部节点的数据更新之后

drawsnakehead(*(snake.head));//画新的头部
drawsnakenode(*(snake.head->next));//将原来的头部变成身体的颜色

蛇的速度

所谓的控制蛇的速度,也就是控制蛇每次前进一格的速度,简单点就是控制每个循环中最终Sleep()的时间。

延迟等待时间越长、蛇的速度也就越慢。

1
2
3
4
5
#define MAX_SPEED 5		//最大速度
#define MIN_SPEED 29 //最小速度
#define ORG_SPEED 18 //原始速度

int g_speed = ORG_SPEED; //控制速度,全局变量

这里定义三个宏,对应允许的最大速度、最小速度和原始速度。

然后定义一个全部变量g_speed来保存速度。

这里的速度对应的Slepp()延迟是1:10的关系,即Sleep(g_speed*10)

按键控制蛇的速度

在设定中c键加速,x键减速,z键恢复原始速度。

就在代码中的按键监听部分加上相应的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
bool speedchange = false;
if (_kbhit())
{
char key = _getch();
switch (key)
{
case 99: //c 加速
if (g_speed > MAX_SPEED)
{
g_speed -= 1;
speedchange = true;
}
break;
case 120: //x 减速
if (g_speed < MIN_SPEED)
{
g_speed += 1;
speedchange = true;
}
break;
case 122: //z 回归原速
g_speed = ORG_SPEED;
speedchange = true;
break;
}
}

如果速度改变了,那就将speedchange置为true,便于之后在数据显示区进行是否刷新的判断。

显示蛇的速度

定义蛇的速度等级不能简单粗暴的从5到29,不直观。直接采用30 - g_speed这样的方式,得到的结果就是1到25级,初始默认是12级,等级越低速度越慢,玩家可以自行调节速度等级。

游戏暂停与继续

刚开始想这个问题的时候头疼了一下,如何让一个死循环停止运行呢,答案是进入另一个死循环就行了。

设定的是空格键暂停与继续,在按键监听那里加入空格键32。当按了空格键之后就直接进入了另一个循环,整个界面游戏循环就暂时停滞了。

在另一个循环中一直处于按键监听等待空格键的再次按下,break循环,从而回到原来的大循环中,继续执行,这样就达到了暂停的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
case 32:         //空格暂停
drawpause(); //状态区显示
while (true)
{
if (_kbhit())
{
char key = _getch();
if (key == 32) //按空格继续
{
clearstate();
drawruntime(); //绘制程序正在运行中
break;
}
}
}
break;

当然要在对应的游戏状态区给出提示,让人知道这个游戏是暂停了,而非卡死了。

游戏重新开始

当失败的时候,可以选择重新开始游戏或者是退出游戏。

如何重新开始一个游戏呢?我的思路是将整个游戏的玩耍过程封装成一个函数,当我们最后游戏结束的时候,调用这个函数就能够重新开始了。

那么以为着要改动一下原来第一版的函数结构了。

  • 将原先程序中的init()函数中的数据初始化部分拿出来创建成initdata()函数。
  • 原先的函数主体是drawgame()绘制游戏初始界面,以及gameplay()游戏玩耍控制,这两个函数。
    • 现在将这两个函数以及initdata()三个函数封装成一个play()函数,当需要开始游戏时直接调用play()函数即可。

当游戏结束的时候会给出提示r键重新开始、q键退出游戏,这一部分也是按键检测和控制的,参照上面的按键检测代码也是很容易写出来的。

其他

关于数据展示以及游戏状态展示这一部分的代码就不详细介绍了,比较简单。

还有问题就是在中文输入法下按键会优先弹出输入法的框,需要手动将输入法切换为英文。

大致这样吧,后续再更新代码。