C++小游戏 之 Flappy Bird

newflappy

成功运行的游戏效果如上图所示。

想象一下我们要做的游戏:

  1. 有一只小鸟(就像一个 “0” 字符,带着翅膀)
  2. 小鸟要穿过一些管道(像这样 ”|==|”)
  3. 每通过一个管道就能得1分
  4. 小鸟会自动往下掉,按上键可以让它往上飞

就像玩具一样,我们需要先准备好所有的”零件”,然后一步步把它们组装起来。下面,让我们来详细地一步步编程实现它。让刚学编程的小朋友也能理解。

第1步:搭建基础框架

新建一个源代码文件:flappybird.cpp,内容如下:

#include <ncurses.h> // 控制终端显示的库
#include <unistd.h>  // 提供usleep函数

int main() {
    initscr();        // 初始化ncurses
    raw();            // 直接获取键盘输入
    noecho();         // 不显示输入字符
    curs_set(0);      // 隐藏光标

    // 游戏主循环将在这里

    endwin();         // 结束ncurses
    
    return 0;
}

编译时需要加-lncurses参数,例如:

g++ flappybird.cpp -o game -lncurses`

-lncurses的意思, 是链接 ncurses依赖库,如果系统中没有ncurses库,需要执行如下命令安装:

sudo apt install libncurses-dev

第2步:创建游戏窗口

const int NUM_ROWS = 24;  // 窗口高度
const int NUM_COLS = 80;  // 窗口宽度

int main() {
    // ...之前初始化代码...

    // 设置窗口尺寸(需要实际终端支持)
    resizeterm(NUM_ROWS, NUM_COLS);

    // ...其他代码...
}

第3步:绘制地板和天花板

void draw_floor() {
    for(int i=0; i<NUM_COLS; i++) {
        mvaddch(NUM_ROWS-1, i, '-'); // 最后一行画地板
        mvaddch(0, i, '-');          // 第一行画天花板
    }
}

int main() {
    // ...初始化后...
    draw_floor();
    refresh(); // 刷新显示
    getch();   // 等待按键
}

第4步:创建小鸟结构

struct Flappy {
    int y;      // 当前高度
    int speed;  // 下落速度
};

int main() {
    Flappy bird {NUM_ROWS/2, 1};
    mvaddch(bird.y, 10, '>'); // 在第10列显示小鸟
    refresh();
    getch();
}

第5步:实现重力效果

void update_bird(Flappy &b) {
    b.speed += 1;       // 重力加速度
    b.y += b.speed;     // 更新位置
    if(b.y < 0) b.y = 0;
    if(b.y >= NUM_ROWS-1) b.y = NUM_ROWS-2;
}

int main() {
    Flappy bird {NUM_ROWS/2, 0};
    while(true) {
        clear();
        update_bird(bird);
        mvaddch(bird.y, 10, '>');
        refresh();
        usleep(100000); // 0.1秒延迟
    }
}

第6步:添加跳跃控制

int main() {
    // ...初始化...
    while(true) {
        int ch = getch();
        if(ch == ' ') { // 按空格跳跃
            bird.speed = -3; // 向上速度
        }
        // ...其他代码...
    }
}

第7步:创建管道结构

struct Pipe {
    int x;           // 水平位置
    int gap_y;       // 缺口位置
    bool passed;     // 是否已通过
};

Pipe create_pipe() {
    return {NUM_COLS, rand()%(NUM_ROWS-6)+3, false};
}

第8步:绘制管道

void draw_pipe(Pipe p) {
    // 上管道
    for(int y=0; y<p.gap_y-2; y++)
        mvaddch(y, p.x, '|');

    // 下管道
    for(int y=p.gap_y+2; y<NUM_ROWS-1; y++)
        mvaddch(y, p.x, '|');
}

第9步:移动管道

void move_pipes(Pipe pipes[], int count) {
    for(int i=0; i<count; i++) {
        pipes[i].x--;
        if(pipes[i].x < -5) // 移出屏幕后重置
            pipes[i] = create_pipe();
    }
}

第10步:碰撞检测

bool check_collision(Flappy b, Pipe p) {
    if(p.x != 10) return false; // 小鸟在第10列
    return (b.y < p.gap_y-2 || b.y > p.gap_y+2);
}

第11步:计分系统

int score = 0;

void update_score(Pipe pipes[], int count) {
    for(int i=0; i<count; i++) {
        if(pipes[i].x == 9 && !pipes[i].passed) {
            score++;
            pipes[i].passed = true;
        }
    }
}

第12步:游戏主循环结构

int main() {
    Pipe pipes[2] = {create_pipe(), create_pipe()};
    pipes[1].x += 20; // 第二个管道的位置偏移

    while(true) {
        // 处理输入
        // 更新位置
        // 检测碰撞
        // 绘制画面
        usleep(1000000/30); // 30帧/秒
    }
}

第13步:游戏结束画面

void game_over() {
    clear();
    mvprintw(NUM_ROWS/2, NUM_COLS/2-5, "游戏结束!");
    mvprintw(NUM_ROWS/2+1, NUM_COLS/2-8, "得分: %d", score);
    refresh();
    getch();
    endwin();
    exit(0);
}

第14步:添加启动画面

void splash_screen() {
    mvprintw(NUM_ROWS/2-3, NUM_COLS/2-10, "FLAPPY BIRD");
    mvprintw(NUM_ROWS/2, NUM_COLS/2-12, "按空格键开始游戏");
    refresh();
    while(getch() != ' '); // 等待空格键
}

第15步:最终整合

// 包含所有上述功能的完整代码
// (由于篇幅限制,这里展示主循环结构)

int main() {
    // 初始化...
    splash_screen();

    Flappy bird {NUM_ROWS/2, 0};
    Pipe pipes[2] = {create_pipe(), create_pipe()};
    pipes[1].x += 20;

    while(true) {
        // 处理输入
        // 更新物理
        // 检测碰撞
        // 绘制画面
        if(碰撞检测) game_over();
    }
}

完整代码的实现需要将上述所有步骤组合起来。这个游戏涉及的主要知识点:

  1. ncurses 库:用于终端图形界面
  2. 结构体:组织游戏对象的数据
  3. 游戏循环:处理输入 → 更新状态 → 渲染画面的循环
  4. 物理模拟:简单的重力加速度实现
  5. 碰撞检测:判断小鸟和管道的位置关系
  6. 状态管理:游戏进行、结束、重启等状态转换

建议你按照以下顺序逐步实现:

  1. 先让小鸟能上下移动
  2. 添加静止的管道
  3. 让管道移动
  4. 添加碰撞检测
  5. 最后完善计分和界面

遇到问题可以随时问我,比如:

  • “为什么小鸟不显示?”
  • “管道移动速度怎么调整?”
  • “碰撞检测不准确怎么办?”

要编译运行这个游戏,记得使用:

g++ -o flappy flappy.cpp -lncurses