欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

pygame 实现 flappybird 并打包成 exe 运行文件

程序员文章站 2024-02-03 22:27:10
...

前述

本次的 flappybird 小游戏源文件来自于 《零基础学Python》(全彩版),本次的实现是在此基础上更改而来,源代码只有一个flappybird.py 文件,主要更改部分为:

1.封装
2.添加开始与再来一次按钮 
3.更改界面内鼠标样式
4.打包exe

pygame 实现 flappybird 并打包成 exe 运行文件
书中源码如下:

import pygame
import sys
import random

class Bird(object):
    """定义一个鸟类"""
    def __init__(self):
        """定义初始化方法"""
        self.birdRect = pygame.Rect(65, 50, 50, 50) # 鸟的矩形
        # 定义鸟的3种状态列表
        self.birdStatus = [pygame.image.load("assets/1.png"),
                            pygame.image.load("assets/2.png"),
                            pygame.image.load("assets/dead.png")]   
        self.status = 0     # 默认飞行状态
        self.birdX = 120    # 鸟所在X轴坐标,即是向右飞行的速度               
        self.birdY = 350    # 鸟所在Y轴坐标,即上下飞行高度
        self.jump = False   # 默认情况小鸟自动降落
        self.jumpSpeed = 10 # 跳跃高度 
        self.gravity = 5    # 重力     
        self.dead = False   # 默认小鸟生命状态为活着 

    def birdUpdate(self):
        if self.jump:
            # 小鸟跳跃
            self.jumpSpeed -= 1             # 速度递减,上升越来越慢
            self.birdY -= self.jumpSpeed    # 鸟Y轴坐标减小,小鸟上升    
        else:   
            # 小鸟坠落
            self.gravity += 0.2             # 重力递增,下降越来越快
            self.birdY += self.gravity      # 鸟Y轴坐标增加,小鸟下降
        self.birdRect[1] = self.birdY       # 更改Y轴位置

class Pipeline(object):
    """定义一个管道类"""
    def __init__(self):
        """定义初始化方法"""
        self.wallx    = 400;    # 管道所在X轴坐标
        self.pineUp   = pygame.image.load("assets/top.png")
        self.pineDown = pygame.image.load("assets/bottom.png")
        
    def updatePipeline(self):
        """"管道移动方法"""
        self.wallx -= 5       # 管道X轴坐标递减,即管道向左移动
        # 当管道运行到一定位置,即小鸟飞越管道,分数加1,并且重置管道
        if self.wallx < -80: 
            global score 
            score += 1
            self.wallx = 400

def createMap():
    """定义创建地图的方法"""
    screen.fill((255, 255, 255))      # 填充颜色
    screen.blit(background, (0, 0))   # 填入到背景

    # 显示管道
    screen.blit(Pipeline.pineUp,(Pipeline.wallx,-300));    # 上管道坐标位置 
    screen.blit(Pipeline.pineDown,(Pipeline.wallx,500)); # 下管道坐标位置
    Pipeline.updatePipeline()      # 管道移动

    # 显示小鸟
    if Bird.dead:           # 撞管道状态
        Bird.status = 2
    elif Bird.jump:         # 起飞状态
        Bird.status = 1 
    screen.blit(Bird.birdStatus[Bird.status], (Bird.birdX, Bird.birdY)) # 设置小鸟的坐标    
    Bird.birdUpdate()           # 鸟移动

    # 显示分数
    screen.blit(font.render('Score:'+str(score),-1,(255, 255, 255)),(100, 50)) # 设置颜色及坐标位置
    pygame.display.update()     # 更新显示

