目录
前言
在之前的弹跳小球的基础上增加反弹小球的挡板、边框和消除砖块。
代码参考了《C语言课程设计与游戏开发实践教程》
一、小球代码的重构
为了能更好地实现反弹球消除砖块,我先对之前弹跳小球的代码进行重构。这里我不对弹跳小球的代码像上一篇文章一样进行详细的解释,只是在代码中添加注释。
直接看弹跳小球重构后的代码:
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <Windows.h>
// 全局变量
int high, width; // 游戏画面大小
int ball_x, ball_y; // 小球的坐标
int ball_vx, ball_vy; // 小球的速度
// 光标移动到(x,y)位置
void gotoxy(int x, int y)
{
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos;
pos.X = x;
pos.Y = y;
SetConsoleCursorPosition(handle, pos);
}
// 数据初始化
void startup()
{
high = 13;
width = 17;
ball_x = 0;
ball_y = width / 2;
ball_vx = 1;
ball_vy = 1;
}
// 显示画面
void show()
{
// 光标移动到原点位置,以下重画清屏
gotoxy(0, 0);
int i, j;
for (i = 0; i <= high + 1; i++)
{
for (j = 0; j <= width; j++)
{
if ((i == ball_x) && (j == ball_y))
printf("0"); // 输出小球
else
printf(" "); // 输出空格
}
printf("\n");
}
}
// 与用户输入无关的更新
void updateWithoutInput()
{
ball_x = ball_x + ball_vx;
ball_y = ball_y + ball_vy;
if ((ball_x == 0) || (ball_x == high - 1))
ball_vx = -ball_vx;
if ((ball_y == 0) || (ball_y == width - 1))
ball_vy = -ball_vy;
Sleep(80); // 降低小球的跳动速度
}
// 隐藏光标函数
void HideCursor()
{
CONSOLE_CURSOR_INFO cursor_info = {1, 0};
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}
int main()
{
// 数据初始化
startup();
// 隐藏光标
HideCursor();
// 游戏循环执行
while (1)
{
show(); // 显示画面
updateWithoutInput(); // 与用户输入无关的更新
}
return 0;
}
当我们运行这段代码时会发现小球与之前一样跳动了,同时代码也更加清晰了。这是因为用函数实现了功能的模块化,让每一个函数实现一个功能。
函数
1、定义
在维基百科中,函数的定义叫做子程序。
2.分类
(1)库函数:C语言内部提供的函数。
(2)自定义函数:自我发挥写出的函数。
3、具体请看:这位大佬的
二、 实现游戏的边框
当我们成功对代码进行重构,便于后续添加功能后,我们就要开始添加功能了。首先我们来完成游戏的边界的显示,这里我用符号 ' | ' 来显示右边界,符号 ' - ' 来显示下边界(当然你也可以根据自己的喜好来设置边界的样式)。先来看代码:
int i, j;
for (i = 0; i <= high + 1; i++)
{
for (j = 0; j <= width; j++)
{
if ((i == ball_x) && (j == ball_y))
printf("0"); // 输出小球
else if (j == width)
printf("|"); // 输出右边框
else if (i == high + 1)
printf("-"); // 输出下边框
else if ((i == high) && (j > left) && (j < right))
printf("*"); // 输出挡板
else if ((i == block_x) && (j == block_y))
printf("B"); // 输出方块
else
printf(" "); // 输出空格
}
printf("\n");
}
同之前对小球的打印原理一样,将光标移动到指定的位置后使用先前设置好的符号来填充,使得游戏界面有可以看见的边界。
这里我让小球与边界的实现在同一个函数里面,所以你会发现在打印边界的同时也打印了小球。
三、显示移动挡板
在反弹球消除砖块中,当然有一块可以移动的挡板啦。刚刚我们实现了边界和小球的打印,接下来我们接着来实现可以移动的挡板。老规矩先来看代码:
int position_x, position_y; // 挡板中心坐标
int ridus; // 挡板半径大小
int left, right; // 挡板左右位置
ridus = 6;
position_x = high;
position_y = width / 2;
left = position_y - ridus;
right = position_y + ridus;
这些是挡板设置的参数,为了减少代码量的我把挡板的实现与打印边界放在一起了,就在上一段代码里面。这个挡板的实现与小球和边界的实现原理是一样的,可以看看我之前发的C语言——弹跳小球,这里面有我实现小球打印的方法。
四、反弹小球
我们结束小球、边界和挡板的打印后就开始小球的弹跳了。先来看代码:
if (ball_x == high - 1)
{
if ((ball_y >= left) && (ball_y <= right)) // 被挡板挡住
{
ball_number++;
printf("\a"); // 响铃
// ball_y = ball_y + rand()%4-2;
}
else // 没有被挡板挡住
{
printf("游戏失败\n");
system("pause");
exit(0);
}
}
ball_x = ball_x + ball_vx;
ball_y = ball_y + ball_vy;
if ((ball_x == 0) || (ball_x == high - 1))
ball_vx = -ball_vx;
if ((ball_y == 0) || (ball_y == width - 1))
ball_vy = -ball_vy;
实现原理:
通过判断小球的坐标是否在规定的范围内,是就可以让小球进行反弹(也就是小球相应的速度更改为负数,通过数的相加来实现小球的反弹),不是就结束游戏。
五、消除砖块
消除砖块的原理与反弹小球的原理是一样的,首先设置相应的变量来表示砖块的位置,以及使用符号来表示砖块。接下来就是判断小球的坐标是否与砖块的坐标重合,是就将原来的砖块消除,同时小球反弹。也可以定义一个变量来记录砖块的消除数,小球的反弹数(这个最好在设置小球的与挡板的碰撞时就一并设置) 。
if ((ball_x == block_x) && (ball_y == block_y)) // 小球击中方块
{
score++; // 分数加1
block_y = rand() % width; // 产生新的方块
}
六、玩家输入
当我们实现这些后,我们会发现其中最重要的玩家输入数据没有实现。这里我们用getchar()函数来实现数据的输入,kbhit()函数来时实现游戏进行的流畅性。
void updateWithInput() // 与用户输入有关的更新
{
char input;
if (kbhit()) // 判断是否有输入
{
input = getch(); // 根据用户的不同输入来移动,不必输入回车
if (input == 'a')
{
position_y--; // 位置左移
left = position_y - ridus;
right = position_y + ridus;
}
if (input == 'd')
{
position_y++; // 位置右移
left = position_y - ridus;
right = position_y + ridus;
}
}
}
getchar()
我们用这个函数来实现输入字符后不用回车来确定,增加游戏体验度。(具体用法请看这里)
kbhit()
用这个函数来实现,当玩家没有输入字符时游戏依旧可以进行。(具体用法请看这里)
七、结尾——最后实现的代码
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <windows.h>
#include <time.h>
// 宏定义数据,便于更改数值
#define RIDUS 5 // 挡板的半径
// 全局变量
int high, width; // 游戏画面大小
int ball_x, ball_y; // 小球的坐标
int ball_vx, ball_vy; // 小球的速度
int position_x, position_y; // 挡板中心坐标
int left, right; // 挡板左右位置
int ball_number; // 反弹小球的次数
int block_x, block_y; // 方块的位置
int score; // 消掉方块的个数
void gotoxy(int x, int y) // 光标移动到(x,y)位置
{
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos;
pos.X = x;
pos.Y = y;
SetConsoleCursorPosition(handle, pos);
}
void startup() // 数据初始化
{
high = 13;
width = 17;
ball_x = 0;
ball_y = width / 2;
ball_vx = 1;
ball_vy = 1;
position_x = high;
position_y = width / 2;
left = position_y - RIDUS;
right = position_y + RIDUS;
ball_number = 0;
block_x = 0;
block_y = width / 2 + 1;
score = 0;
}
void show() // 显示画面
{
gotoxy(0, 0); // 光标移动到原点位置,以下重画清屏
int i, j;
for (i = 0; i <= high + 1; i++)
{
for (j = 0; j <= width; j++)
{
if ((i == ball_x) && (j == ball_y))
printf("0"); // 输出小球
else if (j == width)
printf("|"); // 输出右边框
else if (i == high + 1)
printf("-"); // 输出下边框
else if ((i == high) && (j > left) && (j < right))
printf("*"); // 输出挡板
else if ((i == block_x) && (j == block_y))
printf("B"); // 输出方块
else
printf(" "); // 输出空格
}
printf("\n");
}
printf("反弹小球数:%d\n", ball_number);
printf("消掉的方块数:%d\n", score);
}
void updateWithoutInput() // 与用户输入无关的更新
{
if (ball_x == high - 1)
{
if ((ball_y >= left) && (ball_y <= right)) // 被挡板挡住
{
ball_number++;
printf("\a"); // 响铃
// ball_y = ball_y + rand()%4-2;
}
else // 没有被挡板挡住
{
printf("游戏失败\n");
system("pause");
exit(0);
}
}
if ((ball_x == block_x) && (ball_y == block_y)) // 小球击中方块
{
score++; // 分数加1
block_y = rand() % width; // 产生新的方块
}
ball_x = ball_x + ball_vx;
ball_y = ball_y + ball_vy;
if ((ball_x == 0) || (ball_x == high - 1))
ball_vx = -ball_vx;
if ((ball_y == 0) || (ball_y == width - 1))
ball_vy = -ball_vy;
Sleep(80);
}
void updateWithInput() // 与用户输入有关的更新
{
char input;
if (kbhit()) // 判断是否有输入
{
input = getch(); // 根据用户的不同输入来移动,不必输入回车
if (input == 'a')
{
position_y--; // 位置左移
left = position_y - RIDUS;
right = position_y + RIDUS;
}
if (input == 'd')
{
position_y++; // 位置右移
left = position_y - RIDUS;
right = position_y + RIDUS;
}
}
}
void HideCursor() // 隐藏光标函数
{
CONSOLE_CURSOR_INFO cursor_info = {1, 0};
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}
int main()
{
srand((unsigned)time(NULL)); // 创建随机数种子
startup(); // 初始化
HideCursor();
while (1)
{
show(); // 显示画面
updateWithoutInput(); // 与用户输入无关的更新
updateWithInput(); // 与用户输入有关的更新
}
return 0;
}
最后我们可以使用 #define 来让变量便于修改
#define
在C语言源程序中允许用一个标识符来表示一个字符串,称为“宏”。被定义为“宏”的标识符称为“宏名”。在编译预处理时,对程序中所有出现的“宏名”,都用宏定义中的字符串去代换,这称为“宏代换”或“宏展开”。宏定义是由源程序中的宏定义命令完成的。宏代换是由预处理程序自动完成的。在C语言中,“宏”分为有参数和无参数两种。具体请看这里。
总结
学习C语言的过程中我门更多的应该是去使用我们学到知识,而不是去看、去记。
下次再见。