为使用 pygame 模块制作的 python 游戏文件创建可执行安装程序时出错

Error while creating an executable installer for a python game file made using pygame module

我使用 Python 的 pygame 模块制作了一个游戏。我想制作该文件的可执行安装程序,这样任何人都可以玩该游戏,而无需安装 python 或 pygame。我使用模块 cx-freeze 为我的游戏文件创建可执行文件。我将用于制作可执行安装程序的代码存储在名为 setup.py 的文件中,并将其保存在与游戏文件和游戏所需的所有其他文件相同的目录中,例如图像、声音等 当我执行命令 python setup.py build 来创建可执行文件时,我开始收到 警告 并在最后发生此错误:

Traceback (most recent call last):
  File "setup.py", line 5, in <module>
    cx_Freeze.setup(
  File "C:\Users\Armaan Barak\AppData\Local\Programs\Python\Python38\lib\site-packages\cx_Freeze\dist.py", line 342, in setup
    distutils.core.setup(**attrs)
  File "C:\Users\Armaan Barak\AppData\Local\Programs\Python\Python38\lib\distutils\core.py", line 148, in setup
    dist.run_commands()
  File "C:\Users\Armaan Barak\AppData\Local\Programs\Python\Python38\lib\distutils\dist.py", line 966, in run_commands
    self.run_command(cmd)
  File "C:\Users\Armaan Barak\AppData\Local\Programs\Python\Python38\lib\distutils\dist.py", line 985, in run_command
    cmd_obj.run()
  File "C:\Users\Armaan Barak\AppData\Local\Programs\Python\Python38\lib\distutils\command\build.py", line 135, in run
    self.run_command(cmd_name)
  File "C:\Users\Armaan Barak\AppData\Local\Programs\Python\Python38\lib\distutils\cmd.py", line 313, in run_command
    self.distribution.run_command(command)
  File "C:\Users\Armaan Barak\AppData\Local\Programs\Python\Python38\lib\distutils\dist.py", line 985, in run_command
    cmd_obj.run()
  File "C:\Users\Armaan Barak\AppData\Local\Programs\Python\Python38\lib\site-packages\cx_Freeze\dist.py", line 217, in run
    freezer.Freeze()
  File "C:\Users\Armaan Barak\AppData\Local\Programs\Python\Python38\lib\site-packages\cx_Freeze\freezer.py", line 645, in Freeze
    self._WriteModules(fileName, self.finder)
  File "C:\Users\Armaan Barak\AppData\Local\Programs\Python\Python38\lib\site-packages\cx_Freeze\freezer.py", line 536, in _WriteModules
    sourcePackageDir = os.path.dirname(module.file)
  File "C:\Users\Armaan Barak\AppData\Local\Programs\Python\Python38\lib\ntpath.py", line 223, in dirname
    return split(p)[0]
  File "C:\Users\Armaan Barak\AppData\Local\Programs\Python\Python38\lib\ntpath.py", line 185, in split
    p = os.fspath(p)
TypeError: expected str, bytes or os.PathLike object, not NoneType

显示的警告是:

running build
running build_exe
pygame 1.9.6
Hello from the pygame community. https://www.pygame.org/contribute.html
C:\Users\Armaan Barak\AppData\Local\Programs\Python\Python38\lib\site-packages\setuptools\distutils_patch.py:25: UserWarning: Distutils was imported before Setuptools. This usage is discouraged and may exhibit undesirable behaviors or errors. Please use Setuptools' objects directly or at least import Setuptools first.
  warnings.warn(
C:\Users\Armaan Barak\AppData\Local\Programs\Python\Python38\lib\site-packages\pygame\examples\chimp.py:32: SyntaxWarning: "is" with a literal. Did you mean "=="?
  if colorkey is -1:
C:\Users\Armaan Barak\AppData\Local\Programs\Python\Python38\lib\site-packages\pygame\tests\test_utils\__init__.py:162: SyntaxWarning: "is not" with a literal. Did you mean "!="?
  (rect.left is not 0 and [(rect.left-1, rect.top)] or []) +
copying c:\users\armaan barak\appdata\local\programs\python\python38\python38.dll -> build\exe.win-amd64-3.8\python38.dll
copying c:\users\armaan barak\appdata\local\programs\python\python38\vcruntime140.dll -> build\exe.win-amd64-3.8\vcruntime140.dll
copying c:\users\armaan barak\appdata\local\programs\python\python38\python3.dll -> build\exe.win-amd64-3.8\python3.dll
copying C:\Users\Armaan Barak\AppData\Local\Programs\Python\Python38\lib\site-packages\cx_Freeze\bases\Console.exe -> build\exe.win-amd64-3.8\SpaceInvader2.exe
*** WARNING *** unable to create version resource
version must be specified

安装文件中的代码:

import cx_Freeze

executable = [cx_Freeze.Executable('SpaceInvader2.py')]

cx_Freeze.setup(
    name="Space Invaders 2",
    options={
        "build_exe": {
            "packages": [
                "pygame",
                "random",
                "math",
                "pygame.mixer",
                "time"
            ],
            "include_files": [
                "spaceship.png",
                "space.png",
                "home_screen.png",
                "ammunition.png",
                "alien.png",
                "alien2.png",
                "alien3.png",
                "alien4.png",
                "player.png",
                "settings.png",
                "controls.png",
                "game_over.png",
                "background.wav",
                "laser.wav",
                "explosion.wav"
            ]
        }
    },
    executables=executable
)

游戏文件中的代码:

import pygame 
import random
import math
from pygame import mixer
import time

# Game Screen Initialization
pygame.init()
screen = pygame.display.set_mode((800, 600))

# Loading Images
icon = pygame.image.load('spaceship.png')
game_background = pygame.image.load('space.png')
home_screen_img = pygame.image.load('home_screen.png')
bullet = pygame.image.load('ammunition.png')
alien_img = pygame.image.load('alien.png')
alien_img2 = pygame.image.load('alien2.png')
alien_img3 = pygame.image.load('alien3.png')
alien_img4 = pygame.image.load('alien4.png')
player_img = pygame.image.load('player.png')
settings_img = pygame.image.load('settings.png')
controls_img = pygame.image.load('controls.png')
game_over_img = pygame.image.load('game_over.png')

# Loading sounds
mixer.music.load('background.wav')
mixer.music.play(-1)

bullet_sound = mixer.Sound('laser.wav')
explosion_sound = mixer.Sound('explosion.wav')

# Title and Icon
pygame.display.set_caption('Space Invader')
pygame.display.set_icon(icon)

# Bullet variables
bullet_x = 0
bullet_y = 480
bullet_x_change = 0
bullet_y_change = 4
bullet_state = 'ready'

# Player variables
player_x = 370
player_y = 480
player_x_change = 0

# Enemy variables
enemy_img = []
enemy_x = []
enemy_y = []
enemy_x_change = []
enemy_y_change = []
num_of_enemies = 13

# Creating 10 enemy objects
for i in range(num_of_enemies):

    enemy_img.append(random.choice([alien_img, alien_img2, alien_img3, alien_img4]))
    enemy_x.append(random.randint(0, 735))
    enemy_y.append(random.randint(50, 200))
    enemy_x_change.append(2)
    enemy_y_change.append(40)

# Score variables
score_value = 0
font = pygame.font.Font('freesansbold.ttf', 24)
over_font = pygame.font.Font('freesansbold.ttf', 80)

text_X = 10
text_y = 10

# Game Functions
def show_score(x, y):
    score = font.render('Score: ' + str(score_value), True, (165,240,67))
    screen.blit(score, (x, y))

def game_over_text():
    over_text = over_font.render('GAME OVER!', True, (255, 0, 0))
    screen.blit(over_text, (160, 250))

def player(x, y):
    screen.blit(player_img, (x, y))

def enemy(x, y, i):
    screen.blit(enemy_img[i], (x, y))

def fire_bullet(x, y):
    global bullet_state
    bullet_state = 'fire'
    screen.blit(bullet, (x + 16, y + 10))

def is_collision(enemyX, enemyY, bulletX, bulletY):
    distance = math.sqrt((math.pow(enemyX - bulletX, 2)) + (math.pow(enemyY-bulletY, 2)))
    return distance < 27

# Button colors and fonts
color = (0, 0, 0)
color_light = (100,149,237)
color_dark = (138,43,226)

# Fonts
game_name_font = pygame.font.SysFont("Chiller", 140)
btn_font = pygame.font.SysFont('Corbel Bold', 34)
random_font = pygame.font.SysFont('Arial', 60)
info_btn = pygame.font.SysFont('Century Gothic', 24)
controls_font = pygame.font.SysFont('Consolas', 15)
credits_btn = pygame.font.SysFont('Consolas', 18)

# Texts
game_text = game_name_font.render('Space Invaders!', True, (255,255,0))
play_text = btn_font.render('Play Game', True, color)
controls_text = btn_font.render('Controls', True, color)
credits_text = btn_font.render('Credits', True, color)
settings_text = btn_font.render('Settings', True, color)
quit_text = btn_font.render('Quit Game', True, color)
version_text = btn_font.render('Version 2.8', True, (255, 255, 255))
back_text = btn_font.render('Go Back', True, color)
under_development_text = random_font.render('This area is under development', True, (0, 0, 0))

# Controls text
controls_1 = controls_font.render('Left Arrow   :   Spaceship moves Left', True, (124, 252, 0))
controls_2 = controls_font.render('Right Arrow  :  Spaceship moves Right', True, (124, 252, 0))
controls_3 = controls_font.render('Spacebar     :         Shoots bullets', True, (124, 252, 0))

# Credits text
credits_line_1 = credits_btn.render("The games first version was made while learning from the pygame video", True, (255, 255, 255))
credits_line_2 = credits_btn.render("of FreeCodeCamp.org. The successive updates in version 1 and 2 are", True, (255, 255, 255))
credits_line_3 = credits_btn.render("made by Armaan Barak. No external code was copied. But help was taken", True, (255, 255, 255))
credits_line_4 = credits_btn.render("from google. Images and icons shown this game are taken from freepik.com", True, (255, 255, 255))
credits_line_5 = credits_btn.render("and flaticon.com respectively. The images were resized from", True, (255, 255, 255))
credits_line_6 = credits_btn.render("reduceimages.com and the sounds are imported from github account of", True, (255, 255, 255))
credits_line_7 = credits_btn.render("Mr. Attreya Bhatt. Direct Link to repository:", True, (255, 255, 255))
credits_line_8 = credits_btn.render("https://github.com/attreyabhatt/Space-Invaders-Pygame.", True, (255, 255, 255))
credits_line_9 = credits_btn.render("This game is free and open-source. User is independent for", True, (255, 255, 255))
credits_line_10 = credits_btn.render("manipulating code but it's encouraged that you avoid cheating.", True, (255, 255, 255))
credits_line_11 = credits_btn.render("Creator and Developer: Armaan Barak", True, (255, 255, 255))

# Game loop variables
running = True
home_screen = True
settings = False
controls = False
game_screen = False
credits_screen = False
game_over_screen = False

while running:

    # To loop through events untill event is QUIT
    for event in pygame.event.get():

        # QUIT Button
        if event.type == pygame.QUIT:
            running = False

        # Mouse key controls
        if event.type == pygame.MOUSEBUTTONDOWN:
            
            # Game is Over
            if game_over_screen:
                
                if 250 <= mouse[0] <= 385 and 520 <= mouse[1] <= 560:
                    home_screen = True
                    settings = False
                    controls = False
                    game_screen = False
                    credits_screen = False
                    game_over_screen = False

                    for i in range(num_of_enemies):
                        enemy_y[i] = random.randint(50, 200)
                    
                    player_y = 480
                    bullet_y = 480

                if 420 <= mouse[0] <= 560 and 520 <= mouse[1] <= 560:
                    quit()

            # Play button clicked
            if 230 <= mouse[0] <= 370 and 270 <= mouse[1] <= 310:
                home_screen = False
                settings = False
                controls = False
                game_screen = True
                credits_screen = False

            # Instructions button clicked
            if 400 <= mouse[0] <= 540 and 270 <= mouse[1] <= 310:
                home_screen = False
                settings = False
                controls = True
                game_screen = False
                credits_screen = False

            # Settings button clicked
            if 230 <= mouse[0] <= 370 and 350 <= mouse[1] <= 390:
                home_screen = False
                settings = True
                controls = False
                game_screen = False
                credits_screen = False
            
            # Credits button clicked
            if 400 <= mouse[0] <= 540 and 350 <= mouse[1] <= 390:
                home_screen = False
                settings = False
                controls = False
                game_screen = False
                credits_screen = True

            # Quit button clicked
            if 315 <= mouse[0] <= 455 and 430 <= mouse[1] <= 470:
                quit()

            if settings or controls or credits_screen:

                if 50 <= mouse[0] <= 165 and 30 <= mouse[1] <= 70:
                    home_screen = True
                    settings = False
                    controls = False
                    game_screen = False
                    credits_screen = False

        # KeyBoard Controls
        if event.type == pygame.KEYDOWN:

            if event.key == pygame.K_LEFT:
                player_x_change = -3

            if event.key == pygame.K_RIGHT:
                player_x_change = 3
            
            if event.key == pygame.K_SPACE:
                if bullet_state == 'ready':
                    
                    bullet_sound.play()
                    bullet_x = player_x
                    fire_bullet(bullet_x, bullet_y)

        # Reseting player's x axis speed when left or right key is lifted
        if event.type == pygame.KEYUP:

            if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT:
               player_x_change = 0
    
    # Home Screen
    if home_screen:
        
        # Displaying main screen
        screen.blit(home_screen_img, (0,0))

        # Getting Mouse Position
        mouse = pygame.mouse.get_pos()

        # Changing color of text boxes on the basis of cursor position
        # play game text
        if 230 <= mouse[0] <= 370 and 270 <= mouse[1] <= 310:
            pygame.draw.rect(screen, color_light, [230, 270, 140, 40])
        
        else:
            pygame.draw.rect(screen, color_dark,[230,270,140,40])

        # instructions text
        if 400 <= mouse[0] <= 540 and 270 <= mouse[1] <= 310:
            pygame.draw.rect(screen, color_light, [400, 270, 140, 40])
        
        else:
            pygame.draw.rect(screen, color_dark, [400, 270, 140, 40])

        # settings text
        if 230 <= mouse[0] <= 370 and 350 <= mouse[1] <= 390:
            pygame.draw.rect(screen, color_light, [230, 350, 140, 40])
        
        else:
            pygame.draw.rect(screen, color_dark, [230, 350, 140, 40])

        # credits text
        if 400 <= mouse[0] <= 540 and 350 <= mouse[1] <= 390:
            pygame.draw.rect(screen, color_light, [400, 350, 140, 40])
        
        else:
            pygame.draw.rect(screen, color_dark, [400, 350, 140, 40])

        # quit game text
        if 315 <= mouse[0] <= 455 and 430 <= mouse[1] <= 470:
            pygame.draw.rect(screen, color_light, [315, 430, 140, 40])
        
        else:
            pygame.draw.rect(screen, color_dark, [315, 430, 140, 40])

        # Displaying text
        screen.blit(game_text, (90, 100))

        # Displaying buttons
        screen.blit(play_text, (240, 280))

        screen.blit(controls_text, (421, 280))

        screen.blit(settings_text, (253, 360))

        screen.blit(credits_text, (427, 360))

        screen.blit(quit_text, (325, 440))

        # Version Display
        screen.blit(version_text, (325, 580))

        # Updating Screen
        pygame.display.update()

    # Settings Screen
    elif settings:

        screen.blit(settings_img, (0, 0))

        mouse = pygame.mouse.get_pos()

        if 50 <= mouse[0] <= 165 and 30 <= mouse[1] <= 70:
            pygame.draw.rect(screen, color_light, [50, 30, 115, 40])
        else:
            pygame.draw.rect(screen, color_dark, [50, 30, 115, 40])

        screen.blit(back_text, (60, 40))

        screen.blit(under_development_text, (60, 250))

        pygame.display.update()
    
    # Controls Screen
    elif controls:
        
        screen.blit(controls_img, (0, 0))

        mouse = pygame.mouse.get_pos()

        if 50 <= mouse[0] <= 165 and 30 <= mouse[1] <= 70:
            pygame.draw.rect(screen, color_light, [50, 30, 115, 40])
        else:
            pygame.draw.rect(screen, color_dark, [50, 30, 115, 40])

        screen.blit(back_text, (60, 40))

        screen.blit(controls_1, (255, 240))
        screen.blit(controls_2, (255, 290))
        screen.blit(controls_3, (255, 340))

        pygame.display.update()

    # Credits Screen
    elif credits_screen:

        screen.fill((85, 107, 47))

        mouse = pygame.mouse.get_pos()

        if 50 <= mouse[0] <= 165 and 30 <= mouse[1] <= 70:
            pygame.draw.rect(screen, color_light, [50, 30, 115, 40])
        else:
            pygame.draw.rect(screen, color_dark, [50, 30, 115, 40])

        screen.blit(back_text, (60, 40))

        screen.blit(credits_line_1, (40, 150))
        screen.blit(credits_line_2, (40, 180))
        screen.blit(credits_line_3, (40, 210))
        screen.blit(credits_line_4, (40, 240))
        screen.blit(credits_line_5, (40, 270))
        screen.blit(credits_line_6, (40, 300))
        screen.blit(credits_line_7, (40, 330))
        screen.blit(credits_line_8, (40, 360))
        screen.blit(credits_line_9, (40, 390))
        screen.blit(credits_line_10, (40, 420))
        screen.blit(credits_line_11, (40, 450))

        pygame.display.update()

    # Game Screen
    else:

        # Adding background image
        screen.blit(game_background, (0,0))

        # Player Movement
        player_x += player_x_change

        if player_x <= 0:
            player_x = 0
        elif player_x >= 736:
            player_x = 736

        # Handling movement of enemies
        for i in range(num_of_enemies):

            # Game Over
            if enemy_y[i] > 450:
                
                game_over_screen = True

                # Hiding Characters
                for j in range(num_of_enemies):
                    enemy_y[j] = 2000

                player_y = -2000
                bullet_y = -4000

                screen.blit(game_over_img, (0, 0))
                
                # Back button
                if 250 <= mouse[0] <= 385 and 520 <= mouse[1] <= 560:
                    pygame.draw.rect(screen, color_light, [250, 520, 140, 40])
                else:
                    pygame.draw.rect(screen, color_dark, [250, 520, 140, 40])

                # Quit Button
                if 420 <= mouse[0] <= 560 and 520 <= mouse[1] <= 560:
                    pygame.draw.rect(screen, color_light, [420, 520, 140, 40])
                else:
                    pygame.draw.rect(screen, color_dark, [420, 520, 140, 40])

                mouse = pygame.mouse.get_pos()

                screen.blit(back_text, (270, 530))

                screen.blit(quit_text, (430, 530))

            # Enemy Movement
            enemy_x[i] += enemy_x_change[i]

            if enemy_x[i] <= 0:
                enemy_x_change[i] = 2
                enemy_y[i] += enemy_y_change[i]
            elif enemy_x[i] >= 736:
                enemy_x_change[i] = -2
                enemy_y[i] += enemy_y_change[i]

            # Collision control
            collision = is_collision(enemy_x[i], enemy_y[i], bullet_x, bullet_y)

            if collision:
                
                explosion_sound.play()
                bullet_y = 480
                bullet_state = 'ready'
                score_value += 1
                enemy_x[i] = random.randint(0, 735)
                enemy_y[i] = random.randint(50, 200)

            # drawing enemy
            enemy(enemy_x[i], enemy_y[i], i)
        
        # Bullet Movement
        if bullet_y <= 0:
            bullet_y = 480
            bullet_state = 'ready'

        if bullet_state == "fire":
            fire_bullet(bullet_x, bullet_y)
            bullet_y -= bullet_y_change

        # drawing player
        player(player_x, player_y)

        # Version Display
        screen.blit(version_text, (325, 580))

        # drawing score card
        show_score(text_X, text_y)

        # Updates screen
        pygame.display.update()

我尝试使用命令 python setup.py bdist_msi 但同样的事情发生了。

我正在使用 python 版本 3.8.6rc1 我也尝试过使用 pyinstaller 但是每当我尝试 运行 pyinstaller 制作的可执行文件时它说执行失败游戏文件.

我知道这需要阅读很多内容并且很难理解。但我真的需要一些帮助。 如果有人可以建议该怎么做或如何解决这个问题,那将非常有帮助。如果您需要更多信息来为我提供解决方案,请告诉我。

P.S。所有游戏文件都保存在同一个目录中,并且游戏 运行s 没有任何错误。我遇到的唯一麻烦是为这个问题创建这个可执行安装程序。

Thanks in Advance

我以前没有用过cxFreeze,所以我不能直接帮助你解决问题,但我真的很推荐使用pyinstaller来打包python程序。我之前在 pygame 中使用过它,它运行良好,甚至不需要安装文件。

Pyinstaller可以使用pip安装,打包一个文件你调用: pyinstaller file.py,以及您可能需要的任何标志。通常,我使用 pyinstaller file.py --onefile --noconsole,它创建一个可执行文件。

Pyinstaller 不打包资产文件,所以你必须在项目中提供它们,但是项目使用的所有 python 文件(包括库)都被打包,所以项目不需要提供任何代码.

Pyinstaller 有时会不稳定,所以我建议首先创建一个简单的 hello world 文件,并使用 pyinstaller helloworld.py --onefile 打包它,然后 运行 将文件 dist/文件夹。

docs对pyinstaller的帮助并不总是很大,但我发现学习打包项目的最好方法是慢慢增加你尝试打包的项目的复杂性,并在打包后检查每个项目的工作情况.

如果您使用相关脚本和资产导入(例如 pygame.image.load('image.png')),您需要将生成的 .exe 移动到原始脚本的位置(通常创建 .exedist/ 文件夹中,如果您使用相对导入和 运行 那里的 .exe 将不起作用。

如果要添加图标,可以像这样使用 --icon 标志:pyinstaller file.py --icon=image.ico(图标文件必须是 .ico 文件 - 您可以随时在线转换器(如果需要)