使用pygame库复刻经典游戏像素鸟

最近在刷youtube的时候发现有人用Python复刻了那个曾经超级火的小游戏——像素鸟。
游戏初始界面
这个项目是用一个叫Pygame的库做的。Pygame是一个跨平台的Python库,由Pete Shinners开发。它不仅支持图像处理,还能处理声音。最棒的是,它是建立在SDL这个更底层的库上的,这样你就可以用Python语言来开发游戏。你可以用Python来控制游戏里的图像和逻辑,而不需要担心那些复杂的底层细节。让你可以更专注于游戏的创意和玩法,而不是那些技术细节。看这感觉挺简单也挺有意思,于是就研究了一下。

废话不多说,直接贴代码。

首先引入pygame库
pip install pygamepip3 install pygame

创建游戏配置类configs.py,配置游戏参数

# 画面宽度
SCREEN_WIDTH = 288
# 画面高度
SCREEN_HEIGHT = 512
# 游戏帧数
FPS = 120
# 管道出现时间间隔
COLUMN_TIME_INTERVAL = 2000
# 小鸟跳跃高度
JUMP_HEIGHT = 260
# 小鸟起始位置
START_POSITION = 50

创建资料类asstes.py
将资源文件全部拷贝到项目根目录,然后创建资源文件管理类assets.py ,方便代码中根据文件名称获取指定文件,资源文件是从网上下载的,具体是哪个网站记不清了,不过我已经把这个项目上传到我的github上了,有需要的同学可以去下载。github地址我贴到最后。

# 通过名称获取图片资源
import os, pygame

sprites = {}

def load_sprites():
    # 获取资源文件夹路径
    path = os.path.join("assets", "sprites")
    for file in os.listdir(path):
        sprites[file.split(".")[0]] = pygame.image.load(os.path.join(path, file))

def get_sprite(name):
    return sprites[name]

游戏背景类background.py

# 游戏主背景
import pygame.sprite
import assets
from layer import Layer


class Background(pygame.sprite.Sprite):
    def __init__(self, *groups):
        self._layer = Layer.BACKGROUND
        self.sprite_name = "background"
        bg_image = assets.get_sprite("background").convert()
        # 创建矩形绘制区域 宽度为两倍,横向放置两张图片
        self.image = pygame.Surface((bg_image.get_width() * 2, bg_image.get_height()))
        # 图片放置位置
        self.image.blit(bg_image, (0, 0))
        self.image.blit(bg_image, (bg_image.get_width(), 0))

        # 检查 self.image 是否是有效的 Pygame 表面
        self.rect = self.image.get_rect(topleft=(0, 0))

        self.pos = pygame.math.Vector2(self.rect.topleft)

        super().__init__(*groups)

    def update(self, dt):
        self.pos.x -= 200 * dt
        self.rect.x = round(self.pos.x)
        # print("self.pos.x", self.pos.x)
        if self.rect.centerx <= 0:
            self.pos.x = 0

创建地面类floor.py

import pygame
import assets, configs
from layer import Layer


class Floor(pygame.sprite.Sprite):
    def __init__(self, *groups):
        self._layer = Layer.FLOOR
        self.sprite_name = "floor"
        bg_image = assets.get_sprite("floor").convert()
        # 相同图片拼接两张,由于画面是从右往左移动,但是图片只有一张,
        # 为了让画面看起来是连续的,所以需要两张图片切换来实现这种效果,
        # 在背景移动到第二张图片的边缘时,你将背景的位置重置到第一张图片的起始位置,
        # 这样玩家就感觉不到背景的切换,从而实现了无缝滚动的效果
        self.image = pygame.Surface((bg_image.get_width() * 2, bg_image.get_height()))
        # 图片放置位置
        self.image.blit(bg_image, (0, 0))
        self.image.blit(bg_image, (bg_image.get_width(), 0))

        # 检查 self.image 是否是有效的 Pygame 表面
        self.rect = self.image.get_rect(bottomleft=(0, configs.SCREEN_HEIGHT))

        self.pos = pygame.math.Vector2(self.rect.topleft)

        # mask
        self.mask = pygame.mask.from_surface(self.image)

        super().__init__(*groups)

    def update(self, dt):
        self.pos.x -= 150 * dt
        self.rect.x = round(self.pos.x)
        if self.rect.centerx <= 0:
            self.pos.x = 0

管道类column.py

