Introduction to Game Development in Python (part 3)
In the previous article, we delved deeper into the objects and Surfaces in Pygame. We also learned how to draw some primitive graphics on the screen. In today’s post, we will make all of the drawings come alive!
Animation
Now that we know how to get the Pygame framework to draw on the screen, let’s learn how to make animated pictures. First, we need to know how the brain perceives animation. When our brain “sees” an image, it lasts on our brain for some time. This is called the persistence of vision. If our eyes are shown a number of images in succession, the brain perceives the images to be continuous. In short, if we show the eyes images that differ minorly in quick succession, it produces an illusion of motion. Here’s an example:
Suppose the window that we are showing currently is 6 pixels wide and 1 pixel tall. All the pixels are white except for a black pixel at (4,0). It would look something like this:
If you changed the window, so that the black spot is now present on (3,0) instead of (4,0), it would look like this:
To the user, it looks like the black pixel has “moved” to the left. We again redraw the window to have the black pixel at (2,0), it would continue to look like the black pixel is moving left:
It may look like the black pixel is moving, but it is just an illusion. The computer is just showing three different images that each just happens to have one black pixel.
The trick to making believable animations is to have your program draw a picture to the window, wait a fraction of a second, and then draw a slightly different picture.
Here is an example program demonstration simple animation using a rectangle object is Pygame:
Some screenshots from the program:
Working with Images
The last article was quite long. Thus, I decided to purposefully not include images in the tutorial. Images in Pygame are often called “sprites”. Pygame is able to load images onto Surface objects from PNG, JPG, GIF, and BMP image files.
Here is a code for a simple animation, using an image:
The image of the ball was stored in a file named ball.jpg. To load this file’s image, the string ‘ball.png’ is passed to the pygame.image.load()
function. The pygame.image.load()
function call will return a Surface object that has the image drawn on it. This Surface object will be a separate Surface object from the display Surface object, so we must blit (i.e., copy) the image’s Surface object to the display Surface object. Blitting is drawing the contents of one Surface onto another. It is done with the blit()
Surface object method.
If you get an error message like pygame.error:Couldn't open ball.jpg
when calling pygame.image.load()
, then make sure the ‘ball.png’ file is in the same folder as the image_animation.py file before you run the program.
DISPLAYSURF.blit(ballImg, (ballx, bally))
The above line in the image_animation.py file uses the blit()
method to copy ballImg to DISPLAYSURF. There are two parameters for blit()
. The first is the source Surface object, which is what will be copied onto the DISPLAYSURF Surface object. The second parameter is a two-integer tuple for the X and Y values of the top-left corner where the image should be blitted to.
If ballx and bally were set to 100 and 200 and the width of ballImg was 125 and the height was 79, this blit() call would copy this image onto the DISPLAYSURF so that the top left corner of the ballImg was at the XY coordinate (100,200) and the bottom right corner’s XY coordinate was at (225, 279).
Note: blitting is not allowed for Surfaces that are locked (e.g., when working with PixelArray objects).
Frames Per Second and ‘Clock’ objects
The frame rate or refresh rate is the number of pictures that the program draws per second and is measured in FPS or frames per second. (Many monitors have a frame rate of 60 hertz or 60 frames per second).
A low frame rate in video games can make the game look choppy or jumpy. If the program has too much code to run to draw to the screen frequently enough, then the FPS goes down.
A pygame.time.Clock
object can help us make sure our program runs at a certain maximum FPS. This Clock object will ensure that our game programs don’t run too fast by putting in small pauses on each iteration of the game loop. If we didn’t have these pauses, our game programs would run as fast as the computer can run it. This is often too fast for the player (typically for any human). A call to the tick()
method of a Clock object in the game loop sets the FPS to a maximum value that is provided. In our program, we create the Clock object on the following line:
fpsClock = pygame.time.Clock()
The Clock object’s tick()
method should be called at the very end of the game loop, after the call to pygame.display.update()
. The length of the pause is calculated based on how long it has been since the previous call to the tick()
, which would have taken place at the end of a previous iteration of the game loop. The first time the tick()
method is called, it doesn’t pause at all. In the animation program, the tick()
command is the last instruction on the game loop.
fpsClock.tick(FPS)
Try changing the FPS constant to different values to see the effect it has on the speed of the animation. In the end, comment the above line and see how it affects the program.
Text and Fonts
How does the user know how well they are doing in the game? We require a display where we can write the score of the user. We also require to display text for certain other stuff like any names or menu options on the game. We can write several calls to the pygame.draw.line()
to draw out the lines of each letter. This would be a headache to type out all those pygame.draw.line()
calls and figure out all the XY coordinates, and probably wouldn’t look very good.
Pygame provides some much simpler functions for fonts and creating text. Here is a small Hello World program using Pygame’s font functions:
The screen looks something like this when you execute the program:
There are six steps to making text appear on the screen:
- Create a
pygame.font.Font
object (Line 12) - Create a Surface object with the text drawn on it by calling the Font object’s
render()
method (Line 13) - Create a Rect object from the Surface object by calling the Surface object’s
get_rect()
method. This Rect object will have the width and height correctly set for the text that was rendered, but the top and left attributes will be zero (Line 14). - Set the position of the Rect object by changing one of its attributes. On line 15, we set the center of the Rect object to be at 200,150.
- Blit the Surface object with the text onto the Surface object returned by
pygame.display.set_mode()
(Line19). - Call
pygame.display.update()
to make the display Surface appear on the screen (Line 24).
The parameters to the pygame.font.Font()
constructor is a string of the font file to use, and an integer of the size of the font (in points, like how word processors measure font size). On line 12, we pass ‘freesansbold.ttf’ (this is a font that comes with Pygame) and the integer 32 (for a 32-point sized font).
The parameters to the render()
method call are a string of the text to render, a Boolean value to specify if we want anti-aliasing (see below), the color of the text, and the color of the background. If you want a transparent background, then simply leave off the background color parameter in the method call.
Anti-Aliasing
Anti-Aliasing is a graphics technique for making text and shapes look less blocky by adding a little bit of blur to the edges. It takes a little more computation time to draw with anti-aliasing, so although the graphics may look better, your program may run slower (but only just a little).
Here is the difference between an aliased and anti-aliased line:
To make Pygame’s text use anti-aliasing, just pass True for the second parameter of the render()
method. The pygame.draw.aaline()
and pygame.draw.aalines()
functions have the same parameters as pygame.draw.line()
and pygame.draw.lines()
, except they will draw anti-aliased (smooth) lines instead of aliased (blocky) lines.
Playing Sounds
Another quite well-known component of any game is sound. In Pygame, playing sounds is much simpler than displaying images from image files. First, we need to create a pygame.mixer.Sound
object (we’ll call them as Sound objects for short). The pygame.mixer.Sound()
constructor takes one string parameter, which is the filename of the sound file. Pygame can load WAV, MP3, or OGG files.
To play this sound, call the Sound object’s play()
method. If you want to immediately top the Sound object from playing, call the stop()
method. The stop()
method has no arguments. Here is a sample code:
import timesoundObj = pygame.mixer.Sound('beeps.wav')
soundObj.play()
time.sleep(1) # wait and let the sound play for 1 secondsoundObj.stop()
The program execution continues immediately after play()
is called; it does not wait for the sound to finish playing before moving on to the next line of code.
The Sound objects are good for sound effects to play when the player takes damage, slashes a sword, or collects a coin. But your games might also be better if they had background music regardless of what was going on in the game. Pygame can only load one music file to play in the background at a time. To load a background music file, call the pygame.mixer.load()
function and pass it a string argument of the sound file to load. This file can be WAV, MP3, or MIDI format.
To begin playing the loaded sound file as the background music, call the pygame.mixer.music.play(-1, 0.0)
function. The -1 argument makes the background music forever loop when it reaches the end of the sound file. If you set it to an integer 0 or larger, then the music will only loop that number of times. The 0.0 means to start playing the sound file from the beginning. Changing this value will lead the sound file to be played from a different point of time.
To stop playing the background music immediately, call the pygame.mixer.music.stop()
function. This function has no arguments.
Here is a code sample:
# Loading and playing a sound effect:
soundObj = pygame.mixer.Sound('beepingsound.wav')
soundObj.play()# Loading and playing background music:
pygame.mixer.music.load('backgroundmusic.mp3')
pygame.mixer.music.play(-1, 0.0)
# ...some more of your code goes here...
pygame.mixer.music.stop()
Conclusion
This concludes the short tutorial on Pygame. Now you are ready to:
- Write a game loop in Pygame from scratch
- Add images, drawings and other graphical representations to the game window
- Animate objects in the windows
- Add music and sound effects to the game
Only reading the posts and replicating the snippets of code present here won’t help you master Pygame or begin developing games. However, practicing what you learned and trying out different and new stuff on your own will help. A good idea would be to replicate the alien demo game included in the Pygame package:
I will post tutorials for some basic games that one can build using Pygame, meanwhile, you can check out the tutorial for creating a Snake game in Pygame. I hope this helps you and motivates you to try out Pygame and develop games for fun and recreation. Share, clap, and recommend if you liked the article and would want to read more articles like this.