PyGame Images

Adding some flair!

Introduction

In this section we will look at how you include and manipulate images in Pygame. Good quality visuals can go a long way to making a game engaging and drawing the player in.

Getting Set Up

Tom, our heroic character, standingBefore we begin, let's create a new file (call it images.py) and copy in the template code from the previous section

Once you've saved your file with the template code in it, run the file to make sure you've copied it in ok.

We will also need some images to play with. Create a directory called images in the same directory as your Python file and place these images into this directory. Right click and download these images.

  • Tom - our heroic hero.

(Right click and "Save image as ...", or similar)

It is possible to access and load the images from anywhere on the system but if you place them in the same location as the Python file it makes things a little easier. It also makes things easier when you want to package up and share your game later on.

Including Images

Including images is a three step process (though a lot of the time we can skip the second step) :

  • Load the image
  • Make some modifications to the image (resize, crop, rotate etc)
  • Render the image to the screen

Let's start by loading our image so that it is ready to use. Place the following lines of code in your main function, near the top like so :

  •   looping = True
  •  
  •   tom = pygame.image.load('images/tom_standing.png').convert_alpha()
  •   tomX = 10
  •   tomY = 40

This command will load the image into what is called a surface and save it to a variable called tom.

Here we have loaded the image into a variable within the function that it will be used. This is an ideal location to load the image if it is only going to be used within that function. It keeps things clean and local. If you will be using the image in several areas across several functions however then it may be better to load the image once at the top of the script as a global variable. This will give a slight performance boost as you won't need to keep loading the same image.

The end of the command .convert_alpha() sets your surface to manage colours and transparency in the native format for your operating system. You can leave this off and the conversion will happen when you BLIT the image to the screen (see below) but if you do this then it has to do the conversion every time the screen is updated (30, maybe 60 times a second) which will give a big hit to your game performance.

If you save and run the program you will notice that nothing has yet happened. Our screen is blank. Let's change that. Add in the following line just between filling the window and updating the display :

  •     WINDOW.fill(BACKGROUND)
  •     WINDOW.blit(tom, (tomX, tomY))
  •     pygame.display.update()

Tom in a windowThe blit command does what is called a Bit BLock Transfer and copies the bits from the surface which holds our image (in the above script, held by the variable tom) into the relevant location on the surface which is our actual WINDOW.

If you change the BACKGROUND colour you will also notice that the transparency in the png image has been brought through as well. This is a nice advantage of using PNG over JPEG as the file format for your images as otherwise your game elements will have borders around them which is a bit unsightly.

Modifying an image