import pygame, assets, configs
# 导入pygame库,assets模块(可能是用于加载资源的自定义模块)和configs模块(可能是配置文件)

from random import randint
# 从random模块导入randint函数,用于生成随机数

from layer import Layer

class Column(pygame.sprite.Sprite):

    def __init__(self, *groups):

        self._layer = Layer.OBSTACLE

        self.sprite_name = "column"

        floor_height = assets.get_sprite("floor").get_height()
        # 从assets模块获取"floor"精灵的高度

        bottom_image = assets.get_sprite("pipe-green").convert()
        # 从assets模块获取"pipe-green"精灵,并转换为新的Surface对象

        column_height = bottom_image.get_height()
        # 获取管道底部图像的高度

        top_image = pygame.transform.flip(bottom_image, False, True)
        # 垂直翻转bottom_image图像,创建管道顶部图像

        self.image = pygame.Surface((bottom_image.get_width(), configs.SCREEN_HEIGHT - floor_height), pygame.SRCALPHA)
        # 创建一个新的Surface对象作为管道的图像,大小为管道宽度和屏幕高度减去地板高度,使用SRCALPHA标志以支持透明

        max_y = 300
        # 定义管道顶部的最大Y坐标

        min_y = 100
        # 定义管道顶部的最小Y坐标

        gap = 100
        # 定义管道顶部和底部之间的间隙

        random_y = randint(min_y, max_y)
        # 随机生成管道顶部的Y坐标

        self.image.blit(top_image, (0, -random_y))
        # 在图像上绘制顶部管道

        self.image.blit(bottom_image, (0, column_height - random_y + gap))
        # 在图像上绘制底部管道

        self.rect = self.image.get_rect(topleft=(configs.SCREEN_WIDTH, 0))
        # 获取图像的矩形区域,并将其位置设置在屏幕宽度的起始位置

        self.pos = pygame.math.Vector2(self.rect.topleft)
        # 将管道的位置设置为矩形区域的左上角位置

        self.mask = pygame.mask.from_surface(self.image)
        # 从管道的图像创建一个遮罩,用于碰撞检测

        self.passed = False
        # 初始化一个标志,表示管道是否被通过

        super().__init__(*groups)

    # 管道通过小鸟下方得分
    def is_passed(self):

        if self.rect.x < configs.START_POSITION and not self.passed:
            # 如果管道的x坐标小于起始位置,并且尚未被通过
            self.passed = True
            # 将通过标志设置为True
            return True
        return False

    def update(self, dt):
        # 定义update方法,用于更新管道的状态

        self.pos.x -= 100 * dt
        # 更新管道的位置,根据传入的时间增量dt

        self.rect.x = round(self.pos.x)
        # 将管道的矩形区域的x坐标更新为位置的四舍五入值

        if self.rect.right <= -100:
            # 如果管道的矩形区域的右边界小于-100
            self.kill()

小鸟类brid.py

import pygame, assets, configs

from random import randint
from layer import Layer


class Brid(pygame.sprite.Sprite):
    def __init__(self, *groups):
        self._layer = Layer.PLAYER
        self.sprite_name = "brid"
        self.import_frames()
        self.frams_index = 0
        self.image = self.frams[self.frams_index]

        # self.rect = self.image.get_rect(midleft=(0, (configs.SCREEN_HEIGHT - assets.get_sprite("floor").convert().get_height()) / 2))
        self.rect = self.image.get_rect(topleft=(-50, 50))

        self.pos = pygame.math.Vector2(self.rect.topleft)

        # 下降重力值
        self.gravity = 800
        # 方向
        self.direction = 0

        # mask
        self.mask = pygame.mask.from_surface(self.image)

        super().__init__(*groups)

    # 获取小鸟动作集合,一共三个动作通过快速切换实现飞行动画
    def import_frames(self):
        self.frams = [
            assets.get_sprite("redbird-downflap").convert_alpha(),
            assets.get_sprite("redbird-midflap").convert_alpha(),
            assets.get_sprite("redbird-upflap").convert_alpha(),
        ]

	# 设置小鸟起始位置,在画面最左边向右平移200像素
    def start_action(self, dt):
        if self.pos.x < configs.START_POSITION:
            self.pos.x += 200 * dt
            self.rect.x = round(self.pos.x)

    def animate(self, dt):
        self.frams_index += 10 * dt
        if self.frams_index >= len(self.frams):
            self.frams_index = 0
        self.image = self.frams[int(self.frams_index)]

    def apply_gravity(self, dt):
        self.direction += self.gravity * dt
        self.pos.y += self.direction * dt
        self.rect.y = round(self.pos.y)

        if int(self.pos.y) < 0:
            # 判断小鸟的高度超出平米上方-100限制爬升
            self.pos.y = 0

	# 跳跃方法
    def jump(self):
        self.direction = -configs.JUMP_HEIGHT

	# 下坠方法
    def rotate(self):
        # 旋转角度
        rotate_bird = pygame.transform.rotozoom(self.image, -self.direction * 0.06, 1)
        self.image = rotate_bird

        # mask
        self.mask = pygame.mask.from_surface(self.image)

	# 更新数据
    def update(self, dt):
        self.start_action(dt)
        self.animate(dt)
        self.apply_gravity(dt)
        self.rotate()