def checkDead():
    # 上方管子的矩形位置
    upRect = pygame.Rect(Pipeline.wallx,-300,
                         Pipeline.pineUp.get_width() - 10,
                         Pipeline.pineUp.get_height())

    # 下方管子的矩形位置
    downRect = pygame.Rect(Pipeline.wallx,500,
                           Pipeline.pineDown.get_width() - 10,
                           Pipeline.pineDown.get_height())    
    # 检测小鸟与上下方管子是否碰撞
    if upRect.colliderect(Bird.birdRect) or downRect.colliderect(Bird.birdRect):
        Bird.dead = True
    # 检测小鸟是否飞出上下边界
    if not 0 < Bird.birdRect[1] < height:
        Bird.dead = True    
        return True
    else :
        return False  

def getResutl():
    final_text1 = "Game Over" 
    final_text2 = "Your final score is:  " + str(score) 
    ft1_font = pygame.font.SysFont("Arial", 70)               # 设置第一行文字字体
    ft1_surf = font.render(final_text1, 1, (242,3,36))  # 设置第一行文字颜色
    ft2_font = pygame.font.SysFont("Arial", 50)               # 设置第二行文字字体
    ft2_surf = font.render(final_text2, 1, (253, 177, 6)) # 设置第二行文字颜色
    screen.blit(ft1_surf, [screen.get_width()/2 - ft1_surf.get_width()/2, 100]) # 设置第一行文字显示位置
    screen.blit(ft2_surf, [screen.get_width()/2 - ft2_surf.get_width()/2, 200]) # 设置第二行文字显示位置
    pygame.display.flip()      # 更新整个待显示的Surface对象到屏幕上

if __name__ == '__main__':
    """主程序"""
    pygame.init()       # 初始化pygame
    pygame.font.init()  # 初始化字体
    font = pygame.font.SysFont("Arial", 50)    # 设置字体和大小
    size   = width, height = 400, 650       # 设置窗口
    screen = pygame.display.set_mode(size)  # 显示窗口
    clock  = pygame.time.Clock()             # 设置时钟
    Pipeline = Pipeline() # 实例化管道类
    Bird = Bird()         # 实例化鸟类
    score = 0
    while True:
        clock.tick(60)  # 每秒执行60次
        # 轮询事件
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
            if (event.type == pygame.KEYDOWN or event.type == pygame.MOUSEBUTTONDOWN) and not Bird.dead:
                Bird.jump = True    # 跳跃
                Bird.gravity = 5    # 重力
                Bird.jumpSpeed = 10 # 跳跃速度            

        background = pygame.image.load("assets/background.png") # 加载背景图片
        if checkDead() : # 检测小鸟生命状态
            getResutl()  # 如果小鸟死亡,显示游戏总分数
        else :
            createMap()  # 创建地图
    pygame.quit()

本次文章的所有代码我已上传至 GitHub,README有详细说明

程序编写

一、Pygame安装

我使用的 Anaconda Python 环境,关于此环境的创建参考 NO.1 Tensorflow在win10下实现object detection
pygame 实现 flappybird 并打包成 exe 运行文件
Python 包的安装默认使用的外国源地址,为了加快安装速度我们进行换源

pip help install

向下滑动可以看到-i, --index-url 默认为 https://pypi.org/simple
pygame 实现 flappybird 并打包成 exe 运行文件
选择国内源,例如选择清华的源直接安装你可以输入为(XXXX为模块名):

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple XXXX

也可以设置为默认源

pip install pip -U
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

pygame 实现 flappybird 并打包成 exe 运行文件
例如安装 pygame,就会默认启用清华源,由于我已经安装,所以显示 Requirement already satisfied

pip install pygame

pygame 实现 flappybird 并打包成 exe 运行文件

二、算法模块与逻辑

模块简介

作用 对应函数,模块或方法
鸟类 bird.py
障碍 pipeline.py
鼠标 mouse.py
主程序入口 main.py
主画面 create_map(flag1, flag2)
开始按钮 start_button()
再来按钮 again_button(score_get)
得分 get_result.(score_get)
死亡检测 check_dead(flag3, flag4)

逻辑简化

T = True
F = False
一队括弧代表(Flag, again_flag)的 bool值

