添加游戏元素——实现简易的打砖块游戏 Part 2

从零开始,一步一步地添加内容,直至它成为一个初始的“游戏”。(图文无关)


前置知识:函数,随机数,强制类型转换(C语言)

添加挡板

既然是打砖块游戏,接砖块的挡板自然是必不可少的。对于一个挡板,我们需要两个参数(其实是三个)来进行决定。一个是挡板的中心坐标,另一个是挡板左右延伸的长度。此外,还需要两个辅助变量来记录挡板左右端点的位置,方便我们进行打印和后续的接球判定。

(实际上,以左端起始,再加一个长度参数也可以,只要能正确表示出一个挡板就可以)

我们在上一篇文章的代码基础上修改三个函数。此外,再添加一些全局变量。

全局变量部分:

1
2
3
4
5
int ball_x, ball_y;
int ball_vx, ball_vy;
int pos_x, pos_y;
int ridus;
int pos_left, pos_right;

初始化:
1
2
3
4
5
6
7
8
9
10
11
12
void startup() {
ball_x = Top - 3;
ball_y = Right / 2;
ball_vx = 1;
ball_vy = 1;
ridus = 5;
pos_x = Top - 2;
pos_y = Right / 2;
pos_left = pos_y - ridus;
pos_right = pos_y + ridus;

}

显示函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void show() {
gotoxy(0, 0);
for (int i = Bottom; i < Top; i++) {
for (int j = Left; j < Right; j++) {
if ((i == Bottom) || (i == Top - 1))
printf("-");
else if ((j == Left) || (j == Right - 1))
printf("|");
else if ((i == ball_x) && (j == ball_y))
printf("O");
else if ((i == Top - 2) && (j >= pos_left) && (j <= pos_right))
printf("=");
else
printf(" ");
}
printf("\n");
}

}

这里要注意,我们的游戏界面存在一个方框,它的上边界是0,由于高度是Top,所以下边界是Top-1,左右边界同理。所以想要把挡板放在离下边界最近的地方的话,就要放在Top-2了。小球的位置倒是可以随意,别放在边界上就好。

运行程序,大抵如此:

可是这个时候挡板是不能运动的,而且小球碰到挡板时会直接把挡板“吃掉”,接下来我们要怎么办呢?

让挡板动起来

要让挡板按照玩家的意愿移动,需要从键盘获取录入信息。

从键盘读取数据?难道要用scanf吗?

实际上,使用scanf输入字符后要按一下回车才可以继续进行,但这显然不是我们想要的。这里我们使用conio.h里面的getch()函数获取输入。getch不需要任何参数,用法和getchar一样的(我相信您会用getchar)。

再来介绍一个函数,它叫kbhit,也是在conio.h里面的一个函数,它可以监听键盘输入,如果有任何键盘输入就会返回1,否则返回0。

这里我用a控制左移,d控制右移。如果想用上下左右来控制也是可以的,这里给出方向键对应的ascii码:

左键:37 上键:38 右键:39 下键:40

判断键盘输入其实很简单,使用if或者switch就可以,当输入为什么按键时要向哪里移动,更新坐标的值即可。

PS:笔者使用Visual Studio 2017 Community最初编译此程序时出现了编译错误的问题,后发现高版本的Visual Studio直接使用getch和kbhit函数会出现编译错误,要让编译通过应该在函数名前加一下划线,具体原因未知。

下面给出修改后的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void updateWithInput() {  
char input;
if (_kbhit()) {
input = _getch();
if (input == 'a') {
pos_y--;
pos_left = pos_y - ridus;
pos_right = pos_y + ridus;
}
if (input == 'd') {
pos_y++;
pos_left = pos_y - ridus;
pos_right = pos_y + ridus;
}
}
}

思路是,先检测有无输入,有则获取输入并判断。为什么不直接用getch呢?如果直接用getch,当程序跑到getch那一行时会一直等待键盘输入,这样会导致一个不按键不进行游戏的bug。更新挡板坐标位置的操作并不难理解,由于打砖块游戏通常不允许挡板上下移动,所以只会有y坐标发生变化。中心坐标发生变化时,其左端点和右端点的坐标也要随之变化。

判定接球和失球

当小球到达游戏画面底部时,应要做一些判断。如果小球即将到达与挡板平齐的位置时,我们要检测小球的y坐标是否在左右端点覆盖的范围之内。如果是则按照反弹规律反弹小球,否则游戏失败。

由于小球的反弹与用户输入没有联系,所以我们修改updateWithoutInput函数。只需要在撞墙反弹前判断是否撞上了挡板就可以。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void updateWithoutInput() {

if (ball_x == Top - 3) {
if ((ball_y >= pos_left) && (ball_y <= pos_right)) {

}
else {
printf("GAME OVER\n");
system("pause");
exit(0);
}
}

ball_x = ball_x + ball_vx;
ball_y = ball_y + ball_vy;

if (ball_x == Top - 3 || ball_x == Bottom + 1)
ball_vx = -ball_vx;
if (ball_y == Left + 1 || ball_y == Right - 1)
ball_vy = -ball_vy;

Sleep(100);
}

目前有一个问题不能解决,那就是“底线”到底在哪。如果把底线放在与挡板相同一行,那么小球会落到边框上才会宣告失败,这和我们的想法是一样的,但是小球打在挡板上时会先进入到挡板里面再反弹,但如果把底线放在挡板上一行,挡板没有接到小球时小球会悬停在半空中宣告失败,这也是不太合适的。但是这个问题等使用了easyX就可以解决。

代码当中有一个if后面执行的语句我留了空,那里可以写一些当小球撞到挡板上可以做的事情,比如记分,播放音效等。

添加得分记录器和一个砖块

得分记录很好实现,设立一个变量score初始化为0,然后在show函数的最后添加一行printf输出这个变量就好。

那么怎样判断是否得分呢?既然是打砖块游戏,那自然是习惯上认作是打掉砖块得分。所以我们来添加一个砖块,没错,先只加一个。

在updateWithoutInput函数里,添加这样的一块代码:

1
2
3
4
5
if ((ball_x == block_x) && (ball_y == block_y)) {
score++;
block_x = rand() % 5 + 1;
block_y = (rand() % (Right - 1)) + 1;
}

我们让这个砖块被敲掉后在一个限定的范围内重新生成一个砖块,同时加1分。在使用前我先初始化了随机数种子,使用了srand((unsigned int)time(NULL));即以时间为种子初始化随机数。srand函数接收一个无符号整型的参数,而time函数的返回值是time t类型,如果不进行类型转换会产生一个warning:warning C4244: “参数”: 从“time t”转换到“unsigned int”,可能丢失数据

好了,现在一个最基础的打砖块游戏就做好了。

下一步我们要添加大量砖块,同时要用到二维数组作为画布,这样的画布从某种方面上讲会更方便。

敬请期待明天的博文:运用数组——实现简易的打砖块游戏 Part 2.5


-------------本文结束,感谢您的阅读转载请注明原作者及出处-------------


本文标题:添加游戏元素——实现简易的打砖块游戏 Part 2

文章作者:Shawn Zhou

发布时间:2018年08月03日 - 20:08

最后更新:2018年12月09日 - 18:12

原始链接:http://yoursite.com/2018/08/03/添加游戏元素——实现简易的打砖块游戏-Part-2/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

知识无价,码字不易。对您有用,敬请打赏。金额随意,感谢关心。