创建得分类型score.py

import pygame, assets, configs
from layer import Layer

class Score(pygame.sprite.Sprite):

    def __init__(self, *groups):

        self._layer = Layer.UI
        # 设置分数精灵的层级为UI层

        self.value = 0
        # 初始化分数值为0

        self.image = pygame.surface.Surface((0, 0), pygame.SRCALPHA)
        # 创建一个初始大小为0的半透明Surface对象,用于后续绘制分数

        self.__create()
        # 调用__create方法来初始化分数的显示

        super().__init__(*groups)

    def __create(self):

        self.images = []
        # 初始化一个列表,用于存储分数数字的图像

        self.width = 0
        # 初始化分数宽度为0

        self.str_value = str(self.value)
        # 将分数值转换为字符串

        for str_value_char in self.str_value:
            img = assets.get_sprite(str_value_char)
            # 从assets模块获取对应字符的图像

            self.images.append(img)
            # 将获取的图像添加到列表中

            self.width += img.get_width()
            # 更新分数的总宽度

        self.height = self.images[0].get_height()
        # 设置分数的高度为列表中第一个图像的高度

        self.image = pygame.surface.Surface((self.width, self.height), pygame.SRCALPHA)
        # 创建一个新的Surface对象,大小为分数的总宽度和高度,使用SRCALPHA标志以支持透明

        self.rect = self.image.get_rect(center=(configs.SCREEN_WIDTH / 2, 50))
        # 获取分数图像的矩形区域,并将其中心位置设置在屏幕水平中心偏上50像素的位置

        x = 0
        # 初始化x坐标用于绘制分数数字图像

        for img in self.images:
            self.image.blit(img, (x, 0))
            # 将分数数字图像绘制到分数画布上

            x += img.get_width()
            # 更新x坐标,为下一个数字图像绘制做准备

    def update(self, dt):
        self.__create()

创建游戏开始类game_start_message.py

import pygame, assets, configs
from random import randint
from layer import Layer

class GameStartMessage(pygame.sprite.Sprite):
    # 定义一个用于显示游戏开始信息的精灵类,继承自pygame的Sprite类

    def __init__(self, *groups):

        self._layer = Layer.UI

        self.sprite_name = "game_start_message"

        self.image = assets.get_sprite("message").convert_alpha()
        # 从assets模块获取名称为"message"的精灵图像,并转换为支持透明通道的Surface

        self.rect = self.image.get_rect(center=(configs.SCREEN_WIDTH / 2, configs.SCREEN_HEIGHT / 2))
        # 获取图像的矩形区域,并将其中心设置在屏幕的中心位置

        super().__init__(*groups)

游戏结束类game_over_message.py

import pygame, assets, configs
from layer import Layer
from random import randint

class GameOverMessage(pygame.sprite.Sprite):

    def __init__(self, *groups):

        self._layer = Layer.UI

        self.sprite_name = "game_over_message"
        # 为精灵设置一个标识符,有助于识别和管理精灵

        self.image = assets.get_sprite("gameover").convert_alpha()
        # 从assets模块加载名为"gameover"的精灵图像,并转换为带有透明通道的Surface对象

        self.rect = self.image.get_rect(center=(configs.SCREEN_WIDTH / 2, configs.SCREEN_HEIGHT / 2))
        # 获取图像的矩形区域,并将其中心点设置在屏幕的水平和垂直中心位置

        super().__init__(*groups)

最后是启动类main.py

