C 语言实现 Flappy Bird 游戏

C语言实现 Flappy Bird 游戏

在本文中,我将带你一步步实现一个基于C语言的Flappy Bird游戏,并结合代码示例详细讲解实现过程中的关键逻辑、设计思路及其背后的原因。同时,我也会分享在实现过程中遇到的一些问题和解决方案。


引言

Flappy Bird 是一款因其简单但极具挑战性而广受欢迎的小游戏。本文的目标是使用C语言和ncurses库在Linux系统的终端中实现一个简化版的Flappy Bird游戏。在这个游戏中,鸟由字符O表示,障碍物由字符*组成。玩家需要通过键盘控制鸟的高度,避免撞到不断从右向左移动的障碍物。


项目环境

在开始编码之前,我们需要配置开发环境并安装所需的库:

  • 操作系统:Linux(本文使用Ubuntu为例)
  • 编辑器:GVim 或者其他你喜欢的文本编辑器
  • ncurses,用于在终端中绘制字符界面。

安装ncurses库:

sudo apt-get install libncurses5-dev

项目环境与准备工作

在开始编码之前,我们需要配置开发环境并安装所需的库。以下是具体步骤:

  1. 打开Xfce终端:首先在你的Linux系统中打开Xfce终端。

  2. 安装ncurses

    sudo apt-get install libncurses5-dev
    
  3. 进入项目目录:接下来,进入你想要存放项目的目录,比如Code目录。

    cd Code
    
  4. 创建项目文件:在Code目录下创建一个名为my_flappy_bird.c的C语言文件。

    touch my_flappy_bird.c
    
  5. 使用GVim编辑器编写代码:打开GVim编辑器开始编写代码。

    vim my_flappy_bird.c
    

 


代码实现与逻辑解析

1. 初始化与设置

首先,我们需要导入相关的头文件,并定义一些常量和全局变量。

#include <curses.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/time.h>

// 定义鸟、石头和空格的符号
#define CHAR_BIRD 'O'
#define CHAR_STONE '*'
#define CHAR_BLANK ' '

// 链表结构体,存储柱子的位置
typedef struct node {
    int x, y;
    struct node *next;
} node, *Node;

// 全局变量
Node head, tail;
int bird_x, bird_y;
int ticker;

逻辑解析

  • ncurses库的引入使得我们可以在终端中处理字符界面。
  • 定义的字符符号用于在屏幕上表示鸟和障碍物。
  • 使用链表结构体存储柱子的位置是因为链表可以动态调整元素数量,更灵活地管理障碍物的出现和移动。

设计理由

  • 选择链表而非数组是因为柱子的数量不固定,如果使用数组,不仅难以管理,而且会增加不必要的空间开销和复杂度。

2. 核心函数设计

在主函数main之前,我们需要编写几个初始化和信号处理函数。

2.1 初始化函数
void init() {
    initscr();
    cbreak();
    noecho();
    curs_set(0);
    srand(time(0));
    signal(SIGALRM, drop);

    init_bird();
    init_head();
    init_wall();
    init_draw();
    sleep(1);
    ticker = 500;
    set_ticker(ticker);
}

void init_bird() {
    bird_x = 5;
    bird_y = 15;
    move(bird_y, bird_x);
    addch(CHAR_BIRD);
    refresh();
    sleep(1);
}

逻辑解析

  • init() 函数负责整个游戏的初始化,包括屏幕设置、鸟的位置初始化、链表(用于存储障碍物)的初始化等。
  • init_bird() 函数专注于鸟的初始化位置,并在屏幕上显示。

设计理由

  • 将初始化分为多个函数,使得代码更具可读性和可维护性。如果所有初始化工作都在main函数中进行,会导致代码过于冗长和复杂。
2.2 信号处理与定时器设置
int set_ticker(int n_msec) {
    struct itimerval timeset;
    long n_sec, n_usec;

    n_sec = n_msec / 1000;
    n_usec = (n_msec % 1000) * 1000L;

    timeset.it_interval.tv_sec = n_sec;
    timeset.it_interval.tv_usec = n_usec;

    timeset.it_value.tv_sec = n_sec;
    timeset.it_value.tv_usec = n_usec;

    return setitimer(ITIMER_REAL, &timeset, NULL);
}

void drop(int sig) {
    int j;
    Node tmp, p;

    move(bird_y, bird_x);
    addch(CHAR_BLANK);
    refresh();

    bird_y++;
    move(bird_y, bird_x);
    addch(CHAR_BIRD);
    refresh();

    if((char)inch() == CHAR_STONE) {
        set_ticker(0);
        sleep(1);
        endwin();
        exit(0);
    }

    p = head->next;
    if(p->x < 0) {
        head->next = p->next;
        free(p);
        tmp = (node *)malloc(sizeof(node));
        tmp->x = 99;
        tmp->y = rand() % 11 + 5;
        tail->next = tmp;
        tmp->next = NULL;
        tail = tmp;
        ticker -= 10;
        set_ticker(ticker);
    }

    for(p = head->next; p->next != NULL; p->x--, p = p->next) {
        for(j = 0; j < p->y; j++) {
            move(j, p->x);
            addch(CHAR_BLANK);
            refresh();
        }
        for(j = p->y+5; j <= 23; j++) {
            move(j, p->x);
            addch(CHAR_BLANK);
            refresh();
        }

        if(p->x-10 >= 0 && p->x < 80) {
            for(j = 0; j < p->y; j++) {
                move(j, p->x-10);
                addch(CHAR_STONE);
                refresh();
            }
            for(j = p->y + 5; j <= 23; j++) {
                move(j, p->x-10);
                addch(CHAR_STONE);
                refresh();
            }
        }
    }
    tail->x--;
}

