控制台小游戏集锦

这里收集了我初次接触C语言游戏开发时的学习笔记,该部分内容是曾在2018年发布的。

当前博客显示的发布时间非真实时间,而是这些内容在当时发布时的最后发布时间。

实现一个简易的反弹球游戏

屏幕坐标

我们要让一个小球在一个给定的平面上弹跳,第一步是什么?

当然是要创造一个平面啊……在控制台上,这个平面的“建立”就可以由一个二重for循环来完成。两个循环变量i和j,走到哪个地方,这里的“坐标”就可以由相应的i,j的值来代替。看一下这张图片,相信您就能大致的明白小球的坐标应该如何表示了。

可能有人会问,我们要坐标干什么?其实,小球在平面上的移动,本质上是小球坐标的改变,我们只需要改变小球的坐标,就可以改变小球的位置。

这个所谓的“坐标系”,严格来说并不可以称之为坐标系,只能说它可以方便我们描述物体在屏幕上的位置。

思考:坐标原点在哪?

打印一个静止的小球

我们使用printf()函数打印小球,利用for循环找到小球的位置。

下面给出一个简单的代码示例:

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>  
const int x = 15;
const int y = 20;
int main() {
for (int i = 0; i < x; i++)
printf("\n");
for (int j = 0; j < y; j++)
printf(" ");
printf("O\n");
return 0;
}

把它改成一个二重循环也可以,但是要通过if else来换行,不如这样实现的简洁一些。执行程序,发现在一个大黑框中出现了一个静止的小球(字母O),就算大功告成。更改x和y的值,就可以改变小球的位置。

当然,如果使用二维数组会让更简单。想一想,怎么做?

让小球动起来

我们已经有了一个静止的小球,现在我们要尝试让这个球上下弹跳。

(暂时不考虑落体和弹跳的物理规律)
先来考虑一下,要让小球一直不停地跳,要怎么办?使用for循环只能显示小球静止,那要让小球动起来,要怎么办?

一个显然的想法是一直不停地使用for循环。可以在打印的for循环之外再加一层管理“时间”的循环。可是那样的程序小球也只能跳动一定时间。

想让小球一直跳动,外面得加一层“死循环”——while(1)。

与此同时,需要注意的是,使用for循环打印换行符或者空格后,那些位置已经有字符存在了,如果直接去打印下一帧的小球肯定是不行的,此时我们需要一个清除屏幕的操作,只需要把以前打印的符号全部删除,然后重新画就可以了。

放心,电脑运行速度很快。这个操作听起来麻烦,但是电脑却可以在几乎一瞬间完成。可以使用位于stdlib.h里的system函数来清屏,只需调用system(“cls”);即可。

好,下面解决最主要的问题,处理下落,上升和碰撞。

我们先考虑单独处理下落。由坐标变化可以得知,下落的本质是x坐标的增加(想一想,为什么)。那么在外层管理“时间”的循环只需要写成管理x的循环就可以啦,每次循环x都要增加,小球看起来就像是“下落的”。

hint:只需要将上一份示例代码的打印部分外面套一层x递增的循环,然后在里面调用一下清屏函数就可以了。

下面,我们要让小球在这个平面的一条直线上来回运动。既然考虑到碰撞,碰撞所需要的边界就是我们需要考虑的问题。需要设置一个“地板”和一个“天花板”,也就是下界和上界。这一点很好实现,设置好常量即可。

再来想下一个问题,如何处理碰撞?我们不考虑现实的物理规律,只需要知道,当球碰到地板或者天花板时,速度的方向应该和原来的方向相反,大小是不需要改变的。(实际碰撞并非如此,我们只是简化了一下)

这里牵扯到了速度,我们设速度为v就好。

那么,坐标的变化,就可以由一个公式来表达。

设原来的x坐标是x __ old,该时刻变化后的坐标是x __ new,那么有:

x __ new = x __ old + v

可能有人会说,这个公式是错误的。为什么?你怎么能直接把坐标和速度相加呢?实际上,这里的“速度”是已经“乘”上了“时间”的。换句话说,这里的v虽然我们设成了“速度”,但它实际上表示的是坐标的改变量,也算作是坐标。这个公式是在死循环下使用的,也就是说每执行一次循环都会使用一次这个公式,这样x就可以不断改变了。

但是,v并不是保持不变的。当小球触碰天花板或地板时,根据我们简化的碰撞规律,速度要变为反向。那么如何体现速度为反向呢?学过高中物理的同学应该知道,速度是一个矢量,它既有大小又有方向。表示速度时所带的正负号不参与比较大小,只代表速度的方向是与给定的正方向相同还是相反。所以,要让速度反向,取一个相反数就好啦。

下面给出一个简单的代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>  
#include <stdlib.h>
#define Top 20
#define Bottom 0
int x = 5; //这里就不能加const了
const int y = 10;
int v = 1;
int main() {
while (1) {
x = x + v;
system("cls");

for (int i = 0; i < x; i++)
printf("\n");
for (int j = 0; j < y; j++)
printf(" ");
printf("O\n");

if (x == Top || x == Bottom)
v = -v;

}
return 0;
}

