关于python 3.x:使用Pyinstaller制作.exe后,Pygame无法加载png

Pygame not loading png after making .exe with Pyinstaller

我一直在尝试从我的.py游戏中制作一个.exe,这确实令人沮丧。

我正在使用Python 3.5.2,Pygame 1.9.2和Pyinstaller 3.2。

该游戏以.py完美运行,但是输入pyinstaller --debug game.py之后,便生成了.exe并运行了它,我得到以下信息:

调试屏幕

这些是来自game.py的代码行,可能与该错误有关:

1
2
3
from os import path
img_dir = path.join(path.dirname(__file__), 'sprites')
title_screen = pygame.image.load(path.join(img_dir, 'title_screen.png'))

我认为这一定与pyinstaler无法获取sprites文件夹有关,因为当我尝试运行pyinstaller --icon=/sprites/icon.ico game.py时,我得到了:

图标错误屏幕

但是,如果我使用pyinstaller --icon=icon.ico game.py,则可以正常加载图标。

这是我的规格文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# -*- mode: python -*-

block_cipher = None

added_files = [
         ( '/sprites', 'sprites' ),
         ( '/music', 'music' ),
         ( 'Heavitas.ttf', '.'),
         ( 'Roboto-Light.ttf', '.'),
         ( 'high scores.csv', '.')
         ]


a = Analysis(['spec_file.py'],
             pathex=['C:\\Users\
odri\\Documents\\Code\\The Color That Fell From The Sky'],
             binaries=None,
             datas=added_files,
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          exclude_binaries=True,
          name='spec_file',
          debug=False,
          strip=False,
          upx=True,
          console=True )
coll = COLLECT(exe,
               a.binaries,
               a.zipfiles,
               a.datas,
               strip=False,
               upx=True,
               name='spec_file')


终于做到了!

如果您因为有类似问题而正在阅读本文,这是我的快速指南。 (我使用了Python 3.5.2,Pygame 1.9.2和Pyinstaller 3.2)

准备你的代码

就像C._在另一个答案上的解释(谢谢)一样,如果您正在加载这样的文件:

1
2
3
import os
folder_path = os.path.join(path.dirname(__file__), 'folder')
some_image = pygame.image.load(os.path.join(folder_path, 'some_image.png'))

而是这样做:

1
2
3
4
5
6
7
8
9
10
11
12
import sys
import os
# If the code is frozen, use this path:
if getattr(sys, 'frozen', False):
    CurrentPath = sys._MEIPASS
# If it's not use the path we're on now
else:
    CurrentPath = os.path.dirname(__file__)
# Look for the 'sprites' folder on the path I just gave you:
spriteFolderPath = os.path.join(CurrentPath, 'sprites')
# From the folder you just opened, load the image file 'some_image.png'
some_image = pygame.image.load(path.join(spriteFolderPath, 'some_image.png'))

这是必需的,因为在冻结代码时,文件将被移到与以前使用的文件夹不同的文件夹中。确保对所有文件都执行此操作。

这是另一个例子:

1
2
3
4
if hasattr(sys, '_MEIPASS'):  # the same logic used to set the image directory
    font = path.join(sys._MEIPASS, 'some_font.otf')  # specially useful to make a singlefile .exe
font = pygame.font.Font(font, size)
# Don't ask me the difference between hasattr and getattr because I don't know. But it works.

图标(可选)

如果您想要的图标不是pyinstaller的默认图标,则可以选择png图像,然后使用一些在线转换器(Google将该图像转换为.ico)。之后,将.ico文件放在.py文件所在的文件夹中。

制作规格文件

在这一点上,您应该知道要压缩的单个.exe文件还是一堆单独的文件,然后将其发送给人们。无论如何,请在您的.py文件所在的文件夹中打开终端。

如果只需要一个文件,请使用以下命令:

1
pyinstaller --onefile --icon=icon_file.ico game_file.py

如果不这样做,请使用以下命令:

1
pyinstaller --icon=icon_file.ico game_file.py

如果您不想现在设置图标,请不要使用--icon部分。您可以稍后进行更改。您以后无法更改的是--onefile选项(至少据我所知)。

将创建一个名为game_file.spec的文件。它将自动从game_file.py获得名称。如果它们的名称不同,则可以将其弄乱,所以不要立即发挥创意。如果选择单个文件,则它应如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# -*- mode: python -*-

block_cipher = None

a = Analysis(['game_file.py'],
             pathex=['C:\\some\\path\\The path where your .py and .spec are'],
             binaries=None,
             datas=None,
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          exclude_binaries=True,
          name='game_file',
          debug=False,
          strip=False,
          upx=True,
          console=True , icon='icon_file.ico')

