目录
前言
一、飞机游戏的代码
二、代码解析
1、代码重构
1)主函数
2)其它函数
2、新的子弹
3、敌机
1)静止的敌机
2)敌机的移动
3)击中敌机
4、清屏功能
总结
前言
代码参考了《C语言课程设计与游戏开发实践教程》
游戏介绍:
对我之前的博客 C语言——简单的飞机小游戏 升级,玩法和之前的没有区别,只是代码的实现不同,依旧是玩家通过输入 "W S A D" 控制飞机 " * " 的上下左右移动,同时可以通过空格键来让飞机发射激光击中敌机 " @ "。
一、飞机游戏的代码
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <windows.h>
//定义边界,游戏边界
#define HIGH 20
#define WIDTH 30
// 全局变量
int position_x, position_y; // 飞机位置
int bullet_x, bullet_y; // 子弹位置
int enemy_x, enemy_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 HideCursor() // 用于隐藏光标
{
CONSOLE_CURSOR_INFO cursor_info = {1, 0}; // 第二个值为0表示隐藏光标
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}
void startup() // 数据初始化
{
position_x = HIGH / 2;
position_y = WIDTH / 2;
bullet_x = -2;
bullet_y = position_y;
enemy_x = 0;
enemy_y = position_y;
score = 0;
HideCursor(); // 隐藏光标
}
void show() // 显示画面
{
gotoxy(0, 0); // 光标移动到原点位置,以下重画清屏
int i, j;
for (i = 0; i < HIGH; i++)
{
for (j = 0; j < WIDTH; j++)
{
if ((i == position_x) && (j == position_y))
printf("*"); // 输出飞机*
else if ((i == enemy_x) && (j == enemy_y))
printf("@"); // 输出敌机@
else if ((i == bullet_x) && (j == bullet_y))
printf("|"); // 输出子弹|
else if (i == HIGH - 1)
printf("-");
else if (j == WIDTH - 1)
printf("|"); // 输出边界
else
printf(" "); // 输出空格
}
printf("\n");
}
printf("得分:%d\n", score);
}
void updateWithoutInput() // 与用户输入无关的更新
{
if (bullet_x > -1)
bullet_x--;
if ((bullet_x == enemy_x) && (bullet_y == enemy_y)) // 子弹击中敌机
{
score++; // 分数加1
enemy_x = -1; // 产生新的飞机
enemy_y = rand() % WIDTH;
bullet_x = -2; // 子弹无效
}
if (enemy_x > HIGH) // 敌机跑出显示屏幕
{
enemy_x = -1; // 产生新的飞机
enemy_y = rand() % WIDTH;
}
// 用来控制敌机向下移动的速度。每隔几次循环,才移动一次敌机
// 这样修改的话,用户按键交互速度还是保持很快,但我们NPC的移动显示可以降速
static int speed = 0;
if (speed < 20)
speed++;
if (speed == 20)
{
enemy_x++;
speed = 0;
}
}
void updateWithInput() // 与用户输入有关的更新
{
char input;
if (kbhit()) // 判断是否有输入
{
input = getch(); // 根据用户的不同输入来移动,不必输入回车
if (input == 'a')
position_y--; // 位置左移
if (input == 'd')
position_y++; // 位置右移
if (input == 'w')
position_x--; // 位置上移
if (input == 's')
position_x++; // 位置下移
if (input == ' ') // 发射子弹
{
bullet_x = position_x - 1; // 发射子弹的初始位置在飞机的正上方
bullet_y = position_y;
}
}
}
int main()
{
startup(); // 数据初始化
while (1) // 游戏循环执行
{
show(); // 显示画面
updateWithoutInput(); // 与用户输入无关的更新
updateWithInput(); // 与用户输入有关的更新
}
return 0;
}
二、代码解析
1、代码重构
1)主函数
对之前只是在主函数中实现的功能,我们首先来将需要实现的功能进行拆分,让它通过一个个函数来实现,最后通过主函数来实现功能的统一(实现预期效果)。
int main()
{
startup(); // 数据初始化
while (1) // 游戏循环执行
{
show(); // 显示画面
updateWithoutInput(); // 与用户输入无关的更新
updateWithInput(); // 与用户输入有关的更新
}
return 0;
}
将我们需要达到的预期效果分成若干个函数,让需要重复实现的放在循环中。
2)其它函数
对主函数进行重构后,对之前小游戏进行功能的模块化(通过相应的函数来实现)。
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <windows.h>
// 定义边界,游戏边界
#define HIGH 20
#define WIDTH 30
// 全局变量
int position_x, position_y; // 飞机位置
void startup() // 数据初始化
{
position_x = HIGH / 2;
position_y = WIDTH / 2;
}
void show() // 显示画面
{
system("cls");
int i, j;
for (i = 0; i < HIGH; i++)
{
for (j = 0; j < WIDTH; j++)
{
if ((i == position_x) && (j == position_y))
printf("*"); // 输出飞机 *
else if (i == HIGH - 1)
printf("-");
else if (j == WIDTH - 1)
printf("|"); // 输出边界
else
printf(" "); // 输出空格
}
printf("\n");
}
}
void updateWithoutInput() // 与用户输入无关的更新
{
}
void updateWithInput() // 与用户输入有关的更新
{
char input;
if (kbhit()) // 判断是否有输入
{
input = getch(); // 根据用户的不同输入来移动,不必输入回车
if (input == 'a')
position_y--; // 位置左移
if (input == 'd')
position_y++; // 位置右移
if (input == 'w')
position_x--; // 位置上移
if (input == 's')
position_x++; // 位置下移
}
}
int main()
{
startup(); // 数据初始化
while (1) // 游戏循环执行
{
show(); // 显示画面
updateWithoutInput(); // 与用户输入无关的更新
updateWithInput(); // 与用户输入有关的更新
}
return 0;
}
到此我们就完成了对之前代码的重构,这里我没有使用之前的飞机模型,使用的是 " * " 来代替飞机。
2、新的子弹
实现之前的功能之后我们来实现新的功能和对旧功能的优化。这一次我们让子弹发射后自动向前飞行。通过在打印飞机和边界的函数中添加新的分支来实现子弹的打印。
void show() // 显示画面
{
system("cls"); //清屏函数
int i, j;
for (i = 0; i < HIGH; i++)
{
for (j = 0; j < WIDTH; j++)
{
if ((i == position_x) && (j == position_y))
printf("*"); // 输出飞机 *
else if ((i == bullet_x) && (j == bullet_y))
printf("|"); // 输出子弹|
else if (i == HIGH - 1)
printf("-");
else if (j == WIDTH - 1)
printf("|"); // 输出边界
else
printf(" "); // 输出空格
}
printf("\n");
}
}
3、敌机
接下来就是敌机的实现了
1)静止的敌机
首先我们来实现敌机的出现。
依旧是在打印飞机的函数中增加新的分支来实现敌机 " @ " 的出现。(记得定义敌机相应的变量)
void show() // 显示画面
{
system("cls"); //清屏函数
int i, j;
for (i = 0; i < HIGH; i++)
{
for (j = 0; j < WIDTH; j++)
{
if ((i == position_x) && (j == position_y))
printf("*"); // 输出飞机*
else if ((i == enemy_x) && (j == enemy_y))
printf("@"); // 输出敌机@
else if ((i == bullet_x) && (j == bullet_y))
printf("|"); // 输出子弹|
else if (i == HIGH - 1)
printf("-");
else if (j == WIDTH - 1)
printf("|"); // 输出边界
else
printf(" "); // 输出空格
}
printf("\n");
}
}
2)敌机的移动
实现敌机的出现后我们来移动敌机,为了降低敌机的移动速度而不影响玩家输入相应的频率。这里通过定义静态变量speed来让每实现20次 updateWithoutInput() 函数,敌机飞行一次。
// 用来控制敌机向下移动的速度。每隔几次循环,才移动一次敌机
// 这样修改的话,用户按键交互速度还是保持很快,但我们NPC的移动显示可以降速
static int speed = 0;
if (speed < 20)
speed++;
if (speed == 20)
{
enemy_x++;
speed = 0;
}
3)击中敌机
通过判断飞机和子弹的位置来判断是否击中敌机,如果击中就通过随机数来实现新敌机的出现。
void updateWithoutInput() // 与用户输入无关的更新
{
if (bullet_x > -1)
bullet_x--;
if ((bullet_x == enemy_x) && (bullet_y == enemy_y)) // 子弹击中敌机
{
score++; // 分数加1
enemy_x = -1; // 产生新的飞机
enemy_y = rand() % WIDTH;
bullet_x = -2; // 子弹无效
}
if (enemy_x > HIGH) // 敌机跑出显示屏幕
{
enemy_x = -1; // 产生新的飞机
enemy_y = rand() % WIDTH;
}
// 用来控制敌机向下移动的速度。每隔几次循环,才移动一次敌机
// 这样修改的话,用户按键交互速度还是保持很快,但我们NPC的移动显示可以降速
static int speed = 0;
if (speed < 20)
speed++;
if (speed == 20)
{
enemy_x++;
speed = 0;
}
}
4、清屏功能
到这里基本已经实现了我们预期的效果,但会发现闪烁非常严重,这个时候我们只需要将通过移动光标来让画面重新画过就可以了,同时实现光标的隐藏。
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 HideCursor() // 用于隐藏光标
{
CONSOLE_CURSOR_INFO cursor_info = {1, 0}; // 第二个值为0表示隐藏光标
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}
总结
我没有对这里使用到的知识点做出解释,相应的知识点请看这里