使用Pygame制作“Flappy Bird”游戏

1. 前言

Flappy Bird 是一款"点击上浮松手下落"的横向卷轴游戏:

  • 场景中持续出现上下成对的管道,玩家需要让小鸟在管道之间穿行;
  • 每穿过一对管道记 1 分;
  • 若小鸟碰到管道或掉到地面,则游戏结束;
  • 一旦上手,就会体会到"魔性"且容易让人"上头"的乐趣。

在本篇中,我们使用 Python + Pygame 来从零开始构建一个精简版的 Flappy Bird,主要关注以下要点:

  • 重力/速度:小鸟受"重力"影响不断向下,下落速度随时间加快;
  • 点击上浮:每次按键或点击,即让小鸟速度向上,模拟腾空;
  • 管道生成:随机生成多组上下管道,让它们从右向左移动;
  • 碰撞与得分:检测小鸟是否与管道矩形或地面碰撞;若小鸟成功穿过管道中间的缺口,则得分 +1;
  • 游戏结束:当小鸟撞到管道或地面,游戏停止,显示分数或重来选项。

2. 开发环境

  1. Python 3.x
  2. Pygame 库:若尚未安装,可通过 pip install pygame 来获取。
  3. 图形界面:Windows、macOS、Linux 桌面环境皆可。

确认 import pygame 无报错后,即可开始项目编写。


3. 简要实现思路

  1. 小鸟(Bird)

    • 记录小鸟的 x, y 位置以及 velocity(竖直方向速度);
    • 受"重力"影响,每帧更新时给速度增加一固定值,如 gravity = 0.4,再更新 y 位置;
    • 当按键(如空格)或鼠标点击时,让速度变为负值(如 -6),模拟向上飞。
  2. 管道(Pipe)

    • 管道由上下两根部分组成,中间留有空隙让小鸟通过;
    • 随机生成管道时,可随机空隙位置(上下波动),管道整体从屏幕右侧出现后向左移动。
    • 若管道完全离开屏幕左侧,则可将其移除。
  3. 碰撞检测

    • 小鸟若 y 越过地面(例如 y + bird_height >= 地面高度)或 < 0(飞出屏幕上端),则判定失败;
    • 小鸟若与任意管道矩形重叠,同样判定失败;
    • 可以用 pygame.Rectcolliderect 来检测。
  4. 得分判定

    • 当管道刚好穿过小鸟的 x 位置时,可以认为小鸟成功通过中间的空隙,这时分数 +1。
    • 常见做法是检测小鸟的中心点是否越过某个管道的"通过线"。
  5. 游戏循环

    • 初始化小鸟、管道、分数等;
    • 不断更新(小鸟坐标、管道移动、碰撞检测)和绘制(背景、小鸟、管道、分数);
    • 知道小鸟死亡或玩家退出,结束循环,显示结算画面。

4. 完整示例代码

将以下内容保存为 flappy_bird.py 并运行即可。该示例使用简单的矩形、颜色和文字来模拟 Flappy Bird 的玩法,后期可以替换成更精美的贴图与音效。

python 复制代码
import pygame
import sys
import random

# 初始化 Pygame
pygame.init()

# -----------------------
# 全局参数
# -----------------------
WIDTH, HEIGHT = 400, 600
FPS = 60

# 颜色定义
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GREEN = (0, 200, 0)
RED   = (255, 0, 0)

# 窗口与时钟
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Flappy Bird - Pygame 示例")
clock = pygame.time.Clock()

# 字体
font = pygame.font.SysFont("arial", 32)

# 地面高度(小鸟碰到地面即失败)
GROUND_LEVEL = HEIGHT - 50