如果选择一堆文件,则会看到以下附加部分:

1
2
3
4
5
6
7
coll = COLLECT(exe,
               a.binaries,
               a.zipfiles,
               a.datas,
               strip=False,
               upx=True,
               name='game_file')

block_cipher = None之后,您将添加游戏加载的文件。像这样:

1
2
3
4
5
6
7
8
9
10
11
12
added_files = [
         ( 'a folder', 'b folder' ),  # Loads the 'a folder' folder (left) and creates
                                      # an equivalent folder called 'b folder' (right)
                                      # on the destination path
         ( 'level_1/level2', 'level_2' ),  # Loads the 'level_2' folder
                                           # that's inside the 'level_1' folder
                                           # and outputs it on the root folder
         ( 'comic_sans.ttf', '.'),  # Loads the 'comic_sans.ttf' file from
                                    # your root folder and outputs it with
                                    # the same name on the same place.
         ( 'folder/*.mp3', '.')  # Loads all the .mp3 files from 'folder'.
         ]

现在,您必须在此处添加" added_files":

1
2
3
4
5
6
7
8
9
10
11
12
a = Analysis(['game_file.py'],
                 pathex=['C:\\some\\path\\The path where your .py and .spec are'],
                 binaries=None,
                 datas=added_files,  # Change 'None' to 'added_files' here
                                     # Leave everything else the way it is.
                 hiddenimports=[],
                 hookspath=[],
                 runtime_hooks=[],
                 excludes=[],
                 win_no_prefer_redirects=False,
                 win_private_assemblies=False,
                 cipher=block_cipher)

您还可以更改一些设置:

1
2
3
4
5
6
7
8
9
10
11
exe = EXE(pyz,
              a.scripts,
              exclude_binaries=True,
              name='game_file',  # Name of the output file. Equivalent to '--name'
                                 # Don't change it.
              debug=False,  # If True shows a debug screen on start. Equivalent to '--debug'.
              strip=False,
              upx=True,  # Compresses executable files and libraries
              # If console=True, a console screen will be shown on start up.
              # icon= is the location of the icon of the exe.
              console=True , icon='icon_file.ico')

如果您没有像我在注释中告诉您的那样更改exe的路径或输出名称,我们只需要运行它,就会更新以前创建的.exe文件。在命令窗口中输入以下内容:

1
pyinstaller game_file.spec

请记住,game_file.spec是我们刚刚编辑的文件," game_file"是我用作示例的随机名称。还要注意,没有--some_option,因为它们不能与spec文件一起使用。这就是为什么您必须直接在脚本中更改它们的原因。 --onefile在这里也不起作用,无法在脚本中完成,这就是为什么我之前告诉过您这样做的原因。

您会发现在.spec文件所在的同一文件夹上创建了两个文件夹。名为" Dist"的文件包含exe文件,如果您不使用--onefile,它还应该包含一堆其他文件,您需要将它们与exe一起压缩以与其他人共享应用程序。也将有一个'Buid'文件夹,但我不知道它的用途,因为使用该应用程序不需要它。

就是这样。它应该为您工作。

当我问这个问题时,我的错误是我不知道sys._MEIPASS部分(再次感谢C._),我的spec文件的名称与我的py文件不同,我使用了'/sprites'而不是'sprites'added_files中,并且不知道我应该运行spec文件而不是py文件。

有关Pyinstaller的更多信息,请查看手册,但是由于它远非易事,有时甚至会引起误解,因此使用Google最好。


使用PyInstaller进行编译时,在运行exe时,所有文件都将移至其他目录。 因此,要生成此位置,请在生成路径之前将其添加到代码的开头

1
2
3
4
5
6
7
8
import sys

if getattr(sys, 'frozen', False): # PyInstaller adds this attribute
    # Running in a bundle
    CurrentPath = sys._MEIPASS
else:
    # Running in normal Python environment
    CurrentPath = os.path.dirname(__file__)

然后可以从您的位置生成所有文件夹路径

1
spriteFolderPath = path.join(CurrentPath, 'sprites') # Do the same for all your other files

然后,当您确定要执行的位置时,可以从此处获取所有文件:

1
title_screen = pygame.image.load(path.join(spriteFolderPath, 'title_screen.png')) # Use spriteFolderPath instead of img_dir

我还看到您还有其他字体/材料,您可以执行相同的操作来加载它们

1
fontRobotoLight = pygame.font.Font(path.join(CurrentPath, 'Roboto-Light.ttf'))

对于您的图标,只需在主文件夹中粘贴一个临时icon.ico并输入
pyinstaller -i"icon.ico""spec_file.spec"

最后,由于我之前遇到过相同的问题,我建议您仅通过运行pyinstaller"spec_file.spec"来编译exe