【Dison夏令营 Day 22】如何用 Python 制作一款 Flappy Bird 游戏

Flappy Bird 是一款经典的上瘾游戏,以其简单而富有挑战性的玩法俘获了无数人的心。在本教程中,我们将指导您使用 Pygame 模块(一种流行的 Python 游戏开发库),从头开始制作自己的 Flappy Bird 游戏。

只需对 Python 有基本的了解,再加上一点点创造力,您就可以利用 Pygame 的功能来创建一个有趣且引人入胜的游戏体验,并可根据自己的喜好进行定制。

游戏设置

首先确保电脑中已安装 Pygame,然后前往终端,使用 pip 安装 pygame 模块:

$ pip install pygame

然后,为游戏创建一个目录,并在其中创建以下 .py 文件: settings.py、main.py、world.py、game.py、pipe.py、bird.py。在游戏目录下创建另一个文件夹,命名为 assets,用于存储游戏媒体文件。以下是我们代码的文件结构:

在这里插入图片描述
现在我们可以开始编码了。让我们在 settings.py 中定义游戏变量和函数:

# settings.py
from os import walk
import pygame

WIDTH, HEIGHT = 600, 650

pipe_pair_sizes = [
    (1, 7),
    (2, 6),
    (3, 5),
    (4, 4),
    (5, 3),
    (6, 2),
    (7, 1)
]
pipe_size = HEIGHT // 10
pipe_gap = (pipe_size * 2) + (pipe_size // 2)
ground_space = 50

def import_sprite(path):
    surface_list = []
    for _, __, img_file in walk(path):
        for image in img_file:
            full_path = f"{path}/{image}"
            img_surface = pygame.image.load(full_path).convert_alpha()
            surface_list.append(img_surface)
    return surface_list

接下来,让我们在 main.py 中创建游戏的主类,其中也包含游戏的主循环:

# main.py
import pygame, sys
from settings import WIDTH, HEIGHT, ground_space
from world import World

pygame.init()

screen = pygame.display.set_mode((WIDTH, HEIGHT + ground_space))
pygame.display.set_caption("Flappy Bird")

class Main:
    def __init__(self, screen):
        self.screen = screen
        self.bg_img = pygame.image.load('assets/terrain/bg.png')
        self.bg_img = pygame.transform.scale(self.bg_img, (WIDTH, HEIGHT))
        self.ground_img = pygame.image.load('assets/terrain/ground.png')
        self.ground_scroll = 0
        self.scroll_speed = -6
        self.FPS = pygame.time.Clock()
        self.stop_ground_scroll = False

    def main(self):
        world = World(screen)
        while True:
            self.stop_ground_scroll = world.game_over
            self.screen.blit(self.bg_img, (0, 0))
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                    sys.exit()
                elif event.type == pygame.KEYDOWN:
                    if not world.playing and not world.game_over:
                        world.playing = True
                    if event.key == pygame.K_SPACE:
                        world.update("jump")
                    if event.key == pygame.K_r:
                        world.update("restart")
            world.update()
            self.screen.blit(self.ground_img, (self.ground_scroll, HEIGHT))
            if not self.stop_ground_scroll:
                self.ground_scroll += self.scroll_speed
                if abs(self.ground_scroll) > 35:
                    self.ground_scroll = 0
            pygame.display.update()
            self.FPS.tick(60)

if __name__ == "__main__":
    play = Main(screen)
    play.main()

main() 函数是游戏的入口,负责运行游戏循环。游戏循环的目的是让游戏持续运行,直到玩家退出或关闭游戏。

在循环开始时,会创建一个名为 world 的 World 实例,它代表游戏世界并保存游戏状态信息。World 类负责处理游戏机制,如小鸟的移动和与障碍物的碰撞检测。在循环中,第一步是将 self.stop_ground_scroll 设置为 world.game_over 的当前值。这将控制游戏结束时是否要停止地面滚动,从而在游戏过程之间提供平滑过渡。接下来,使用 self.screen.blit() 方法将背景图片(self.bg_img)绘制到游戏屏幕的坐标(0,0)处。这样就能确保在绘制其他元素之前,用更新的背景图片刷新屏幕。

然后,游戏会使用 pygame.event.get() 检查事件,检测任何用户输入,例如点击关闭按钮退出游戏。如果用户按下空格键,就会调用 world.update(“jump”) 方法来处理小鸟的跳跃动作。如果按下 "r "键,则会触发 world.update(“restart”) 方法,让玩家在游戏结束后重新开始游戏。

处理完事件后,world.update() 方法会更新游戏状态,并对游戏对象应用物理特性。这包括移动小鸟和障碍物、检查碰撞以及更新分数。

世界更新后,地面图像(self.ground_img)会被绘制到 self.ground_scroll 位置的屏幕上,造成地面滚动的假象。如果 self.stop_ground_scroll 为 False,地面将根据 self.scroll_speed 值水平滚动。滚动会给人一种在游戏世界中连续移动的感觉。

最后,pygame.display.update() 会被调用,以更新游戏显示屏,显示游戏循环迭代中的最新更改。self.FPS.tick(60)行确保游戏以每秒 60 帧的最大帧速率运行,从而控制游戏循环的速度,使其在不同设备上保持一致。

创建游戏世界

既然我们已经有了游戏循环和主类,接下来就创建游戏世界的类吧:

# world.py
import pygame
from pipe import Pipe
from bird import Bird
from game import GameIndicator
from settings import WIDTH, HEIGHT, pipe_size, pipe_gap, pipe_pair_sizes
import random

class World:
    def __init__(self, screen):
        self.screen = screen
        self.world_shift = 0
        self.current_x = 0
        self.gravity = 0.5
        self.current_pipe = None
        self.pipes = pygame.sprite.Group()
        self.player = pygame.sprite.GroupSingle()
        self._generate_world()
        self.playing = False
        self.game_over = False
        self.passed = True
        self.game = GameIndicator(screen)

    # adds pipe once the last pipe added reached the desired pipe horizontal spaces
    def _add_pipe(self):
        pipe_pair_size = random.choice(pipe_pair_sizes)
        top_pipe_height, bottom_pipe_height = pipe_pair_size[0] * pipe_size, pipe_pair_size[1] * pipe_size
        pipe_top = Pipe((WIDTH, 0 - (bottom_pipe_height + pipe_gap)), pipe_size, HEIGHT, True)
        pipe_bottom = Pipe((WIDTH, top_pipe_height + pipe_gap), pipe_size, HEIGHT, False)
        self.pipes.add(pipe_top)
        self.pipes.add(pipe_bottom)
        self.current_pipe = pipe_top

    # creates the player and the obstacle
    def _generate_world(self):
        self._add_pipe()
        bird = Bird((WIDTH//2 - pipe_size, HEIGHT//2 - pipe_size), 30)
        self.player.add(bird)

一旦我们的 main() 函数使用 World 类初始化了一个世界,_generate_world() 函数就会被调用,该函数通过在游戏中添加管道(使用 self._add_pipe())和小鸟玩家角色来生成游戏世界。在_add_pipe()函数中使用 random.choice(pipe_pair_sizes),我们就可以在游戏中添加一对大小随机的管道障碍物。

接下来,让我们添加一些世界组件来处理游戏物理:

# world.py
    # for moving background/obstacle
    def _scroll_x(self):
        if self.playing:
            self.world_shift = -6
        else:
            self.world_shift = 0

    # add gravity to bird for falling
    def _apply_gravity(self, player):
        if self.playing or self.game_over:
            player.direction.y += self.gravity
            player.rect.y += player.direction.y

    # handles scoring and collision
    def _handle_collisions(self):
        bird = self.player.sprite
        # for collision checking
        if pygame.sprite.groupcollide(self.player, self.pipes, False, False) or bird.rect.bottom >= HEIGHT or bird.rect.top <= 0:
            self.playing = False
            self.game_over = True
        else:
            # if player pass through the pipe gaps
            bird = self.player.sprite
            if bird.rect.x >= self.current_pipe.rect.centerx:
                bird.score += 1
                self.passed = True

_scroll_x()函数负责水平移动游戏背景和障碍物,产生滚动效果的错觉。当游戏处于 "播放 "状态时,它会将 self.world_shift 值设置为 -6,从而使游戏世界向左移动。当游戏不处于 "播放 "状态时(例如游戏结束或尚未开始),self.world_shift 会被设置为 0,从而停止滚动效果。

_apply_gravity()函数为游戏中的小鸟角色添加重力,使其逐渐下落。当游戏处于 "正在进行 "或 "游戏结束 "状态时,小鸟的 direction.y(垂直移动)会因重力值而增加,从而模拟出一个向下的力。然后,小鸟的位置会相应更新,将方向 y 值加到其当前的垂直位置上。

_handle_collisions() 函数用于管理游戏的碰撞检测、得分和游戏结束条件。它会检查玩家(小鸟)和管道之间是否发生碰撞。如果发生碰撞,或者小鸟的矩形(碰撞边界框)高于屏幕或低于游戏高度,游戏就会进入 "游戏结束 "状态,将 self.playing 设为 False,self.game_over 设为 True。否则,如果玩家成功通过管道之间的缝隙,他们的分数就会递增,self.passed 也会设为 True。这个函数有效地决定了得分和检测游戏结束碰撞的核心游戏机制。

让我们在 "世界 "类中再创建一个函数,将 "世界 "的所有特性结合起来:

# world.py
    # updates the bird's overall state
    def update(self, player_event = None):
        # new pipe adder
        if self.current_pipe.rect.centerx  <= (WIDTH // 2) - pipe_size:
            self._add_pipe()
        # updates, draws pipes
        self.pipes.update(self.world_shift)
        self.pipes.draw(self.screen)
        # applying game physics
        self._apply_gravity(self.player.sprite)
        self._scroll_x()
        self._handle_collisions()
        # configuring player actions
        if player_event == "jump" and not self.game_over:
            player_event = True
        elif player_event == "restart":
            self.game_over = False
            self.pipes.empty()
            self.player.empty()
            self.player.score = 0
            self._generate_world()
        else:
            player_event = False
        if not self.playing:
            self.game.instructions()
        # updates, draws pipes
        self.player.update(player_event)
        self.player.draw(self.screen)
        self.game.show_score(self.player.sprite.score)

update() 函数负责管理游戏的各个方面,包括滚动管道、对小鸟施加重力、处理碰撞、响应玩家输入、更新玩家分数以及控制游戏流程。

开始时,函数会跟踪当前管道的位置,当其到达屏幕上的某个点(中间减去管道大小的一半)时,就会增加一条新管道,以保持游戏的挑战性。然后,函数会更新所有管道的位置,并应用滚动效果,给人一种小鸟在动态环境中不断飞行的感觉。接下来,重力会作用于小鸟的垂直运动,使其逐渐下落。该函数确保只有在游戏处于 "播放 "或 "游戏结束 "状态时才会施加重力。

为了处理碰撞和游戏结束条件,该函数会检查小鸟与管道或屏幕边界之间的碰撞。如果发生碰撞,游戏就会进入 "游戏结束 "状态,停止游戏并显示最终得分。该函数还会对玩家输入事件做出响应。如果玩家要求跳跃(通过按键或点击屏幕),就会触发小鸟向上运动。此外,该功能还允许玩家在游戏结束后重新启动游戏,将所有游戏元素重置为初始状态。

当游戏不在 "玩 "的状态时,函数会显示游戏说明,指导玩家如何玩。最后,该功能会在游戏过程中更新并在屏幕上显示玩家的分数,每当小鸟成功穿过管道之间的缝隙时,分数就会递增。

为了处理游戏说明和玩家分数,让我们创建另一个类,并将其命名为 GameIndicator:

# game.py
import pygame
from settings import WIDTH, HEIGHT

pygame.font.init()

class GameIndicator:
    def __init__(self, screen):
        self.screen = screen
        self.font = pygame.font.SysFont('Bauhaus 93', 60)
        self.inst_font = pygame.font.SysFont('Bauhaus 93', 30)
        self.color = pygame.Color("white")
        self.inst_color = pygame.Color("black")

    def show_score(self, int_score):
        bird_score = str(int_score)
        score = self.font.render(bird_score, True, self.color)
        self.screen.blit(score, (WIDTH // 2, 50))

    def instructions(self):
        inst_text1 = "Press SPACE button to Jump,"
        inst_text2 = "Press \"R\" Button to Restart Game."
        ins1 = self.inst_font.render(inst_text1, True, self.inst_color)
        ins2 = self.inst_font.render(inst_text2, True, self.inst_color)
        self.screen.blit(ins1, (95, 400))
        self.screen.blit(ins2, (70, 450))

在 show_score() 函数中,玩家的得分会被转换成字符串,然后使用指定的字体和颜色进行渲染。生成的分数文本会显示在屏幕的水平中心和距顶部 50 像素处。至于 instructions() 函数,它会使用两个文本字符串渲染游戏说明。这些说明以不同的字体和颜色显示在屏幕上,分别出现在特定的位置(95,400)和(70,450),为玩家提供如何跳跃和重新开始游戏的指导。

添加游戏组件

对于游戏组件,我们有代表玩家角色的小鸟和作为游戏障碍物的管道。让我们先在 bird.py 中创建小鸟类。

# bird.py
import pygame
from settings import import_sprite

class Bird(pygame.sprite.Sprite):
    def __init__(self, pos, size):
        super().__init__()
        # bird basic info
        self.frame_index = 0
        self.animation_delay = 3
        self.jump_move = -9
        # bird animation
        self.bird_img = import_sprite("assets/bird")
        self.image = self.bird_img[self.frame_index]
        self.image = pygame.transform.scale(self.image, (size, size))
        self.rect = self.image.get_rect(topleft = pos)
        self.mask = pygame.mask.from_surface(self.image)
        # bird status
        self.direction = pygame.math.Vector2(0, 0)
        self.score = 0

    # for bird's flying animation
    def _animate(self):
        sprites = self.bird_img
        sprite_index = (self.frame_index // self.animation_delay) % len(sprites)
        self.image = sprites[sprite_index]
        self.frame_index += 1
        self.rect = self.image.get_rect(topleft=(self.rect.x, self.rect.y))
        self.mask = pygame.mask.from_surface(self.image)
        if self.frame_index // self.animation_delay > len(sprites):
            self.frame_index = 0

    # to make the bird fly higher
    def _jump(self):
        self.direction.y = self.jump_move

    # updates the bird's overall state
    def update(self, is_jump):
        if is_jump:
            self._jump()
        self._animate()

鸟类继承自 pygame.sprite.Sprite,因此适合与 Pygame 中的精灵组一起使用。该类初始化时有一个 position pos 和一个 size,以及与鸟的动画、移动和状态有关的各种属性。

init() 方法中,将设置小鸟的基本信息,如当前动画帧索引、动画延迟和跳跃运动(小鸟跳多高)。小鸟的动画帧是使用设置模块中的 import_sprite() 函数从指定的图像文件中加载的。动画的第一帧被缩放为所需大小,其位置被设置为 pos。用于碰撞检测的小鸟遮罩也会根据图像的透明度进行初始化。小鸟的方向由一个二维向量(pygame.math.Vector2)表示,初始设置为(0,0),表示没有初始运动。得分属性用于在游戏过程中跟踪玩家的得分。

_animate() 方法用于处理小鸟的动画。它会遍历小鸟的动画帧,以指定的延迟更新显示的图像,以创建流畅的动画。一旦到达最后一帧,动画就会重置到第一帧,从而产生循环效果。

_jump()方法负责让小鸟飞得更高。它将小鸟的 direction.y 属性更新为负值(self.jump_move),从而使小鸟在调用该方法时向上移动。

update() 方法是小鸟状态的核心。它接受一个布尔参数 is_jump,表示小鸟是否应该执行跳跃。如果 is_jump 为真,就会调用小鸟的 _jump() 方法使其向上移动。然后调用 _animate() 方法来处理小鸟的动画。

现在让我们为游戏中的管道创建一个类:

# pipe.py
import pygame

class Pipe(pygame.sprite.Sprite):
    def __init__(self, pos, width, height, flip):
        super().__init__()
        self.width = width
        img_path = 'assets/terrain/pipe.png'
        self.image = pygame.image.load(img_path)
        self.image = pygame.transform.scale(self.image, (width, height))
        if flip:
            flipped_image = pygame.transform.flip(self.image, False, True)
            self.image = flipped_image
        self.rect = self.image.get_rect(topleft = pos)

    # update object position due to world scroll
    def update(self, x_shift):
        self.rect.x += x_shift
        # removes the pipe in the game screen once it is not shown in the screen anymore
        if self.rect.right < (-self.width):
            self.kill()

管道类管理管道的外观、定位和游戏过程中的更新。

init() 方法中,管道类使用 pos(位置)、宽度、高度和翻转参数进行初始化。管道的图像文件路径会被设置,图像会被加载并缩放至指定的宽度和高度。如果 flip 为 True,图像将垂直翻转,以营造管道从顶部伸出的假象。

update() 方法负责更新因世界滚动而产生的管道位置。该方法使用 x_shift 参数,该参数代表世界滚动引起的水平移动。该方法将管道的 self.rect.x(水平位置)增加 x_shift,从而有效地随着世界滚动将管道向左移动。

此外,update() 方法还会检查管道是否已完全离开游戏屏幕的左侧(当 self.rect.right 小于 -self.width 或管道宽度时)。如果出现这种情况,它就会调用 kill() 内置方法将管道从游戏中移除,从而释放资源并保持干净的游戏环境。

现在,我们完成了游戏的编码。要测试一切是否正常,请运行 main.py 文件。下面是一些游戏快照:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在本教程中,我们探索了使用 Python 和 Pygame 库创建标志性的 Flappy Bird 游戏的过程。通过简单明了的方法,我们只用了几行代码就制作出了一款游戏,玩家需要驾驶小鸟穿过一系列管道。通过对游戏循环、物理、碰撞和动画的理解,我们构建了一个充满活力、引人入胜的游戏体验。

通过 Bird 类,我们管理鸟的动画、移动和状态,而 Pipe 类则控制管道的外观、定位和更新。World 类在协调游戏的核心机制、处理事件、更新游戏状态和渲染图形方面发挥着核心作用。

通过学习本指南,您现在已经掌握了进一步涉足 Python 游戏开发世界的知识和工具。凭借新掌握的技能,您可以探索音效、多关卡和高分跟踪等其他功能,以增强 Flappy Bird 游戏,甚至创建自己的原创游戏。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值