# -----------------------
# 小鸟类
# -----------------------
class Bird:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.radius = 15
        self.color = RED
        self.velocity = 0  # 垂直速度
        self.gravity = 0.4  # "重力"加速度

    def update(self):
        # 重力作用
        self.velocity += self.gravity
        self.y += self.velocity

    def flap(self):
        # 按键或点击时,让鸟向上冲
        self.velocity = -6

    def draw(self, surface):
        pygame.draw.circle(surface, self.color, (int(self.x), int(self.y)), self.radius)

    def get_rect(self):
        # 方便碰撞检测
        return pygame.Rect(self.x - self.radius, self.y - self.radius,
                           self.radius * 2, self.radius * 2)

# -----------------------
# 管道类
# -----------------------
class Pipe:
    def __init__(self, x, gap_y, gap_height=150, width=60):
        """
        x: 管道出现的x坐标
        gap_y: 空隙的中心Y
        gap_height: 空隙的总高度
        width: 管道宽
        """
        self.x = x
        self.width = width
        self.color = GREEN

        self.gap_y = gap_y
        self.gap_height = gap_height

        # 两部分管道的矩形
        # 上管道: 从顶部到 gap_y - gap_height/2
        # 下管道: 从 gap_y + gap_height/2 到地面
        self.top_height = gap_y - gap_height // 2
        self.bottom_y = gap_y + gap_height // 2
        self.speed = 3  # 管道向左移动速度

    def update(self):
        self.x -= self.speed

    def draw(self, surface):
        # 画上管道
        top_rect = pygame.Rect(self.x, 0, self.width, self.top_height)
        pygame.draw.rect(surface, self.color, top_rect)
        # 画下管道
        bottom_rect = pygame.Rect(self.x, self.bottom_y, self.width, HEIGHT - self.bottom_y)
        pygame.draw.rect(surface, self.color, bottom_rect)

    def get_top_rect(self):
        return pygame.Rect(self.x, 0, self.width, self.top_height)

    def get_bottom_rect(self):
        return pygame.Rect(self.x, self.bottom_y, self.width, HEIGHT - self.bottom_y)

    def is_off_screen(self):
        # 如果管道完全移出屏幕左侧
        return self.x + self.width < 0

