在进行python文件项目打包时,明明添加了资源文件,但是运行exe时却还是报错,显示找不到资源文件,尝试过许多方法,下面是自己觉得有用而且方便的方法,并附上实践例子。
例子使用GitHub上的开源项目https://github.com/sourabhv/FlapPyBird进行打包,对源代码进行的修改等也直接上传到了GitHub上开源https://github.com/WinriseF/FlappyBird-Pyinstaller,相较于原项目,新修改的代码在保证能够运行上可以直接进行打包
准备
首先安装pyinstaller
pip install pyinstaller
打包语法:
pyinstaller --onefile --windowed --icon=flappy.ico --add-data "assets/sprites;assets/sprites" --add-data "assets/audio;assets/audio" main.py
–onefile:将所有文件打包成一个单独的 .exe 文件。
–windowed:防止显示控制台窗口。这对于图形界面程序(如使用 Pygame 的游戏)非常有用,可以避免控制台窗口弹出。
–icon=flappy.ico:指定自定义的软件图标(flappy.ico)用于 .exe 文件。
–add-data “assets/sprites;assets/sprites”:将 assets/sprites 目录中的文件添加到打包文件中,并保留目录结构。
–add-data “assets/audio;assets/audio”:将 assets/audio 目录中的文件添加到打包文件中,并保留目录结构。
main.py:指定你的主 Python 脚本路径。
改代码
在原项目中,项目如下图
其中src
主要是代码文件,assets
主要是项目的资源文件(包括图片与声音文件),main.py
是程序入口。
使用pyinstaller打包后找不到资源文件是因为当 PyInstaller 打包时,资源文件的路径会被修改。你需要使用 sys._MEIPASS 来确定运行时的资源路径,而不是直接使用固定路径。sys._MEIPASS 是 PyInstaller 在打包后提供的临时文件夹路径。
下面对src
中的代码文件进行修改,由于源代码中对路径的引用都在 FlapPyBird\src\utils
这个文件夹下,所以只需要对其下代码修改
以images.py为例子,源代码太长放在最后,只展示关键部分,首先在首部加入:
import os #导入库
import sys #导入库
def get_resource_path(relative_path):
if hasattr(sys, '_MEIPASS'): # 当应用打包成exe时,使用 _MEIPASS 路径
return os.path.join(sys._MEIPASS, relative_path)
return os.path.join(os.path.abspath("."), relative_path)
然后在路径上做修改,在所有有路径的地方:
def __init__(self) -> None:
self.numbers = list(
(
#pygame.image.load(f"assets/sprites/{num}.png").convert_alpha()
pygame.image.load(get_resource_path(f"assets/sprites/{
num}.png")).convert_alpha()
for num in range(10)
)
)
可以看到原代码是pygame.image.load(f"assets/sprites/{num}.png").convert_alpha()
改后为pygame.image.load(get_resource_path(f"assets/sprites/{num}.png")).convert_alpha()
在路径前加了get_resource_path(),动态获取路径,同理,将所有有路径的代码均在路径前加入get_resource_path()即可,对其它代码文件进行此修改,修改后的代码https://github.com/WinriseF/FlappyBird-Pyinstaller
打包
最后使用pyinstaller --onefile --windowed --icon=flappy.ico --add-data "assets/sprites;assets/sprites" --add-data "assets/audio;assets/audio" main.py
进行打包即可。
当然如果你只需要exe文件,这里我有打包好的exe文件,下载即可https://download.csdn.net/download/2301_79442295/90096752?spm=1001.2014.3001.5503
运行如图
最后是修改例子images.py的代码
import random
from typing import List, Tuple
import os
import sys
import pygame
from .constants import BACKGROUNDS, PIPES, PLAYERS
def get_resource_path(relative_path):
if hasattr(sys, '_MEIPASS'): # 当应用打包成exe时,使用 _MEIPASS 路径
return os.path.join(sys._MEIPASS, relative_path)
return os.path.join(os.path.abspath("."), relative_path)
class Images:
numbers: List[pygame.Surface]
game_over: pygame.Surface
welcome_message: pygame.Surface
base: pygame.Surface
background: pygame.Surface
player: Tuple[pygame.Surface]
pipe: Tuple[pygame.Surface]
def __init__(self) -> None:
self.numbers = list(
(
#pygame.image.load(f"assets/sprites/{num}.png").convert_alpha()
pygame.image.load(get_resource_path(f"assets/sprites/{
num}.png")).convert_alpha()
for num in range(10)
)
)
# game over sprite
self.game_over = pygame.image.load(
get_resource_path("assets/sprites/gameover.png")
).convert_alpha()
# welcome_message sprite for welcome screen
self.welcome_message = pygame.image.load(
get_resource_path("assets/sprites/message.png")
).convert_alpha()
# base (ground) sprite
self.base = pygame.image.load(get_resource_path("assets/sprites/base.png")).convert_alpha()
self.randomize()
def randomize(self):
# select random background sprites
rand_bg = random.randint(0, len(BACKGROUNDS) - 1)
# select random player sprites
rand_player = random.randint(0, len(PLAYERS) - 1)
# select random pipe sprites
rand_pipe = random.randint(0, len(PIPES) - 1)
self.background = pygame.image.load(BACKGROUNDS[rand_bg]).convert()
self.player = (
pygame.image.load(PLAYERS[rand_player][0]).convert_alpha(),
pygame.image.load(PLAYERS[rand_player][1]).convert_alpha(),
pygame.image.load(PLAYERS[rand_player][2]).convert_alpha(),
)
self.pipe = (
pygame.transform.flip(
pygame.image.load(PIPES[rand_pipe]).convert_alpha(),
False,
True,
),
pygame.image.load(PIPES[rand_pipe]).convert_alpha(),
)