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

前述

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

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

pygame 实现 flappybird 并打包成 exe 运行文件_第1张图片
书中源码如下:

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 运行文件_第2张图片
Python 包的安装默认使用的外国源地址,为了加快安装速度我们进行换源

pip help install

向下滑动可以看到-i, --index-url 默认为 https://pypi.org/simple
pygame 实现 flappybird 并打包成 exe 运行文件_第3张图片
选择国内源,例如选择清华的源直接安装你可以输入为(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 运行文件_第4张图片
例如安装 pygame,就会默认启用清华源,由于我已经安装,所以显示 Requirement already satisfied

pip install pygame

pygame 实现 flappybird 并打包成 exe 运行文件_第5张图片

二、算法模块与逻辑

模块简介

作用 对应函数,模块或方法
鸟类 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 运行文件_第6张图片

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

pygame 实现 flappybird 并打包成 exe 运行文件_第7张图片
鼠标的标志位 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 运行文件_第8张图片

四、Pyinstaller打包

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

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

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

到此就完成了 flappybird 小游戏

后述

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

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

你可能感兴趣的:(pygame 实现 flappybird 并打包成 exe 运行文件)