pygame 实现 flappybird 并打包成 exe 运行文件

通过 Flag, again_flag 的 bool 值来判断游戏界面和选择主界面运行的方式
Flag, again_flag 的 bool 值受到鸟的存活状态控制

pygame 实现 flappybird 并打包成 exe 运行文件
鼠标的标志位 mouse_shape 由主程序画面与鼠标所在区域控制

三、程序编写

我编写程序用的 Pycharm 编辑器,这是具有免费开源社区版的(我用的**专业版????)
main.py,主程序入口

import pygame
import sys
import mouse
from bird import Bird
from pipeline import Pipeline


def create_map(flag1, flag2):
    mouse_flag = False
    screen.fill((255, 255, 255))
    screen.blit(background, (0, 0))

    screen.blit(Pipeline.pineUp, (Pipeline.wallX, -300))
    screen.blit(Pipeline.pineDown, (Pipeline.wallX, 500))
    Pipeline.update_pipeline()

    if Bird.dead:
        Bird.status = 2
    elif Bird.jump:
        Bird.status = 1


    if flag1 and not flag2:
        screen.blit(Bird.birdStatus[Bird.status], (120, 350))
        Bird.bird_update()
        start_button()
        mouse_flag = True
        Pipeline.score = 0

    elif not flag1 and not flag2:
        screen.blit(Bird.birdStatus[Bird.status], (Bird.birdX, Bird.birdY))
        Bird.bird_update()
        screen.blit(font.render('Sore:' + str(Pipeline.score), -1, (255, 255, 255)), (100, 50))
        global static_score
        static_score = Pipeline.score
        mouse_flag = False

    elif not flag1 and flag2:
        get_result(static_score)
        mouse_flag = True


    elif flag1 and flag2:
        screen.blit(Bird.birdStatus[Bird.status], (Bird.birdX, Bird.birdY))
        start_button()
        mouse_flag = True
        Bird.bird_update()

    pygame.display.update()
    return mouse_flag


def check_dead(flag3, flag4):
    up_rect = pygame.Rect(Pipeline.wallX, -300,
                          Pipeline.pineUp.get_width() - 10,
                          Pipeline.pineUp.get_height())

    down_rect = pygame.Rect(Pipeline.wallX, 500,
                            Pipeline.pineDown.get_width() - 10,
                            Pipeline.pineDown.get_height())

    if not flag3 and not flag4:
        if up_rect.colliderect(Bird.birdRect) or down_rect.colliderect(Bird.birdRect):
            Bird.dead = True
        if not 0 < Bird.birdRect[1] < height:
            Bird.dead = True
            return True
        else:
            return False
    elif flag3 and flag4:
        Bird.dead = False
        return False


def get_result(score_get):
    final_text1 = 'Game_Over'
    final_text2 = 'Your final score is:' + str(score_get)
    ft1_font = pygame.font.SysFont('Arial', 70)
    ft2_font = pygame.font.SysFont('Arial', 50)
    ft1_surf = ft1_font.render(final_text1, 1, (242, 3, 36))
    ft2_surf = ft2_font.render(final_text2, 1, (253, 177, 6))

    screen.blit(ft1_surf, [screen.get_width() / 2 - ft1_surf.get_width() / 2, 100])
    screen.blit(ft2_surf, [screen.get_width() / 2 - ft2_surf.get_width() / 2, 200])
    again_button(static_score)
    pygame.display.flip()


def start_button():
    button_color = (72, 61, 139)
    text_color = (255, 255, 255)
    start_font = pygame.font.SysFont('Arial', 40)
    img_button = start_font.render("Start", True, text_color, button_color)
    screen.blit(img_button, [screen.get_width() / 2 - img_button.get_width() / 2,
                             screen.get_height() / 2 - img_button.get_height() / 2])
    screen.blit(font.render('Sore:' + str(0), -1, (255, 255, 255)), (100, 50))
    pygame.display.flip()