运行该程序,会发现有一个小球以超级快的速度上下运动。如果想让它慢下来,可以使用Sleep()函数。这个函数可以让程序在执行至某位置时暂停一定时间,该时间由函数的参数决定,参数的单位是毫秒。这个函数在windows.h里,使用函数时不要忘了#include <windows.h>。

斜向弹跳

其实和在一条直线上来回弹跳是一样的,把上下的速度设为vx,左右的速度设为vy就可以。实现方式和上文所述完全相同。设定边界的时候除了Top和Bottom外还要加上左边和右边的“墙”——Left和Right。

绘制边框和添加撞击音效

绘制边框在打印换行时一并完成。此时打印部分需要做少许修改。

我们不能只打印到球为止,而是应该要把整个画布打印出来。所以此时需要二重循环+if判断再输出的结构。

我们来看一下代码:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>  
#include <stdlib.h>
#include <windows.h>
#define Top 20
#define Bottom 0
#define Left 0
#define Right 30
int x = 5;
int y = 10;
int vx = 1;
int vy = 1;
int main() {
while (1) {
x = x + vx;
y = y + vy;
system("cls");

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 == x) && (j == y))
printf("O");
else
printf(" ");
//没有东西的地方一定要打印空格,不能什么也不做,这点一定注意
}
printf("\n");
}

if (x == Top - 1 || x == Bottom + 1)
vx = -vx;
if (y == Left + 1 || y == Right - 1)
vy = -vy;

Sleep(100);
}
return 0;
}

可以看出,在打印部分使用了if
else结构来判断到某个位置应该打印什么。有个比较细节的地方需要说一下。在打印后的碰撞检测里,我们检测的边界不再是Top、Bottom、Left、Right,而是Top + 1、Bottom + 1、Left + 1、Right -1,这是为什么呢?很简单,本来要检测的边界变成了边框,小球的活动范围就缩小了一圈,不然会出现小球进墙的bug。

碰撞音效?其实就是printf(“\a”)啦。在碰撞检测成功后,变换速度的同时加一句这个就可以啦。这其实是系统的提示声,如果想播放其他声音……emmmm后续会说到的。

实现一个简易的打砖块游戏

代码重构

接着昨天的反弹球继续写。运用昨天的反弹球可以写出例如flappy bird,打砖块等游戏的核心部分。

为了方便我们的开发,程序中的各个部分通常要用函数进行封装,这样会使各种功能模块化,方便我们日后的调试和维护。

这里给出一个书上的最初的简易游戏框架。

1
2
3
4
5
6
7
8
9
int main(){  
startup();
while (1){
show();
updateWithInput();
updateWithoutInput();
}
return 0;
}

解释一下那几个函数所需要包含的功能。

startup:游戏数据初始化

show:显示游戏画面

updateWithInput:和用户输入有关的游戏更新

updateWithoutInput:和用户输入无关的游戏更新

以后的程序我尽量都会按照这样的结构进行代码编写,这只是一个最基础的框架,随着游戏变得更复杂,游戏所需要的函数就会变得更多。

好,那么让我们把昨天的框架弹球程序按照这个结构重构一下。

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <stdio.h>  
#include <stdlib.h>
#include <windows.h>
#define Top 20
#define Bottom 0
#define Left 0
#define Right 30

int x,y,vx,vy;

void startup() {
x = 5;
y = 10;
vx = 1;
vy = 1;
}

void show() {
system("cls");
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 == x) && (j == y))
printf("O");
else
printf(" ");
}
printf("\n");
}
}

void updateWithInput() { //该程序不需要用户进行输入,所以该函数是空的,但是不可以删去

}

void updateWithoutInput() {
x = x + vx;
y = y + vy;

if (x == Top - 1 || x == Bottom + 1)
vx = -vx;
if (y == Left + 1 || y == Right - 1)
vy = -vy;

Sleep(100);
}
int main() {
startup();
while (1) {
show();
updateWithInput();
updateWithoutInput();
}
return 0;
}

重构完成。不过该程序在显示方面有一个很明显的缺陷:闪屏现象严重,并且伴随光标胡乱跳动。这是由于system(“cls”)的性能较差导致的。现在来介绍一个新的清屏函数。这里面牵扯到一个叫句柄的东西,不理解的话只需要记住这个函数能干什么就好。下面给出实现方式。

1
2
3
4
5
6
7
void gotoxy(int x, int y) {  
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos;
pos.X = x;
pos.Y = y;
SetConsoleCursorPosition(handle, pos);
}

然后把system(“cls”)替换为gotoxy(0,0)就可以了。

下面我们来解决光标的问题。可以自定义一个HideCursor()函数进行解决。

1
2
3
4
void HideCursor() {  
CONSOLE_CURSOR_INFO cursor_info = { 1,0 };
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}

这个函数只需要调用一次,放在while(1)之前就可以。

添加挡板

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

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

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

全局变量部分:

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((unsignedint)time(NULL));即以时间为种子初始化随机数。srand函数接收一个无符号整型的参数,而time函数的返回值是time_t类型,如果不进行类型转换会产生一个warning:

warning C4244: “参数”: 从“time_t”转换到“unsigned int”,可能丢失数据

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

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

重构

我们用一个二维数组canvas存储需要打印到屏幕上的所有元素。该数组存储几个特定的值,比如0输出空格,1输出小球,在后续添加挡板,砖块时可以用2,3来表示。

ps:我只是说用这个数字来进行表示,并不是要直接打印0,1,2到屏幕上。

(偷个懒,用了以前的代码,该代码只会显示一半的边框,你可以尝试在细节处进行修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void show() {  
gotoxy(0, 0);
for (int i = 0; i < High; i++) {
for (int j = 0; j < Width; j++) {
if (canvas[i][j] == 0)
printf(" ");
else if (canvas[i][j] == 1)
printf("0");
else if (canvas[i][j] == 2)
printf("*");
else if (canvas[i][j] == 3)
printf("#");

}
printf("|\n");
}
for (int j = 0; j < Width; j++)
printf("-");
printf("\n");
printf("score: %d\n", score);

}

添加大量砖块

在采用数组后,初始化砖块变得方便了许多。敲掉砖块的操作也很好写,直接把值3变为值0即可。

在startup函数里可以这样写:

1
2
3
4
5
6
for (int k = left; k <= right; k++)  
canvas[position_x][k] = 2;

for (int k = 0; k < Width; k++)
for (int i = 0; i < High / 4; i++)
canvas[i][k] = 3;

消砖块

判断小球坐标是否与砖块坐标重合即可。其实没那么麻烦,只需要询问一下小球坐标对应的canvas值是不是3就可以了。在敲掉砖块后小球也是要反弹的,规律与撞墙相同。

在updateWithoutInput这样写:

1
2
3
4
5
6
if (canvas[ball_x - 1][ball_y] == 3) {  
ball_vx = -ball_vx;
canvas[ball_x - 1][ball_y] = 0;
score++;
printf("\a");
}

成品

(里面包含了少许我用来debug的东西,无视就好。

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#include <stdio.h>  
#include <stdlib.h>
#include <conio.h>
#include <Windows.h>
#define High 15
#define Width 20
#define debug printf("ok\n");

int ball_x, ball_y;
int ball_vx, ball_vy;
int canvas[High][Width] = { 0 };
int position_x, position_y;
int ridus;
int left, right;
int score;
int SPD;

void gotoxy(int x, int y) {
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos;
pos.X = x;
pos.Y = y;
SetConsoleCursorPosition(handle, pos);
}

void HideCursor() {
CONSOLE_CURSOR_INFO cursor_info = { 1,0 };
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}

void startup() {
ball_x = 10;
ball_y = Width / 2;
ball_vx = -1;
ball_vy = 1;
canvas[ball_x][ball_y] = 1;

ridus = 5;
position_x = High - 1;
position_y = Width / 2;
left = position_y - ridus;
right = position_y + ridus;

for (int k = left; k <= right; k++)
canvas[position_x][k] = 2;

for (int k = 0; k < Width; k++)
for (int i = 0; i < High / 4; i++)
canvas[i][k] = 3;

score = 0;
}
void show() {
gotoxy(0, 0);
for (int i = 0; i < High; i++) {
for (int j = 0; j < Width; j++) {
if (canvas[i][j] == 0)
printf(" ");
else if (canvas[i][j] == 1)
printf("0");
else if (canvas[i][j] == 2)
printf("*");
else if (canvas[i][j] == 3)
printf("#");

}
printf("|\n");
}
for (int j = 0; j < Width; j++)
printf("-");
printf("\n");
printf("score: %d\n", score);

}

void updateWithoutInput() {
if (ball_x == High - 2) {
if ((ball_y >= left) && (ball_y <= right)) {
//printf("\a");
}
else {
printf("GG\n");
system("pause");
exit(0);
}
}
canvas[ball_x][ball_y] = 0;
ball_x = ball_x + ball_vx;
ball_y = ball_y + ball_vy;
canvas[ball_x][ball_y] = 1;

if ((ball_x == 0) || (ball_x == High - 2)) {
ball_vx = -ball_vx;
}
if ((ball_y == 0) || (ball_y == Width - 1)) {
ball_vy = -ball_vy;
}

if (canvas[ball_x - 1][ball_y] == 3) {
ball_vx = -ball_vx;
canvas[ball_x - 1][ball_y] = 0;
score++;
printf("\a");
}

Sleep(100);
}

void updateWithInput() {
char input;
if (_kbhit()) {
input = _getch();
if ((input == 'a') && (left > 0)) {
canvas[position_x][right] = 0;
position_y--;
left = position_y - ridus;
right = position_y + ridus;
canvas[position_x][left] = 2;
}
if ((input == 'd') && (right < Width - 1)) {
canvas[position_x][left] = 0;
position_y++;
left = position_y - ridus;
right = position_y + ridus;
canvas[position_x][right] = 2;
}
}
}
int main() {
startup();
HideCursor();
while (1) {
show();
updateWithInput();
updateWithoutInput();
}
return 0;
}

实现一个简易的flappy bird游戏

最后一个游戏示例了。后续将介绍easyX以及使用easyX包装后的程序。

显示小鸟

对flappy bird中小鸟的控制与之前在反弹球中介绍的差不多。我们还是用二维数组当作画布,先画一个随时间下落的小鸟,然后通过按下空格让小鸟的位置上升,代码如下。

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <stdio.h>  
#include <stdlib.h>
#include <conio.h>
#include <windows.h>

#define High 15
#define Width 20

int bird_x, bird_y;
int canvas[High][Width];

void gotoxy(int x, int y) {
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos;
pos.X = x;
pos.Y = y;
SetConsoleCursorPosition(handle, pos);
}

void HideCursor() {
CONSOLE_CURSOR_INFO cursor_info = { 1,0 };
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}

void startup() {
memset(canvas, 0, sizeof(canvas));
bird_x = High / 2;
bird_y = 3;

canvas[bird_x][bird_y] = 1;
}

void show() {
gotoxy(0, 0);
for (int i = 0; i < High; i++) {
for (int j = 0; j < Width; j++) {
if (canvas[i][j] == 1)
printf("@");
else
printf(" ");
}
printf("\n");
}
}

void updateWithInput() {
char input;
if (_kbhit()) {
input = _getch();
if (input == ' ') {
canvas[bird_x][bird_y] = 0;
bird_x -= 3;
canvas[bird_x][bird_y] = 1;
}
}
}

void updateWithoutInput() {
Sleep(150);
canvas[bird_x][bird_y] = 0;
bird_x++;
canvas[bird_x][bird_y] = 1;

}

int main() {
startup();
HideCursor();
while (1) {
show();
updateWithInput();
updateWithoutInput();
}
return 0;
}

制造障碍物

其实没必要在设置的时候把整个障碍物都设置出来。想一下,flappy bird的障碍物是长什么样的?其实,我们只需要设定三个值就可以表示一个障碍物。

我们默认同屏有三个障碍物出现。笔者尝试用数组优化实现但是无果,最后选择使用单独的变量。

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#include <stdio.h>  
#include <stdlib.h>
#include <conio.h>
#include <windows.h>
#include <time.h>

#define High 20
#define Width 30
#define Barnum 3

int bird_x, bird_y;
int canvas[High][Width];

int bar1_y, bar2_y, bar3_y;
int bar1_xtop, bar2_xtop, bar3_xtop;
int bar1_xbottom, bar2_xbottom, bar3_xbottom;

void gotoxy(int x, int y) {
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos;
pos.X = x;
pos.Y = y;
SetConsoleCursorPosition(handle, pos);
}

void HideCursor() {
CONSOLE_CURSOR_INFO cursor_info = { 1,0 };
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}

void startup() {
memset(canvas, 0, sizeof(canvas));
bird_x = High / 2;
bird_y = 3;
canvas[bird_x][bird_y] = -1;

bar1_y = Width / 2 - 5;
bar2_y = bar1_y + 9;
bar3_y = bar2_y + 9;

int temp = rand() % int(High * 0.8);
bar1_xbottom = temp - High / 10;
bar1_xtop = temp + High / 10;

temp = rand() % int(High * 0.8);
bar2_xbottom = temp - High / 10;
bar2_xtop = temp + High / 10;

temp = rand() % int(High * 0.8);
bar3_xbottom = temp - High / 10;
bar3_xtop = temp + High / 10;

canvas[bar1_xtop][bar1_y] = 1;
canvas[bar1_xbottom][bar1_y] = 1;

canvas[bar2_xtop][bar2_y] = 1;
canvas[bar2_xbottom][bar2_y] = 1;

canvas[bar3_xtop][bar3_y] = 1;
canvas[bar3_xbottom][bar3_y] = 1;

for (int i = 0; i < High; i++) {
for (int j = 0; j < Width; j++) {
if ((j == bar1_y) && ((i < bar1_xbottom) || (i > bar1_xtop)))
canvas[i][j] = 1;
if ((j == bar2_y) && ((i < bar2_xbottom) || (i > bar2_xtop)))
canvas[i][j] = 1;
if ((j == bar3_y) && ((i < bar3_xbottom) || (i > bar3_xtop)))
canvas[i][j] = 1;
}
}

}

void show() {
gotoxy(0, 0);
for (int i = 0; i < High; i++) {
for (int j = 0; j < Width; j++) {
if (canvas[i][j] == -1)
printf("@");
else if (canvas[i][j] == 1)
printf("*");
else
printf(" ");
}
printf("\n");
}
}

void updateWithInput() {
char input;
if (_kbhit()) {
input = _getch();
if (input == ' ') {
canvas[bird_x][bird_y] = 0;
bird_x -= 3;
canvas[bird_x][bird_y] = -1;
}
}
}

void updateWithoutInput() {
Sleep(150);
canvas[bird_x][bird_y] = 0;
bird_x++;
canvas[bird_x][bird_y] = -1;

}

int main() {
startup();
HideCursor();
while (1) {
show();
updateWithInput();
updateWithoutInput();
}
return 0;
}

目前这样的障碍物是没办法动的,下一步我们让它动起来。

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
void updateWithoutInput() {  
Sleep(200);
canvas[bird_x][bird_y] = 0;
bird_x++;
canvas[bird_x][bird_y] = -1;
for (int i = 0; i < High; i++) {
for (int j = 0; j < Width; j++) {
if ((j == bar1_y) && ((i < bar1_xbottom) || (i > bar1_xtop)))
canvas[i][j] = 0;
if ((j == bar2_y) && ((i < bar2_xbottom) || (i > bar2_xtop)))
canvas[i][j] = 0;
if ((j == bar3_y) && ((i < bar3_xbottom) || (i > bar3_xtop)))
canvas[i][j] = 0;
}
}


canvas[bar1_xtop][bar1_y] = 0;
canvas[bar1_xbottom][bar1_y] = 0;
bar1_y--;
canvas[bar1_xtop][bar1_y] = 1;
canvas[bar1_xbottom][bar1_y] = 1;

canvas[bar2_xtop][bar2_y] = 0;
canvas[bar2_xbottom][bar2_y] = 0;
bar2_y--;
canvas[bar2_xtop][bar2_y] = 1;
canvas[bar2_xbottom][bar2_y] = 1;

canvas[bar3_xtop][bar3_y] = 0;
canvas[bar3_xbottom][bar3_y] = 0;
bar3_y--;
canvas[bar3_xtop][bar3_y] = 1;
canvas[bar3_xbottom][bar3_y] = 1;


for (int i = 0; i < High; i++) {
for (int j = 0; j < Width; j++) {
if ((j == bar1_y) && ((i < bar1_xbottom) || (i > bar1_xtop)))
canvas[i][j] = 1;
if ((j == bar2_y) && ((i < bar2_xbottom) || (i > bar2_xtop)))
canvas[i][j] = 1;
if ((j == bar3_y) && ((i < bar3_xbottom) || (i > bar3_xtop)))
canvas[i][j] = 1;
}
}
}


其实只需要注意一点就好,就是在新位置绘画之前一定要先清除旧位置的内容。

判断碰撞,记分,循环出现,细节处理

这是最后的内容了。

由于三个障碍物会循环出现,判断时要略微注意。

小鸟的碰撞和用户的输入无关,所以我们修改updateWithoutInput函数。

记分和游戏失败是同时出现的。小鸟过障碍物只会有两个结果,如果过得去就+1分,过不去则宣告游戏失败。

同时要注意,小鸟触底也算游戏失败,如果小鸟试图飞出屏幕,那么我们应该把它限制在最高位置。具体参见代码:(无力优化,有点冗长)

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
#include <stdio.h>  
#include <stdlib.h>
#include <conio.h>
#include <windows.h>
#include <time.h>
#define High 20
#define Width 40
#define Barnum 3

int bird_x, bird_y;
int canvas[High][Width];

int bar1_y, bar2_y, bar3_y;
int bar1_xtop, bar2_xtop, bar3_xtop;
int bar1_xbottom, bar2_xbottom, bar3_xbottom;
int score;
void gotoxy(int x, int y) {
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos;
pos.X = x;
pos.Y = y;
SetConsoleCursorPosition(handle, pos);
}

void HideCursor() {
CONSOLE_CURSOR_INFO cursor_info = { 1,0 };
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}

void startup() {
memset(canvas, 0, sizeof(canvas));
bird_x = High / 2;
bird_y = 3;
canvas[bird_x][bird_y] = -1;

bar1_y = Width / 2 - 5;
bar2_y = bar1_y + 10;
bar3_y = bar2_y + 10;

int temp = rand() % int(High * 0.8);
bar1_xbottom = temp - High / 10;
bar1_xtop = temp + High / 10;

temp = rand() % int(High * 0.8);
bar2_xbottom = temp - High / 10;
bar2_xtop = temp + High / 10;

temp = rand() % int(High * 0.8);
bar3_xbottom = temp - High / 10;
bar3_xtop = temp + High / 10;

canvas[bar1_xtop][bar1_y] = 1;
canvas[bar1_xbottom][bar1_y] = 1;

canvas[bar2_xtop][bar2_y] = 1;
canvas[bar2_xbottom][bar2_y] = 1;

canvas[bar3_xtop][bar3_y] = 1;
canvas[bar3_xbottom][bar3_y] = 1;

for (int i = 0; i < High; i++) {
for (int j = 0; j < Width; j++) {
if ((j == bar1_y) && ((i < bar1_xbottom) || (i > bar1_xtop)))
canvas[i][j] = 1;
if ((j == bar2_y) && ((i < bar2_xbottom) || (i > bar2_xtop)))
canvas[i][j] = 1;
if ((j == bar3_y) && ((i < bar3_xbottom) || (i > bar3_xtop)))
canvas[i][j] = 1;
}
}

score = 0;

}

void show() {
gotoxy(0, 0);
for (int i = 0; i < High; i++) {
for (int j = 0; j < Width; j++) {
if (canvas[i][j] == -1)
printf("@");
else if (canvas[i][j] == 1)
printf("*");
else
printf(" ");
}
printf("\n");
}

printf("得分: %d", score);
}

void updateWithInput() {
char input;
if (_kbhit()) {
input = _getch();
if (input == ' ') {
canvas[bird_x][bird_y] = 0;
if (bird_x - 3 >= 0)
bird_x -= 3;
else
bird_x = 0;
canvas[bird_x][bird_y] = -1;
}
}
}

void updateWithoutInput() {
Sleep(200);
canvas[bird_x][bird_y] = 0;
bird_x++;
canvas[bird_x][bird_y] = -1;

if (bird_x == High - 1) {
printf("GAME OVER\n");
system("pause");
exit(0);
}

if (bird_y == bar1_y) {
if ((bird_x >= bar1_xbottom) && (bird_y <= bar1_xtop))
score++;
else {
printf("GAME OVER\n");
system("pause");
exit(0);
}
}

if (bird_y == bar2_y) {
if ((bird_x >= bar2_xbottom) && (bird_y <= bar2_xtop))
score++;
else {
printf("GAME OVER\n");
system("pause");
exit(0);
}
}

if (bird_y == bar3_y) {
if ((bird_x >= bar3_xbottom) && (bird_y <= bar3_xtop))
score++;
else {
printf("GAME OVER\n");
system("pause");
exit(0);
}
}
for (int i = 0; i < High; i++) {
for (int j = 0; j < Width; j++) {
if ((j == bar1_y) && ((i < bar1_xbottom) || (i > bar1_xtop)))
canvas[i][j] = 0;
if ((j == bar2_y) && ((i < bar2_xbottom) || (i > bar2_xtop)))
canvas[i][j] = 0;
if ((j == bar3_y) && ((i < bar3_xbottom) || (i > bar3_xtop)))
canvas[i][j] = 0;
}
}

canvas[bar1_xtop][bar1_y] = 0;
canvas[bar1_xbottom][bar1_y] = 0;
bar1_y--;
canvas[bar1_xtop][bar1_y] = 1;
canvas[bar1_xbottom][bar1_y] = 1;

canvas[bar2_xtop][bar2_y] = 0;
canvas[bar2_xbottom][bar2_y] = 0;
bar2_y--;
canvas[bar2_xtop][bar2_y] = 1;
canvas[bar2_xbottom][bar2_y] = 1;

canvas[bar3_xtop][bar3_y] = 0;
canvas[bar3_xbottom][bar3_y] = 0;
bar3_y--;
canvas[bar3_xtop][bar3_y] = 1;
canvas[bar3_xbottom][bar3_y] = 1;
for (int i = 0; i < High; i++) {
for (int j = 0; j < Width; j++) {
if ((j == bar1_y) && ((i < bar1_xbottom) || (i > bar1_xtop)))
canvas[i][j] = 1;
if ((j == bar2_y) && ((i < bar2_xbottom) || (i > bar2_xtop)))
canvas[i][j] = 1;
if ((j == bar3_y) && ((i < bar3_xbottom) || (i > bar3_xtop)))
canvas[i][j] = 1;
}
}
if (bar1_y < 0) {
for (int i = 0; i < High; i++) {
for (int j = 0; j < Width; j++) {
if ((j == bar1_y) && ((i < bar1_xbottom) || (i > bar1_xtop)))
canvas[i][j] = 0;
}
}
canvas[bar1_xtop][bar1_y] = 0;
canvas[bar1_xbottom][bar1_y] = 0;
bar1_y = Width;
int temp = rand() % int(High * 0.8);
bar1_xbottom = temp - High / 10;
bar1_xtop = temp + High / 10;

canvas[bar1_xtop][bar1_y] = 1;
canvas[bar1_xbottom][bar1_y] = 0;

for (int i = 0; i < High; i++) {
for (int j = 0; j < Width; j++) {
if ((j == bar1_y) && ((i < bar1_xbottom) || (i > bar1_xtop)))
canvas[i][j] = 1;
}
}
}

if (bar2_y < 0) {
for (int i = 0; i < High; i++) {
for (int j = 0; j < Width; j++) {
if ((j == bar2_y) && ((i < bar2_xbottom) || (i > bar2_xtop)))
canvas[i][j] = 0;
}
}
canvas[bar2_xtop][bar2_y] = 0;
canvas[bar2_xbottom][bar2_y] = 0;
bar2_y = Width;
int temp = rand() % int(High * 0.8);
bar2_xbottom = temp - High / 10;
bar2_xtop = temp + High / 10;

canvas[bar2_xtop][bar2_y] = 1;
canvas[bar2_xbottom][bar2_y] = 0;

for (int i = 0; i < High; i++) {
for (int j = 0; j < Width; j++) {
if ((j == bar2_y) && ((i < bar2_xbottom) || (i > bar2_xtop)))
canvas[i][j] = 1;
}
}
}

if (bar3_y < 0) {
for (int i = 0; i < High; i++) {
for (int j = 0; j < Width; j++) {
if ((j == bar3_y) && ((i < bar3_xbottom) || (i > bar3_xtop)))
canvas[i][j] = 0;
}
}
canvas[bar3_xtop][bar3_y] = 0;
canvas[bar3_xbottom][bar3_y] = 0;
bar3_y = Width;
int temp = rand() % int(High * 0.8);
bar3_xbottom = temp - High / 10;
bar3_xtop = temp + High / 10;

canvas[bar3_xtop][bar3_y] = 1;
canvas[bar3_xbottom][bar3_y] = 0;

for (int i = 0; i < High; i++) {
for (int j = 0; j < Width; j++) {
if ((j == bar3_y) && ((i < bar3_xbottom) || (i > bar3_xtop)))
canvas[i][j] = 1;
}
}
}
}

int main() {
startup();
HideCursor();
while (1) {
show();
updateWithInput();
updateWithoutInput();
}
return 0;
}

easyX到底是个什么鬼东西???

它是一个图形库,很简单,很简陋的图形库。但是由于非常简单易用,所以通常被用来作为初学者接触图形编程或者游戏编程的一个工具。使用easyX可以做出一些很简单的2D游戏。

不过,麻雀虽小五脏俱全,easyX所附带的功能还是非常多的。具体的我会慢慢说。

目前来说它只适用于VC平台。

在easyX的官网https://www.easyx.cn/可以找到它的下载链接和一些技术性帮助文档。

为方便安装,这里给出 官方文档 中提供的安装方法。

安装
系统支持

操作系统版本:Windows XP(sp3) 及以上操作系统。
编译环境版本:Visual C++ 6.0 / 2008 ~ 2017(x86 & x64)。

安装

请下载最新版 EasyX 安装程序,直接运行,并跟随提示安装即可。

安装程序会自动检测您已经安装的 VC 版本,并根据您的选择将对应的 .h 和 .lib 文件安装至 VC 的 include 和 lib
文件夹内。安装程序不会修改注册表或者您本机的其它任何文件。

卸载

由于安装程序并不改写注册表,因此您在“添加删除程序”中不会看到 EasyX
的卸载项。如需卸载,请再次执行对应版本的安装程序,并根据提示卸载。也可以手动将相关的 .h 和 .lib 删除,系统中不会残留任何垃圾信息。

手动安装方法

EasyX 安装程序是用 7-Zip 封装的自解压缩包程序。手动安装时,可以直接用 7-Zip
将安装文件解压,再根据下面的文件列表说明,将解压后的相关文件分别拷贝到 VC 对应的 include 和 lib 文件夹内。或者将所需的 include
和 lib 文件夹放到任意位置,然后在 VC 中增加 Lib 和 Include 的引用路径。

文件列表说明:

EasyX 安装程序
├ include
│ ├ easyx.h // 头文件
│ └ graphics.h // 头文件(模拟 BGI 绘图库)
├ lib
│ ├ VC6
│ │ └ x86
│ │ ├ EasyXa.lib // VC6 库文件(MBCS 版本)
│ │ └ EasyXw.lib // VC6 库文件(Unicode 版本)
│ ├ VC2013
│ │ ├ x64
│ │ │ ├ EasyXa.lib // VC2008 ~ 2013 库文件(x64, MBCS 版本)
│ │ │ └ EasyXw.lib // VC2008 ~ 2013 库文件(x64, Unicode 版本)
│ │ └ x86
│ │ ├ EasyXa.lib // VC2008 ~ 2013 库文件(x86, MBCS 版本)
│ │ └ EasyXw.lib // VC2008 ~ 2013 库文件(x86, Unicode 版本)
│ └ VC2017
│ ├ x64
│ │ ├ EasyXa.lib // VC2015 ~ 2017 库文件(x64, MBCS 版本)
│ │ └ EasyXw.lib // VC2015 ~ 2017 库文件(x64, Unicode 版本)
│ └ x86
│ ├ EasyXa.lib // VC2015 ~ 2017 库文件(x86, MBCS 版本)
│ └ EasyXw.lib // VC2015 ~ 2017 库文件(x86, Unicode 版本)
├ EasyX_Help.chm // 帮助文件
└ Setup.hta // 安装程序项目依赖

EasyX 库采用静态链接方式,不会为您的程序增加任何额外的 DLL 依赖。

这玩意怎么用啊

简单。这个图形库很多功能都已经打包成了函数,只需要来一句#include<graphics.h>就可以愉快地使用easyX啦。

PS:一定要用好它附带的说明文档:

这里面有最官方并且最详细的解释说明。

一个通俗易懂的例子

easyX自带的绘图函数有很多。这里简单举个例子。

一些最基本的图形,比如直线,矩形,圆,它都可以用对应的函数画出来。

这里要注意,它并不是直接把图像画到控制台那个大黑框里,而是需要先进行初始化窗体,然后在这个初始化后的窗体里进行绘画。
(虽然也是大黑框

想要改变颜色的话也可以使用对应的函数。设置线条,填充,前景,背景……等等,都有对应的函数。当然,线条样式什么的也可以设定。

easyX把一些常用的颜色设定好了颜色常量,我们可以直接使用。如果你需要其它颜色,可以通过三原色设置函数RGB()来进行更多颜色设定,它接受三个0~255之间的整型参数。

给出一个简单的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <graphics.h>  
#include <conio.h>

int main() {
initgraph(640, 480);
setcolor(GREEN);
setfillcolor(YELLOW);
fillcircle(233, 233, 100);
circle(233, 233, 200);
setcolor(RED);
line(600, 0, 600, 480);
_getch();
closegraph();
return 0;
}

解释一下吧。initgraph函数用来初始化绘图窗体,setcolor是设置线条颜色,setfillcolor是设置填充颜色。

circle函数用来画一个“空心”圆,也就是不使用填充颜色的圆,它接受三个参数,前两个代表画布上的xy坐标(和之前说的一样),第三个代表半径,单位都是像素。

fillcircle同理,不过画的是“实心”圆。

line函数可以画一条直线。我们知道,两点确定一条直线,line函数就是这样,它接受四个参数,其实是代表两个点的坐标,然后它就可以画一条直线。

_getch就是让你按任意键退出的……

closegraph用来关闭图形界面。不关闭会发生什么呢?

实测,什么也不会发生。

这个程序执行效果是这样的:

是不是很cooooooooooooooooooooooooooooooooool?(滑稽

其实好玩的,好康的(误,东西还有很多。

使用easyx画一个动起来的小球

首先让我们画一个不动的小球。

1
2
3
4
5
6
7
8
9
10
11
12
#include <graphics.h>  
#include <conio.h>

int main() {
initgraph(640, 480);
setcolor(LIGHTBLUE);//颜色随意
setfillcolor(BLUE);
fillcircle(100, 100, 30);
_getch();
closegraph();
return 0;
}

还记得之前用数组作为画布时如何更新小球的位置吗?先清除原位置的绘画,再改变坐标,再在新位置上画上小球。使用easyX实现动画一般也分三个步骤:

1. 绘制新图形
2. 延时
3. 清除旧图形

给出一个很简单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <graphics.h>  
#include <conio.h>

int main() {
initgraph(640, 480);
for (int x = 100; x < 500; x++) {
setcolor(LIGHTBLUE);
setfillcolor(BLUE);
fillcircle(x, 100, 20);
Sleep(5);
setcolor(BLACK);
setfillcolor(BLACK);
fillcircle(x, 100, 20);
}
_getch();
closegraph();
return 0;
}

诶?这里为什么不是先清除再绘制呢?

看一下这个例子,在每一次for循环中,我们先在这个位置打印出小球,然后立即删除小球,然后x增加,在新位置打印小球,再删除小球……由于人的视觉暂留现象,你看到的就是一个移动的小球。试想,对于每一次for,如果先删除再绘制,那岂不是删除操作就没有任何意义了?因为你每次都会试图删除一个不存在的小球,因为这个小球根本就没有绘制。

为啥要延时?

不延时小球跑那么快你看得清吗……

实际上,Sleep函数设定的值越低,动画就会看起来越流畅。但是会出现闪屏问题,这是我们不能接受的。

其实我们可以借助批量绘图函数来代替延时功能。它们分别是BeginBatchDraw(),FlushBatchDraw(),EndBatchDraw()。

BeginBatchDraw()用于开始绘图时,该命令执行后任何的绘图操作都暂时不会输出到屏幕上,直到遇到FlushBatchDraw()。EndBatchDraw()用来结束批量绘制,并且执行还没有完成的绘制任务。

下面我们用easyX改进反弹球。

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
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <graphics.h>  
#include <conio.h>
#define High 480
#define Width 640

int ball_x, ball_y;
int ball_vx, ball_vy;
int radius;

int main() {
initgraph(Width, High);
ball_x = Width / 2;
ball_y = High / 2;
ball_vx = 1;
ball_vy = 1;
radius = 30;
BeginBatchDraw();
while (1) {
setcolor(BLACK);
setfillcolor(BLACK);
fillcircle(ball_x, ball_y, radius);
ball_x += ball_vx;
ball_y += ball_vy;

if ((ball_x <= radius) || (ball_x >= Width - radius))
ball_vx = -ball_vx;
if ((ball_y <= radius) || (ball_y >= High - radius))
ball_vy = -ball_vy;

setcolor(LIGHTBLUE);
setfillcolor(BLUE);
fillcircle(ball_x, ball_y, radius);
FlushBatchDraw();
Sleep(5);
}
EndBatchDraw();
closegraph();
return 0;
}

诶,这里怎么是先清除再绘制啊,博主你不是说这没有意义吗?

仔细看代码,我在执行完清除操作后, 将小球位置进行了变动
。来模拟一下这个过程,其实就不难理解了。第一遍循环,首先画一个黑球(这个黑球确实没有意义,但是只在这一遍循环中没有意义,你应该不会介意这个的),然后小球的坐标变化,在新位置画一个有颜色的球。然后下一遍循环会先把上一遍循环打印出的小球涂黑,再变化坐标,画一个新球……其实道理是一样的。

想添加方框?画就是了。然后修改一下边界判定,把方框那块大小“扣出去”就好啦。

其实写到这里,我发现已经没有其他内容了,才想起来,当时学到这里就被叫停了。

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!

扫一扫,分享到微信

微信分享二维码
  • Copyrights © 2018-2023 Shawn Zhou
  • Hexo 框架强力驱动 | 主题 - Ayer
  • 访问人数: | 浏览次数:

感谢打赏~

支付宝
微信