Pygame: Alien Invasion Ship Image

Viewed 1513

Hi I am currently trying to create space invasion from chapter 12 in Python Crash Course. However I am stuck when trying to add the ship image to the game. Here is the code for Alien Invasion:

import sys
import pygame
from settings import Settings
from ship import Ship
class AlienInvasion:
#Overall class to manage game assests and behavior

    def __init__(self):
        #Initialize the game and create game resources
        pygame.init()
        self.settings=Settings()
    
        self.screen=pygame.display.set_mode((self.settings.screen_width,self.settings.screen_height))
        pygame.display.set_caption('Alien Invasion')

       self.Ship=Ship(self)



    def run_game(self):
        # Start the main loop for the game
        while True:
           #Watch for keyboard and mouse events
            for event in pygame.event.get():
                 if event.type==pygame.QUIT  :
                     sys.exit()
            #Redraw the screen during each pass through the loop.
             self.screen.fill(self.settings.bg_color)

            self.Ship.blitme()
            #Make the most recently drawn screen visible.
            pygame.display.flip()

if __name__=='__main__':
#Make a game instance, and run the game.
ai=AlienInvasion()
ai.run_game()

Ship Code:

import pygame


 class Ship:
     #A class to manage the ship
     def __init__(self, ai_game):
       #Initialize the ship and set its starting position
       self.screen=ai_game.screen
       self.screen_rect=ai_game.screen.get_rect()

        #Load the ship image and get its rect.
        self.image=pygame.image.load('images/ship.bmp')
        self.rect=self.image.get_rect()

        #start each new ship at the bottom center of the screen.
        self.rect.midbottom=self.screen_rect.midbottom

    def blitme(self):
        #Draw the ship at its current location
        self.scren.bilt(self.image, self.rect)
    

When trying to run it i get this error:

Traceback (most recent call last):
File "c:/Users/rico3/OneDrive/Desktop/Python Scripts/Alien Invasion/Alien_Invaders.py", line 39, in <module>
 ai=AlienInvasion()
 File "c:/Users/rico3/OneDrive/Desktop/Python Scripts/Alien Invasion/Alien_Invaders.py", line 19, in __init__
self.Ship=Ship(self)
File "c:\Users\rico3\OneDrive\Desktop\Python Scripts\Alien Invasion\ship.py", line 12, in __init__
self.image=pygame.image.load('images/ship.bmp')
pygame.error: Couldn't open images/ship.bmp

The images folder is in the same folder as the Alien Invasion file yet it still cant seem to find the ship file.

2 Answers

This question comes up a lot, so I'll aim for a canonical answer.

Basically the code is trying to load a file, but it is assuming it's running in the same directory as the files it needs. This problem isn't limited to Python or PyGame, it's a general programming problem. It becomes more of a problem when PyGame programs are packed into executables, which are unpacked to who-knows-where at runtime.

So, what can you do? First, don't assume anything. On startup, "feel around" the environment - work out where the code is running from.

Your current directory is available from the os module:

import os
print( "Running in directory: " + os.getcwd() ) 

If I run this script from different locations, notice the different result:

tmp> python3 ~/Code/Python/PyGame/working_dir.py
Running in directory: /tmp

And from the same directory:

PyGame> python3 ./working_dir.py 
Running in directory: /home/kingsley/Code/Python/PyGame

In your case, it's like the first output - the current directory is not the same as where your images/ are. So in this case, you can use the os.path module diranme() and realpath() functions to determine the full path to your script. This works well because it's the same no matter what directory the script is launched from.

import os
import os.path

exe_location = os.path.dirname( os.path.realpath( __file__ ) )

print( "Running in directory: " + os.getcwd() ) 
print( "Running script from:  " + exe_location )

Giving:

tmp> python3 ~/Code/Python/PyGame/working_dir.py
Running in directory: /tmp
Running script from:  /home/kingsley/Code/Python/PyGame

Now this path can be used as the offset for your images directory. This brings me to my next point. Don't do things like:

path = exe_location + "\\" + images_dir + "\\" + subdir1  # <-- WRONG!

Your code will fail on any system that doesn't use the same directory separators as you. Use the os.path module function join(). It knows the correct path-separator for the local system, and handles any clean-up of double separators and the like, correctly.

path = os.path.join( exe_location, images_dir, subdir1 )  # <-- PERFECT!

So in your code:

import os.path

# [...]

if __name__=='__main__':
    exe_location = os.path.dirname( os.path.realpath( __file__ ) )

Then use exe_location as a global, or pass it into your class constructors as a parameter. When loading images (or sounds, or fonts, etc.), calculate the correct absolute path with os.path.join():

class Ship:
    #A class to manage the ship
    def __init__( self, ai_game, install_path ):
        #Initialize the ship and set its starting position
        [ ... ]

        #Load the ship image and get its rect.
        filename = os.path.join( install_path, "images", "ship.bmp" )
        self.image=pygame.image.load( filename )
        self.rect=self.image.get_rect()

You may also want to be careful of the alphabetical case of your directory and file names. On some file systems, "Images/" is not the same as "images/". Use what ever case you want, but keep it exact.

By the way, did you make a directory "images" containing ship.bmp and include it in the main project folder, as shown in the screenshots.

vscode:

screen shot from vscode

terminal:

screen shot from terminal

Related