def again_button(score_get):
    button_color = (72, 61, 139)
    text_color = (255, 255, 255)
    start_font = pygame.font.SysFont('Arial', 40)
    img_button = start_font.render("Start Again", True, text_color, button_color)
    screen.blit(img_button, [screen.get_width() / 2 - img_button.get_width() / 2,
                             screen.get_height() / 2 - img_button.get_height() / 2])
    screen.blit(font.render('Sore:' + str(score_get), -1, (255, 255, 255)), (100, 50))
    pygame.display.flip()


if __name__ == '__main__':
    pygame.init()
    pygame.font.init()
    font = pygame.font.SysFont('Arial', 50)
    size = width, height = 400, 650
    screen = pygame.display.set_mode(size)
    clock = pygame.time.Clock()
    Pipeline = Pipeline()
    Bird = Bird()
    Flag = True
    again_flag = False
    mouse_shape = False

    while True:
        clock.tick(60)
        x, y = pygame.mouse.get_pos()
        click = pygame.mouse.get_pressed()

        if x in range(127, 273) and y in range(311, 339) and not Flag and again_flag:
            if click[0] == 1:
                Bird.empty()
                Pipeline.empty()
                Flag = True
            if mouse_shape:
                mouse.TestCursor(mouse.no)
            else:
                mouse.TestCursor(mouse.arrow)
        elif x in range(127, 273) and y in range(311, 339) and not Flag and not again_flag:
            if mouse_shape:
                mouse.TestCursor(mouse.no)
            else:
                mouse.TestCursor(mouse.arrow)
        elif x in range(168, 232) and y in range(311, 339) and Flag and not again_flag:
            if click[0] == 1:
                Flag = False
                Bird.jump = True
            if mouse_shape:
                mouse.TestCursor(mouse.no)
            else:
                mouse.TestCursor(mouse.arrow)
        else:
            mouse.TestCursor(mouse.arrow)

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            if (event.type == pygame.KEYDOWN or event.type == pygame.MOUSEBUTTONDOWN) and not Bird.dead and not Flag:
                Bird.jump = True
                Bird.gravity = 5
                Bird.jump_speed = 10
            elif (event.type == pygame.KEYDOWN or event.type == pygame.MOUSEBUTTONDOWN) and not Bird.dead and again_flag:
                Bird.jump = True
                Bird.gravity = 5
                Bird.jump_speed = 10
        background = pygame.image.load('picture/background.png')
        if check_dead(Flag, again_flag):
            again_flag = True
            if create_map(Flag, again_flag):
                mouse_shape = True
            else:
                mouse_shape = False
        else:
            if create_map(Flag, again_flag):
                mouse_shape = True
            else:
                mouse_shape = False
            again_flag = False

bird.py 描述 bird 类

import pygame


class Bird(object):
    """Bird definition"""
    def __init__(self):
        self.birdRect = pygame.Rect(65, 50, 50, 50)
        self.birdStatus = [pygame.image.load('picture/1.png'),
                           pygame.image.load('picture/2.png'),
                           pygame.image.load('picture/dead.png')]
        self.status = 0
        self.birdX = 120
        self.birdY = 350
        self.jump = False
        self.jump_speed = 10
        self.gravity = 5
        self.dead = False

    def bird_update(self):
        if self.jump:
            self.jump_speed -= 1
            self.birdY -= self.jump_speed

        else:
            self.gravity += 0.2
            #self.birdY += self.gravity

        self.birdRect[1] = self.birdY

    def empty(self):
        self.status = 0
        self.birdX = 120
        self.birdY = 350
        self.jump = False
        self.jump_speed = 10
        self.gravity = 5
        self.dead = False

pipeline.py 描述管道障碍类

import pygame