import sys
import time
import pygame, configs, assets
from objects.background import Background
from objects.floor import Floor
from objects.column import Column
from objects.brid import Brid
from objects.game_start_message import GameStartMessage
from objects.game_over_message import GameOverMessage
from objects.score import Score


class Game:
    def __init__(self):
        pygame.init()

        # title
        pygame.display.set_caption("像素鸟")

        # 设置画面像素
        self.screen = pygame.display.set_mode((configs.SCREEN_WIDTH, configs.SCREEN_HEIGHT))
        self.clock = pygame.time.Clock()

        self.running = True

        # 动作开启
        self.gamestart = False
        self.gameover = False

        # 暂停
        self.paused = False

        # 加载图片资源
        assets.load_sprites()

        self.sprites = pygame.sprite.LayeredUpdates()
        self.collision_sprites = pygame.sprite.Group()

        # 计算画面比例
        bg_height = assets.get_sprite("background").get_height()
        self.scale_factor = configs.SCREEN_HEIGHT / bg_height

        # 创建定时器,每隔一段时间更新管道
        self.column_timer = pygame.USEREVENT + 1

        # 创建精灵组
        self.brid, self.game_start_message, self.score = self.create_sprite()

    def create_sprite(self):
        self.over_msg = False
        self.start_msg = True
        Background(self.sprites)
        Floor([self.sprites, self.collision_sprites])
        Column([self.sprites, self.collision_sprites])
        return Brid(self.sprites), GameStartMessage(self.sprites), Score(self.sprites)

    def collisions(self):
        # 碰撞函数调用 参数:param1:主精灵,param2:碰撞关联的组,param3:是否销毁,param4:使用mask模式
        if pygame.sprite.spritecollide(self.brid, self.collision_sprites, False, pygame.sprite.collide_mask):
            self.game_over()

    # 游戏开始
    def game_start(self):
        # 开启管道定时器
        pygame.time.set_timer(self.column_timer, configs.COLUMN_TIME_INTERVAL)
        self.gamestart = True
        self.paused = False
        self.game_start_message.kill()

    # 游戏结束
    def game_over(self):
        self.game_over_message = GameOverMessage(self.sprites)
        self.gameover = True
        self.gamestart = False
        self.score.value = 0
        pygame.time.set_timer(self.column_timer, 0)

    def run(self):
        last_time = time.time()
        # 开启程序循环监听
        while self.running:
            # 延时时间
            dt = time.time() - last_time
            last_time = time.time()

            for event in pygame.event.get():
                # 监听退出游戏事件
                if event.type == pygame.QUIT:
                    self.running = False
                    pygame.quit()
                    sys.exit()
                if event.type == pygame.MOUSEBUTTONDOWN:
                    # 鼠标点击游戏开始
                    if not self.gamestart and not self.gameover:
                        self.game_start()
                    elif self.gamestart:
                        self.brid.jump()
                if event.type == pygame.KEYDOWN:
                    # 空格点击事件
                    if event.key == pygame.K_SPACE:
                        if not self.gamestart:
                            self.gameover = False
                            # 创建精灵组
                            self.sprites.empty()
                            self.collision_sprites.empty()
                            self.brid, self.game_start_message, self.score = self.create_sprite()
                    elif event.key == pygame.K_p:
                        print("暂停游戏")
                        self.paused = True

                if event.type == self.column_timer and self.gamestart and not self.paused:
                    Column([self.sprites, self.collision_sprites])

            self.screen.fill(0)

            # 绘制图像
            self.sprites.draw(self.screen)
            if self.gamestart and not self.paused:
                # 循环更新背景
                self.sprites.update(dt)
                # 碰撞事件
                self.collisions()

            for sprite in self.sprites:
                if type(sprite) is Column and sprite.is_passed():
                    self.score.value += 1
                    # print("得分:", self.score.value)

            pygame.display.flip()

            # 设置游戏帧数
            self.clock.tick(configs.FPS)


if __name__ == "__main__":
    game = Game()
    game.run()

到这里我们的像素鸟小游戏就完成了,鼠标单击开始游戏,然后单击是跳跃,每跳过一个管道就得一分,触碰到管道或地面游戏即结束,然后点击键盘空格键重新开始。
感谢大家的观看,项目的所有代码和资源在我都放在下面的链接里了。
项目源码地址:
https://github.com/ChuruiCode/FlappyBird-Python

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值