逻辑解析

  • set_ticker() 函数设置定时器,使得游戏每隔一定时间自动更新一次屏幕。
  • drop() 函数在每次定时器触发时运行,用于更新障碍物和鸟的位置,并检测是否发生碰撞。

设计理由

  • 使用定时器而非循环的方式更新屏幕是为了避免因循环占用大量CPU资源导致系统性能下降。
  • drop()函数中,通过动态生成新的障碍物并删除超出边界的障碍物,使得游戏能够持续进行。

3. 主循环与用户输入
int main() {
    char ch;

    init();
    while(1) {
        ch = getch();
        if(ch == ' ' || ch == 'w' || ch == 'W') {
            move(bird_y, bird_x);
            addch(CHAR_BLANK);
            refresh();
            bird_y--;
            move(bird_y, bird_x);
            addch(CHAR_BIRD);
            refresh();

            if((char)inch() == CHAR_STONE) {
                set_ticker(0);
                sleep(1);
                endwin();
                exit(0);
            }
        } else if(ch == 'z' || ch == 'Z') {
            set_ticker(0);
            do {
                ch = getch();
            } while(ch != 'z' && ch != 'Z');
            set_ticker(ticker);
        } else if(ch == 'q' || ch == 'Q') {
            sleep(1);
            endwin();
            exit(0);
        }
    }
    return 0;
}

逻辑解析

  • 主循环通过不断读取用户输入来控制游戏的进行。
  • getch() 函数用于捕获键盘输入,玩家按下空格或w键时,鸟会向上飞;按下z键暂停游戏,q键退出游戏。

设计理由

  • 将用户输入和游戏逻辑放在主循环中,使得代码逻辑清晰明了,并且便于后续扩展(如添加更多控制逻辑)。

结果展示:

暴露的问题

主要组件

  1. Linux环境的设置

    • Linux命令的不熟悉:在开始项目之前,我需要熟悉基本的Linux命令,如cdmkdirvim等。这些命令对于在文件系统中导航、创建目录以及编辑文件至关重要。刚开始时,我对这些命令的语法和用法感到困惑,但通过实践和查阅文档,我逐渐掌握了它们。
  2. 使用ncurses

    • ncurses库函数的学习ncurses是一个功能强大的库,它提供了在终端中创建文本用户界面的函数。然而,由于我对ncurses不熟悉,因此在理解其提供的各种函数时遇到了一些困难,例如initscr()cbreak()noecho()refresh()等函数。它们分别用于初始化屏幕、处理输入、关闭回显以及刷新显示。通过查阅资料和不断实践,我逐渐掌握了这些函数的用法。
  3. 信号处理与游戏逻辑的理解

    • 信号处理与游戏逻辑:信号处理是C编程中的一个高级主题,它是我在本项目中遇到的最大挑战之一。setitimer函数用于设置定时器,定时器会定期向程序发送信号。drop函数是信号处理程序,用于更新游戏状态,比如移动鸟和障碍物。编写这些函数需要对Linux中的信号机制有深入的理解,这花费了我不少时间。

    • 指针与链表的理解:另一个主要挑战是理解指针,尤其是在处理链表时。在游戏中,障碍物是用链表表示的,p->next语法用于遍历链表节点。起初,我对这种语法感到困惑,不确定它的作用。在学习了链表的工作原理并通过实验后,我逐渐理解了如何使用指针来操作链表节点。

  4. 使用sleep函数

    • 调整睡眠时间sleep函数用于在游戏中引入延迟,从而产生平滑的动画效果。找到合适的睡眠时间是一项挑战:时间过短,游戏会运行得太快;时间过长,游戏则变得难以操作。经过多次尝试,我最终找到了一个既具有挑战性又不过于苛刻的时间设置。

结论

通过C语言和ncurses库实现的Flappy Bird游戏展示了如何在终端中创建一个简单但具有挑战性的游戏。在这个过程中,我遇到了许多挑战,包括对Linux命令的不熟悉、ncurses库的使用、信号处理与游戏逻辑的编写,以及对指针和链表的理解。这些问题的解决不仅帮助我深入理解了C语言,也让我在Linux环境下的编程能力有了显著提高。

这个项目的成功实施证明了即使在字符界面中,我们也可以用C语言创造出有趣的游戏。如果你也对C语言和Linux系统感兴趣,强烈推荐你尝试实现这个项目,它将为你带来巨大的成就感和学习体验。


希望这篇博客能帮助你更好地理解如何使用C语言实现Flappy Bird游戏。如果你有任何疑问或需要进一步的帮助,请随时与我联系!

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夏驰和徐策

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值