class Pipeline(object):
    def __init__(self):
        self.wallX = 400
        self.score = 0
        self.pineUp = pygame.image.load('picture/top.png')
        self.pineDown = pygame.image.load('picture/bottom.png')

    def update_pipeline(self):
        self.wallX -= 5
        if self.wallX < -80:
            self.score += 1
            self.wallX = 400

    def empty(self):
        self.wallX = 400
        self.score = 0
        

mouse.py 描述鼠标形态,arrow,no自行绘制,此程序由 pygame提供的官方示例 cursors.py 修改而来,这个示例可以单独运行,我的文件目录在

E:\Anaconda3\envs\tensorflow\Lib\site-packages\pygame\examples\cursors.py
import pygame


arrow = ( "xX                      ",
          "X.X                     ",
          "X..X                    ",
          "X...X                   ",
          "X....X                  ",
          "X.....X                 ",
          "X......X                ",
          "X.......X               ",
          "X........X              ",
          "X.........X             ",
          "X......XXXXX            ",
          "X...X..X                ",
          "X..XX..X                ",
          "X.X XX..X               ",
          "XX   X..X               ",
          "X     X..X              ",
          "      X..X              ",
          "       X..X             ",
          "       X..X             ",
          "        XX              ",
          "                        ",
          "                        ",
          "                        ",
          "                        ")


no = (  "xX                      ",
        "X.X                     ",
        "X..X                    ",
        "X...X                   ",
        "X....X                  ",
        "X.....X                 ",
        "X......X                ",
        "X.......X               ",
        "X........X              ",
        "X.........X             ",
        "X..........X            ",
        "X...X..X....X           ",
        "X..X     X...X          ",
        "X.X        X..X         ",
        "XX           X.X        ",
        "X              XX       ",
        "                        ",
        "                        ",
        "                        ",
        "                        ",
        "                        ",
        "                        ",
        "                        ",
        "                        ")

def TestCursor(arrow):
    hotspot = None
    for y in range(len(arrow)):
        for x in range(len(arrow[y])):
            if arrow[y][x] in ['x', ',', 'O']:
                hotspot = x,y
                break
        if hotspot != None:
            break
    if hotspot == None:
        raise Exception("Error")
    s2 = []
    for line in arrow:
        s2.append(line.replace('x', 'X').replace(',', '.').replace('O',
'o'))
    cursor, mask = pygame.cursors.compile(s2, 'X', '.', 'o')
    size = len(arrow[0]), len(arrow)
    pygame.mouse.set_cursor(size, hotspot, cursor, mask)

开始运行
pygame 实现 flappybird 并打包成 exe 运行文件

四、Pyinstaller打包

安装 Pyinstaller,有的版本可能不会默认安装 pywin32,所以多此一举吧

pip install pyinstaller
pip install pywin32(多此一举)

可选步骤: 选用一个图片制作成 exe 文件的图片,我选择了 picture 文件夹下的 1.png
pygame 实现 flappybird 并打包成 exe 运行文件
我们使用格式工厂转换为 ico 文件,格式工厂下载点此
最后文件的目录结构如下:
pygame 实现 flappybird 并打包成 exe 运行文件
pygame 实现 flappybird 并打包成 exe 运行文件
将python环境导向至目录文件夹下:
输入以下命令
pyinstaller -F -w -i logo.ico main.py
pygame 实现 flappybird 并打包成 exe 运行文件
最后在生成的 dist 文件夹具有 exe 文件,将程序需要的图片拷贝该目录下,双击即可运行,如下
pygame 实现 flappybird 并打包成 exe 运行文件
到此就完成了 flappybird 小游戏

后述

Pyinstaller 与编译器对程序的要求是具有差异的,有可能编译器能运行,但程序打包后出现各种各样的情况,甚至无法打包成功,本次程序有如下几点注意

1.font 的参数不能出现 None(编译器可以运行,Pyinstaller 打包无法正常运行)
2.需要的媒体文件需要拷贝至可执行文件处(Failed to execute script main)
3.ico 图标文件不能将图片直接修改后缀得到来使用,必须转换才能使用(报缓存错误)