最近在刷youtube的时候发现有人用Python复刻了那个曾经超级火的小游戏——像素鸟。
这个项目是用一个叫Pygame的库做的。Pygame是一个跨平台的Python库,由Pete Shinners开发。它不仅支持图像处理,还能处理声音。最棒的是,它是建立在SDL这个更底层的库上的,这样你就可以用Python语言来开发游戏。你可以用Python来控制游戏里的图像和逻辑,而不需要担心那些复杂的底层细节。让你可以更专注于游戏的创意和玩法,而不是那些技术细节。看这感觉挺简单也挺有意思,于是就研究了一下。
废话不多说,直接贴代码。
首先引入pygame库
pip install pygame
或pip3 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