目录

前言

一、飞机游戏的代码

二、代码解析

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);
}

总结

我没有对这里使用到的知识点做出解释,相应的知识点请看这里