# -----------------------
# 主函数
# -----------------------
def main():
    bird = Bird(x=100, y=HEIGHT//2)
    pipes = []
    score = 0

    # 首先生成一组初始管道
    for i in range(3):
        gap_y = random.randint(100, GROUND_LEVEL - 100)
        pipe_x = 400 + i * 200  # 每 200 像素出现一对管道
        pipes.append(Pipe(pipe_x, gap_y))

    # 用于检测是否已经计分
    # 当鸟的x > 某个管道的x+width,就说明顺利通过管道 -> score+1
    passed_pipe_indices = set()

    running = True
    while running:
        clock.tick(FPS)

        # 1) 事件处理
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.KEYDOWN or event.type == pygame.MOUSEBUTTONDOWN:
                # 点击或按任意键让小鸟向上
                bird.flap()

        # 2) 更新小鸟
        bird.update()

        # 检查小鸟是否撞地面或飞出屏幕上方
        if bird.y + bird.radius >= GROUND_LEVEL or bird.y - bird.radius <= 0:
            running = False

        # 3) 更新管道
        for p in pipes:
            p.update()

        # 管道移除 & 新管道生成
        if pipes and pipes[0].is_off_screen():
            pipes.pop(0)
            # 生成新的管道
            gap_y = random.randint(100, GROUND_LEVEL - 100)
            pipe_x = pipes[-1].x + 200  # 相隔200像素出现新管道
            pipes.append(Pipe(pipe_x, gap_y))

        # 4) 碰撞检测: 小鸟与管道
        bird_rect = bird.get_rect()
        for idx, p in enumerate(pipes):
            if bird_rect.colliderect(p.get_top_rect()) or bird_rect.colliderect(p.get_bottom_rect()):
                running = False

            # 得分判定:如果小鸟已超过管道的右边缘,且还未记分
            if bird.x > p.x + p.width and idx not in passed_pipe_indices:
                score += 1
                passed_pipe_indices.add(idx)

        # 5) 绘制场景
        screen.fill(BLACK)

        # 地面
        pygame.draw.rect(screen, WHITE, (0, GROUND_LEVEL, WIDTH, HEIGHT - GROUND_LEVEL))

        # 管道
        for p in pipes:
            p.draw(screen)

        # 小鸟
        bird.draw(screen)

        # 分数显示
        score_surf = font.render(f"Score: {score}", True, WHITE)
        screen.blit(score_surf, (10, 10))

        pygame.display.flip()

    game_over(score)

def game_over(score):
    # 游戏结束场景
    screen.fill(BLACK)
    msg = f"Game Over! Your Score: {score}"
    label = font.render(msg, True, WHITE)
    rect = label.get_rect(center=(WIDTH // 2, HEIGHT // 2))
    screen.blit(label, rect)
    pygame.display.flip()
    pygame.time.wait(3000)
    pygame.quit()
    sys.exit()

if __name__ == "__main__":
    main()

代码解析

  1. Bird 类

    • velocity 表示当前的垂直速度,每帧都增加一定重力 gravity
    • update() 用于更新小鸟的 y 坐标;
    • flap()velocity 设置为负值(比如 -6),让小鸟立刻向上冲。
    • 使用 get_rect() 得到小鸟的矩形边界,用于与管道进行 colliderect 碰撞判断。
  2. Pipe 类

    • 包含上下两根管道,通过 gap_ygap_height 确定中间空隙的位置与大小;
    • update() 向左移动管道,若离开屏幕则 is_off_screen() 返回 True 以便移除;
    • get_top_rect()get_bottom_rect() 返回上下管道的 Rect 边界,用于碰撞检测。
  3. 管道队列

    • 程序开始时先生成 3 个管道,让画面中一开始就有管道出现。
    • 当最左侧的管道移出屏幕后,移除它并在最右侧补充一个新管道。
    • 这样,场景就能源源不断地产生新管道。
  4. 得分逻辑

    • 当小鸟 x 坐标超过管道右边 x + width,说明已安全穿过该管道 -> 分数 + 1。
    • 用一个 passed_pipe_indices 集合来记录已经计分的管道,避免重复加分。
  5. 游戏结束

    • 若小鸟超出上下边界或与管道碰撞,立刻退出主循环;
    • 最后进入 game_over() 显示成绩并延时退出。

5. 实现效果


6. 总结

本示例通过 Pygame 带你体验了一个简化版的 Flappy Bird 开发流程:

  • 利用重力模拟按键上浮实现小鸟的控制;
  • 管道队列完成场景的动态生成与回收;
  • 借助碰撞检测得分机制让游戏规则更加完善。

如果你能理解并实现这些核心逻辑,相信对于 2D 横版卷轴类游戏的基础也会有了更多体会。你可以进一步在此游戏上进行创意改造,实现更完善、更具可玩性的 Flappy Bird。愿你在游戏编程之旅中越飞越高,创作出属于自己的精彩作品!

相关推荐
数据智能老司机13 小时前
精通 Python 设计模式——分布式系统模式
python·设计模式·架构
数据智能老司机14 小时前
精通 Python 设计模式——并发与异步模式
python·设计模式·编程语言
数据智能老司机14 小时前
精通 Python 设计模式——测试模式
python·设计模式·架构
数据智能老司机14 小时前
精通 Python 设计模式——性能模式
python·设计模式·架构
c8i14 小时前
drf初步梳理
python·django
每日AI新事件14 小时前
python的异步函数
python
这里有鱼汤15 小时前
miniQMT下载历史行情数据太慢怎么办?一招提速10倍!
前端·python
databook1 天前
Manim实现脉冲闪烁特效
后端·python·动效
程序设计实验室1 天前
2025年了,在 Django 之外,Python Web 框架还能怎么选?
python
倔强青铜三1 天前
苦练Python第46天:文件写入与上下文管理器
人工智能·python·面试