We have written a bunch of code so far, and our program is starting to look a bit confusing. There's a lot going on, and it can be difficult to read. Maybe you understand it well now, but if you leave it for a week or two and then come back, you'll have forgotten part of it. You'll have to read it all of it again to remember where you were. Also, if you decided to team up with someone else to develop this game, they'd have to figure it out from scratch, reading all that code.
At this point, we should take a step back for a moment and see how we can tidy this up. Neater code is easier for us and others to understand and extend. One tool to help us with this is functions.
To understand functions, it's better to go by example. Have a look at this chunk of code from our game:
if keys[pygame.K_LEFT]:
player.x = player.x - 5
if keys[pygame.K_RIGHT]:
player.x = player.x + 5
if keys[pygame.K_DOWN]:
player.y = player.y + 5
if keys[pygame.K_UP]:
player.y = player.y - 5
This code checks if specific keys are pressed, and then moves the player horizontally or vertically. It's a bit difficult to read though. One possible improvement would be to write it like this:
if keys[pygame.K_LEFT]:
move_x(player, -5)
if keys[pygame.K_RIGHT]:
move_x(player, 5)
if keys[pygame.K_DOWN]:
move_y(player, 5)
if keys[pygame.K_UP]:
move_y(player, -5)
Here we have changed the operations so that now they look like something that can be easier to read: move_x
and move_y
. But where do those instructions come from?
Those instructions are "function calls", and they don't exist yet. If you try to run the game now, it will break because it doesn't know what move_x
and move_y
mean. We need to define what they mean. We need to create functions with those names.
Add this to your program, near the top but after the import
lines:
def move_x(sprite, change):
sprite.x = sprite.x + change
This is an example of a function definition. It reads like follows:
def
indicates that this is a function definition.move_x
will be the name of the function. Later we'll use it elsewhere in our code by writing "move_x
something something".sprite
and change
.sprite
and the change
.How do we use this? Well, wherever you had something like this:
something.x = something.x + something_else
You can now instead write this:
move_x(something, something_else)
That is called a "function call". It goes like follows:
move_x
is "called" and given two pieces of information: something
and something_else
move_x
is.something
becomes sprite
, and something_else
becomes change
. These are the two pieces of information that the function expected.sprite.x = sprite.x + change
.Write the function move_y
, and use it in the code, same as we have done with move_x
above. Run the program to check it works.
In the example above, the functions move_x
and move_y
are used twice each. Functions can be used and reused in many locations, as long as they make sense. For example, we can use move_y
in the code that moves the stars. Initially, that code looks like this:
for star in star_list:
if star.y > SCREEN_HEIGHT:
star.y = 0
star.x = random.randint(0, SCREEN_WIDTH)
else:
star.y = star.y + star.speed
Pay attention to the last line. It reads:
star.y = star.y + star.speed
This fits the following template:
something.y = something.y + something_else
And therefore, it can be rewritten using the function move_y
, like this:
move_y(star, star.speed)
So the code to move the stars ends up reading like this:
for star in star_list:
if star.y > SCREEN_HEIGHT:
star.y = 0
star.x = random.randint(0, SCREEN_WIDTH)
else:
move_y(star, star.speed)
That reads a little bit better. Not a whole lot better, but it's definitely an improvement. Step by step we can make the code easier to understand.
So far, we have created functions that we use in the main body of our code. We can also use functions inside other functions, without limit. Let's carry on tidying up the code and see what this means.
This is a piece of code that we could make clearer, it moves all the stars down, bringing them back to the top when they touch the bottom:
for star in star_list:
if star.y > SCREEN_HEIGHT:
star.y = 0
star.x = random.randint(0, SCREEN_WIDTH)
else:
move_y(star, star.speed)
We can can move it all into a function, pretty much verbatim:
def move_stars(star_list):
for star in star_list:
if star.y > SCREEN_HEIGHT:
star.y = 0
star.x = random.randint(0, SCREEN_WIDTH)
else:
move_y(star, star.speed)
And then we simply call it like this:
move_stars(star_list)
Make that change to the code. Do you know where the above goes?
Note that now we have a function move_stars
that calls another function move_y
. We are using a function within a function, and that's fine.
In fact, many other things we are using in our code are functions too: random.randint
, pygame.display.set_mode
, screen.blit
, etc. They are just functions that Python and Pygame provide by default.
draw_scene
. It will "flip" the screen to show all the changes in each new frame (see pygame.display.flip()
at the end of your code). Use this function in your code.blank_screen
. It will receive the screen and will paint it completely black. Use this function in your code.draw_stars
. It will receive the screen and the list of stars, and will draw the stars on the screen. Use this function in your code.draw_player
. It will receive the screen and the player sprite, and will draw the player on the screen. Use this function in your code.If you completed the challenges, you should have a section of code that looks very similar to this:
move_stars(star_list)
blank_screen(screen)
draw_stars(screen, star_list)
draw_sprite(screen, player)
draw_scene()
This is much easier to read and follow than we had before, and makes the while
loop shorter. It may feel like we have swept the code under the carpet, moving it to functions. This is partly true, but now each of the functions is also easier to read and has a clear title (the name of the function) that describes what it's supposed to do.
Going forward, we'll want to use functions as often as possible. They make programming much easier, even if first we have think a bit more about how to best take advante of them.