使用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 的玩法,后期可以替换成更精美的贴图与音效。

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. 实现效果

image.png

image.png


6. 总结

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/965549.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

BUUCTF_XSS-Lab

xss XSS&#xff08;Cross - Site Scripting&#xff09;即跨站脚本攻击&#xff0c;是一种常见的 Web 安全漏洞。攻击者通过在目标网站注入恶意脚本&#xff08;通常是 JavaScript&#xff09;&#xff0c;当其他用户访问该网站时&#xff0c;这些恶意脚本会在用户的浏览器中执…

【Windows 开发NVIDIA相关组件】CUDA、cuDNN、TensorRT

目录 1. 安装 CUDA Toolkit 2. 安装 cuDNN 3. 安装 Zlib 4. 安装 TensorRT 5. 安装 TensorRT Python 包[c++项目不需要] 6. 安装 ONNX GraphSurgeon 包[c++项目不需要] 1. 安装 CUDA Toolkit 从 CUDA ToolkitArchive 下载对应版本的离线安装包,以 11.7 版本为例。 打开安…

红包雨项目前端部分

创建项目 pnpm i -g vue/cli vue create red_pakage pnpm i sass sass-locader -D pnpm i --save normalize.css pnpm i --save-dev postcss-px-to-viewportpnpm i vantlatest-v2 -S pnpm i babel-plugin-import -Dhttps://vant.pro/vant/v2/#/zh-CN/<van-button click&…

源路由 | 源路由网桥 / 生成树网桥

注&#xff1a;本文为 “源路由” 相关文章合辑。 未整理去重。 什么是源路由&#xff08;source routing&#xff09;&#xff1f; yzx99 于 2021-02-23 09:45:51 发布 考虑到一个网络节点 A 从路由器 R1 出发&#xff0c;可以经过两台路由器 R2、R3&#xff0c;到达相同的…

【React】合成事件语法

React 合成事件是 React 为了处理浏览器之间的事件差异而提供的一种跨浏览器的事件系统。它封装了原生的 DOM 事件&#xff0c;提供了一致的事件处理机制。 合成事件与原生事件的区别&#xff1a; 合成事件是 React 自己实现的&#xff0c;封装了原生事件。合成事件依然可以通…

一文解释nn、nn.Module与nn.functional的用法与区别

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;零基础入门PyTorch框架_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 …

TongSearch3.0.4.0安装和使用指引(by lqw)

文章目录 安装准备手册说明支持的数据类型安装控制台安装单节点(如需集群请跳过这一节)解压和启动开启X-Pack Security和生成p12证书&#xff08;之后配置内置密码和ssl要用到&#xff09;配置内置用户密码配置ssl&#xff08;先配置内置用户密码再配ssl&#xff09;配置控制台…

2025年Android NDK超全版本下载地址

Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总游戏脚本-辅助自动化Android控件全解手册再战Android系列Scratch编程案例软考全系列Unity3D学习专栏蓝桥系列ChatGPT和AIGC &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分…

CSS outline详解:轮廓属性的详细介绍

什么是outline&#xff1f; outline&#xff08;轮廓&#xff09;是CSS中一个有趣的属性&#xff0c;它在元素边框&#xff08;border&#xff09;的外围绘制一条线。与border不同的是&#xff0c;outline不占用空间&#xff0c;不会影响元素的尺寸和位置。这个特性使它在某些…

蓝桥杯之c++入门(六)【string(practice)】

目录 练习1&#xff1a;标题统计方法1&#xff1a;一次性读取整行字符&#xff0c;然后统计方法2&#xff1a;按照单词读取小提示&#xff1a; 练习2&#xff1a;石头剪子布练习3&#xff1a;密码翻译练习4&#xff1a;文字处理软件练习5&#xff1a;单词的长度练习6&#xff1…

Windows编程:下载与安装 Visual Studio 2010

本节前言 在写作本节的时候&#xff0c;本来呢&#xff0c;我正在写的专栏&#xff0c;是 MFC 专栏。而 VS2010 和 VS2019&#xff0c;正是 MFC 学习与开发中&#xff0c;可以使用的两款软件。然而呢&#xff0c;如果你去学习 Windows API 知识的话&#xff0c;那么&#xff0…

基于ansible部署elk集群

ansible部署 ELK部署 ELK常见架构 &#xff08;1&#xff09;ElasticsearchLogstashKibana&#xff1a;这种架构是最常见的一种&#xff0c;也是最简单的一种架构&#xff0c;这种架构通过Logstash收集日志&#xff0c;运用Elasticsearch分析日志&#xff0c;最后通过Kibana中…

(苍穹外卖)项目结构

苍穹外卖项目结构 后端工程基于 maven 进行项目构建&#xff0c;并且进行分模块开发。 1). 用 IDEA 打开初始工程&#xff0c;了解项目的整体结构&#xff1a; 对工程的每个模块作用说明&#xff1a; 序号名称说明1sky-take-outmaven父工程&#xff0c;统一管理依赖版本&…

达梦数据库从单主模式转换为主备模式

目录标题 达梦数据库单主转主备配置笔记前期准备服务器环境数据库安装磁盘空间 流程流程图说明基于脱机备份方式的单实例转主备流程图详细步骤说明 详细步骤1. 检查主库归档模式2. 配置主库配置文件dm.ini 文件dmmal.ini 文件dmarch.ini 文件 3. 备份主库数据库4. 备库配置新建…

计算机毕业设计hadoop+spark+hive民宿推荐系统 酒店推荐系统 民宿价格预测 酒店价预测 机器学习 深度学习 Python爬虫 HDFS集群

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

接口对象封装思想及实现-笔记

目录 接口对象封装代码分层思想 封装案例封装Tpshop商城登录Tpshop商城登录参数化 接口自动化测试框架 接口对象封装 代码分层思想 分层思想&#xff1a;将普通思想分为两层&#xff0c;分为接口对象层和测试脚本层 接口对象层&#xff1a; 对接口进行封装&#xff0c;封装好之…

【LeetCode】5. 贪心算法:买卖股票时机

太久没更了&#xff0c;抽空学习下。 看一道简单题。 class Solution:def maxProfit(self, prices: List[int]) -> int:cost -1profit 0for i in prices:if cost -1:cost icontinueprofit_ i - costif profit_ > profit:profit profit_if cost > i:cost iret…

微信小程序调用企业微信客户服务插件联通企业微信客服

需求背景:用户在小程序页面点击按钮添加企业微信的客服 相关技术:基于uniapp开发的微信小程序 插件名称:企业微信客户服务插件「联系我」插件 - 文档 - 企业微信开发者中心 仔细阅读文档「联系我」插件 - 文档 - 企业微信开发者中心 以下是我的实例代码 1.首先先小程序管…

大数据数仓实战项目(离线数仓+实时数仓)2

目录 1.课程目标和课程内容介绍 2.数仓维度建模设计 3.数仓为什么要分层 4.数仓分层思想和作用 5.数仓中表的种类和同步策略 6.数仓中表字段介绍以及表关系梳理 订单表itcast_orders 订单明细表 itcast_order_goods 商品信息表 itcast_goods 店铺表 itcast_shops 商…

【Android】jni开发之导入opencv和libyuv来进行图像处理

做视频图像处理时需要对其进行水印的添加&#xff0c;放在应用层调用工具性能方面不太满意&#xff0c;于是当下采用opencvlibyuv方法进行处理。 对于Android的jni开发不是很懂&#xff0c;我的需求是导入opencv方便在cpp中调用&#xff0c;但目前找到的教程都是把opencv作为模…