In the above program you will notice that our character ( Tom ) is a little bit large in our window. There are a few ways we can rectify this. We could for instance make the window larger, or we could open up the image of Tom in our favourite image editor and scale the image down a bit. PyGame offers us a means to transform the image within our code and this is an easier and cleaner way to go about it (especially if you want to scale your image for various reasons within your game or you don't know what size will be ideal yet and just want to tinker).

Using the transform module we can perform a variety of operations on our image :

  • scale - make the image larger or smaller.
  • flip - which can be done either horizontally or vertically.
  • rotate - by a given number of degrees.

Although I've talked about an image with the operations above what they are actually processing is a surface (in this case the surface which holds the image). You may apply these operations to any surface however, not just images.

There are other operations available through the transform module as well. I've just introduced the more useful / common ones here but if you do a quick search you can discover more complex transforms available to you.

Let's make Tom a more reasonable size for our window :

  •   tom = pygame.image.load('images/tom_standing.png').convert_alpha()
  •   tom = pygame.transform.scale(tom, (50, 95))
  •   tomX = 10
  •   tomY = 40

If you add the above code in the Processing section of your game loop and run your program you should find that Tom is now half the size he was before.

newSurface = pygame.transform.scale(originalImage, (newWidth, newHeight))

What we've effectively done in the above code is take the image stored in the surface under the variable tom, resize it to be 50 pixels wide and 95 pixels high, then save it back into the original surface, tom. If we wanted to, we could have saved it into a new surface by changing the name of the first variable to something different.

The flip command can be useful if you want the character to look like he's heading in the direction he's moving.

newSurface = pygame.transform.flip(originalImage, Xflip, Yflip) #Xflip and Yflip are both either True or False

Let's start by adding in some code to make Tom move left and right :

  •         pygame.quit()
  •         sys.exit()
  •  
  •     pressed = pygame.key.get_pressed()
  •     if (pressed[K_RIGHT] or pressed[K_d]) :
  •       tomX = tomX + 3
  •     if (pressed[K_LEFT] or pressed[K_a]) :
  •       tomX = tomX - 3

Now Tom should be able to move left and right. Next we will create two surfaces, one where Tom is looking to the left and one where Tom is looking to the right :

  •   looping = True
  •  
  •   tom = pygame.image.load('images/tom_standing.png').convert_alpha()
  •   tomX = 10
  •   tomY = 40
  •   tom = pygame.transform.scale(tom, (50, 95))
  •   tomLeft = pygame.transform.flip(tom, False, True)
  •   tomRunning = tom # this line initialises the variable

And now we will add in a bit of code to change the surface to use depending on which direction Tom in going :

  •         pygame.quit()
  •         sys.exit()
  •  
  •     pressed = pygame.key.get_pressed()
  •     if (pressed[K_RIGHT] or pressed[K_d]) :
  •       tomX = tomX + 3
  •       tomRunning = tom
  •     if (pressed[K_LEFT] or pressed[K_a]) :
  •       tomX = tomX - 3
  •       tomRunning = tomLeft

And finally, let's change the surface that we blit from tom to the new tomRunning which we have now created.

  •     WINDOW.fill(BACKGROUND)
  •     WINDOW.blit(tomRunning, (tomX, tomY))
  •     pygame.display.update()

Making a Character Jump

Just for fun, let's allow Tom to jump as well. This will allow us to tinker with some simple physics.

First off, let's create two new variables to store our current jumping velocity and the jumping strength :

  •   tom = pygame.transform.scale(tom, (50, 95))
  •   tomLeft = pygame.transform.flip(tom, False, True)
  •   tomRunning = tom # this line initialises the variable
  •   tomJumpVelocity = -21
  •   jumpStrength = 20

And now we'll add in a condition to set our velocity higher when SPACE is pressed :

  •     if (pressed[K_LEFT] or pressed[K_a]) :
  •       tomX = tomX - 3
  •       tomRunning = tomLeft
  •     if (pressed[K_SPACE] and tomJumpVelocity < -jumpStrength) :
  •       tomJumpVelocity = jumpStrength

Finally, let's adjust Tom's Y coordinates based upon this velocity :

  •     # Processing
  •  
  •     if tomJumpVelocity >= -jumpStrength :
  •       tomY = tomY - tomJumpVelocity
  •       tomJumpVelocity = tomJumpVelocity - 1

We could have achieve this behaviour with a few if statements to handle moving up and moving down one after the other but doing it this way with a velocity gives us two benefits :

  • It allows us to handle both going up and going down with a single section of code. When tomJumpVelocity is positive Tom will go up and when tomJumpVelocity is negative he will head back down again.
  • By using a velocity which constantly changes, crossing over zero from positive to negative we mimic the behaviour of gravity. Instead of linear motion, Tom slows down as he reaches the top of the jump and accelerates back down to the ground.

Cropping an image

Sometimes you may only want to display part of an image. An example of when this is useful is if you create a single image which contains multiple states of your character or enemy. You can then load the image once and crop it to only show the relevant part of the image.

In PyGame we do this by creating a new surface and BLITing the relevant part of the original surface which holds the image to the new surface.

Let's demonstrate this by creating a simple reaction time game.

Download the following image which is a sprite of our reaction targets.

Here is an annotated version of the image with pixel values marked for your reference:

Annotated reaction sprite

What we're going to do is randomly pick one of the directions, crop the image to only show that direction on the screen then measure how quickly the player can hit that button.

We'll start by just checking that we can crop the image and show only a single tile.

Let's create a new file (call it reaction.py) and copy in the template code from the previous section.

Now we'll load the image (reaction_sprite.png) and crop it to only show the up arrow tile :

  •   looping = True
  •  
  •   tileSprite = pygame.image.load('images/reaction_sprite.png').convert_alpha()
  •   tileX = 125
  •   tileY = 75
  •   cropX = 0
  •   cropY = 0
  •   cropWidth = 150
  •   cropHeight = 150
  •     WINDOW.fill(BACKGROUND)
  •     WINDOW.blit(tileSprite, (tileX, tileY), (cropX, cropY, cropWidth, cropHeight))
  •     pygame.display.update()

You'll notice that the blit call looks a little bit different this time. We have added an optional argument to define the area from the original image to blit to the WINDOW. We do this by specifying the upper left corner of the area that we want to display followed by it's width and height.

If you change the value of cropY to 150 and the value of cropX to 300 which tile gets displayed now? Tinker with the values and see if you can get the other tiles to display as well.

In action

Now let's put these concepts together into a little game that we can tinker with. We're going to create a simple Left / Right reaction timer game.

Let's create a new file (call it reaction.py) and copy in the template code from the previous section

Once you've saved your file with the template code in it, run the file to make sure you've copied it in ok.

We will also need the image from the above section ( Reaction sprite ). Place this image in your images directory in the same directory as your Python file.

We will start by loading the Reaction Sprite image and setting up a few variables. Place the following code at the top of your main function :

  •   looping = True
  •  
  •   tileSprite = pygame.image.load('images/reaction_sprite.png').convert_alpha()
  •   tileX = 125
  •   tileY = 75
  •   cropX = 0
  •   cropY = 0
  •   cropWidth = 150
  •   cropHeight = 150
  •  
  •   selectedTile = random.randint(0,7) # Represents one of the 8 target keys
  •   timeTaken = 0 # How long it took to press the right key
  •   totalScore = 0 # Cumulative score for the 10 rounds
  •   found = False # Becomes True when the right key is pressed
  •   gameRound = 0 # A game will be 10 rounds

And let's also set up some code to detect when keys have been pressed :

  •         pygame.quit()
  •         sys.exit()
  •  
  •     if found == False and event.type == pygame.KEYDOWN
  •       if (event.key == K_UP and selectedTile == 0) :
  •         found = True
  •       if (event.key == K_LEFT and selectedTile == 1) :
  •         found = True
  •       if (event.key == K_DOWN and selectedTile == 2) :
  •         found = True
  •       if (event.key == K_RIGHT and selectedTile == 3) :
  •         found = True
  •       if (event.key == K_w and selectedTile == 4) :
  •         found = True
  •       if (event.key == K_a and selectedTile == 5) :
  •         found = True
  •       if (event.key == K_s and selectedTile == 6) :
  •         found = True
  •       if (event.key == K_d and selectedTile == 7) :
  •         found = True

After detecting key presses we then need to process accordingly :

  •     # Processing
  •  
  •     if found == True :
  •       gameRound = gameRound + 1
  •       print(f"Round : {gameRound}, Score : {timeTaken}")
  •       totalScore = totalScore + timeTaken
  •       timeTaken = 0
  •       selectedTile = random.randint(0,7)
  •       found = False
  •       if gameRound == 10 :
  •         looping = False # Game ended so exit the game loop
  •     else :
  •       timeTaken = timeTaken + 1

We also have to include some processing to display the right image to the player :

  •     else :
  •       timeTaken = timeTaken + 1
  •  
  •     if selectedTile <= 3 :
  •       cropY = 0
  •       cropX = selectedTile * 150
  •     else :
  •       cropY = 150
  •       cropX = (selectedTile - 4) * 150

Let's not forget to blit the image to the screen so the user can see it :

  •     WINDOW.fill(BACKGROUND)
  •     WINDOW.blit(tileSprite, (tileX, tileY), (cropX, cropY, cropWidth, cropHeight))
  •     pygame.display.update()

And finally, once the ten rounds are up, we should display the score to the player :

  •     WINDOW.fill(BACKGROUND)
  •     WINDOW.blit(tileSprite, (tileX, tileY), (cropX, cropY, cropWidth, cropHeight))
  •     pygame.display.update()
  •  
  •   print (f"Total score : {totalScore}!")
  •  
  • main()

We're done! If you save and run your script you should have a basic reaction timer game.

Activities

Even though our program doesn't do too much just yet we can still tinker with a few elements to make sure we understand them.

Have a go at the following :

1. Can you improve your images.py script so that another image is shown when Tom jumps?

2. Can you use the drawing functions to create a ground and background for Tom to run around and jump in front of?

3. Can you improve the reaction timer game by getting the images to show up on the screen in random locations instead of always in the middle? Think of any other enhancements you could create as well.