Using Pygame to move your game character around

In the fourth part of this series, learn how to code the controls needed to move a game character.
370 readers like this.
Python game screenshot

OpenGameArt.org

In the first article in this series, I explained how to use Python to create a simple, text-based dice game. In the second part, you began building a game from scratch, starting with creating the game's environment. And in the third installment, you created a player sprite and made it spawn in your (rather empty) game world. As you've probably noticed, a game isn't much fun when you can't move your character around. In this article, you'll use Pygame to add keyboard controls so you can direct your character's movement.

There are functions in Pygame to add other kinds of controls (such as a mouse or game controller), but since you certainly have a keyboard if you're typing out Python code, that's what this article covers. Once you understand keyboard controls, you can explore other options on your own.

You created a key to quit your game in the second article in this series, and the principle is the same for movement. However, getting your character to move is a little more complex.

Start with the easy part: setting up the controller keys.

Setting up keys for controlling your player sprite

Open your Python game script in IDLE, PyCharm, or a text editor.

Because the game must constantly "listen" for keyboard events, you'll be writing code that needs to run continuously. Can you figure out where to put code that needs to run constantly for the duration of the game?

If you answered "in the main loop," you're correct! Remember that unless code is in a loop, it runs (at most) only once—and it may not run at all if it's hidden away in a class or function that never gets used.

To make Python monitor for incoming key presses, add this code to the main loop. There's no code to make anything happen yet, so use print statements to signal success. This is a common debugging technique.

while main:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit(); sys.exit()
            main = False

        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT or event.key == ord('a'):
                print('left')
            if event.key == pygame.K_RIGHT or event.key == ord('d'):
                print('right')
            if event.key == pygame.K_UP or event.key == ord('w'):
            print('jump')

        if event.type == pygame.KEYUP:
            if event.key == pygame.K_LEFT or event.key == ord('a'):
                print('left stop')
            if event.key == pygame.K_RIGHT or event.key == ord('d'):
                print('right stop')
            if event.key == ord('q'):
                pygame.quit()
                sys.exit()
                main = False    

Some people prefer to control player characters with the keyboard characters W, A, S, and D, and others prefer to use arrow keys. Be sure to include both options.

Note: It's vital that you consider all of your users when programming. If you write code that works only for you, it's very likely that you'll be the only one who uses your application. More importantly, if you seek out a job writing code for money, you are expected to write code that works for everyone. Giving your users choices, such as the option to use either arrow keys or WASD (it's called accessibility), is a sign of a good programmer.

Launch your game using Python, and watch the console window for output when you press the right, left, and up arrows, or the A, D, and W keys.

$ python ./your-name_game.py
  left
  left stop
  right
  right stop
  jump

This confirms that Pygame detects your key presses correctly. Now it's time to do the hard work of making the sprite move.

Coding the player movement function

To make your sprite move, you must create a property for your sprite that represents movement. When your sprite is not moving, this variable is set to 0.

If you are animating your sprite, or should you decide to animate it in the future, you also must track frames so the walk cycle stays on track.

Create these variables in the Player class. The first two lines are for context (you already have them in your code, if you've been following along), so add only the last three:

    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.movex = 0 # move along X
        self.movey = 0 # move along Y
        self.frame = 0 # count frames

With those variables set, it's time to code the sprite's movement.

The player sprite doesn't need to respond to control all the time because sometimes it isn't being told to move. The code that controls the sprite, therefore, is only one small part of all the things the player sprite can do. When you want to make an object in Python do something independent of the rest of its code, you place your new code in a function. Python functions start with the keyword def, which stands for define.

Make a function in your Player class to add some number of pixels to your sprite's position on screen. Don't worry about how many pixels you add yet; that will be decided in later code.

    def control(self,x,y):
        """
        control player movement
        """
        self.movex += x
        self.movey += y

To move a sprite in Pygame, you must tell Python to redraw the sprite in its new location—and where that new location is.

Since the Player sprite isn't always moving, make these updates a dedicated function within the Player class. Add this function after the control function you created earlier.

To make it appear that the sprite is walking (or flying, or whatever it is your sprite is supposed to do), you need to change its position on screen when the appropriate key is pressed. To get it to move across the screen, you redefine its position, designated by the self.rect.x and self.rect.y properties, to its current position plus whatever amount of movex or movey is applied. (The number of pixels the move requires is set later.)

    def update(self):
        """
        Update sprite position
        """
        self.rect.x = self.rect.x + self.movex        

Do the same thing for the Y position:

        self.rect.y = self.rect.y + self.movey

For animation, advance the animation frames whenever your sprite is moving, and use the corresponding animation frame as the player image:

        # moving left
        if self.movex < 0:
            self.frame += 1
            if self.frame > 3*ani:
                self.frame = 0
            self.image = self.images[self.frame//ani]

        # moving right
        if self.movex > 0:
            self.frame += 1
            if self.frame > 3*ani:
                self.frame = 0
            self.image = self.images[self.frame//ani]

Tell the code how many pixels to add to your sprite's position by setting a variable, then use that variable when triggering the functions of your Player sprite.

First, create the variable in your setup section. In this code, the first two lines are for context, so just add the third line to your script:

player_list = pygame.sprite.Group()
player_list.add(player)
steps = 10  # how many pixels to move

Now that you have the appropriate function and variable, use your key presses to trigger the function and send the variable to your sprite.

Do this by replacing the print statements in your main loop with the Player sprite's name (player), the function (.control), and how many steps along the X axis and Y axis you want the player sprite to move with each loop.

        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT or event.key == ord('a'):
                player.control(-steps,0)
            if event.key == pygame.K_RIGHT or event.key == ord('d'):
                player.control(steps,0)
            if event.key == pygame.K_UP or event.key == ord('w'):
                print('jump')

        if event.type == pygame.KEYUP:
            if event.key == pygame.K_LEFT or event.key == ord('a'):
                player.control(steps,0)
            if event.key == pygame.K_RIGHT or event.key == ord('d'):
                player.control(-steps,0)
            if event.key == ord('q'):
                pygame.quit()
                sys.exit()
                main = False

Remember, steps is a variable representing how many pixels your sprite moves when a key is pressed. If you add 10 pixels to the location of your player sprite when you press D or the right arrow, then when you stop pressing that key you must subtract 10 (-steps) to return your sprite's momentum back to 0.

Try your game now. Warning: it won't do what you expect.

Updating the sprite graphic

Why doesn't your sprite move yet? Because the main loop doesn't call the update function.

Add code to your main loop to tell Python to update the position of your player sprite. Add the line with the comment:

    player.update()  # update player position
    player_list.draw(world)
    pygame.display.flip()
    clock.tick(fps)

Launch your game again to witness your player sprite move across the screen at your bidding. There's no vertical movement yet because those functions will be controlled by gravity, but that's another lesson for another article.

Movement works, but there's still one small problem: your hero graphic doesn't turn to face the direction it's walking. In other words, if you designed your hero facing right, then it looks like it's walking backwards when you press the left arrow key. Normally, you'd expect your hero to turn left when walking left, and turn right again to walk to the right.

Flipping your sprite

You can flip a graphic with Pygame's transform function. This, like all the other functions you've been using for this game, is a lot of complex code and maths distilled into a single, easy to use, Python keyword. This is a great example of why a framework helps you code. Instead of having to learn basic principles of drawing pixels on screen, you can let Pygame do all the work and just make a call to a funciton that already exists.

You only need the transform on the instance when your graphic is walking the opposite way it's facing by default. My graphic faces right, so I apply the transform to the left code block. The pygame.transform.flip function takes three arguments, according to Pygame documentation: what to flip, whether to flip horizontally, and whether to flip vertically. In this case, those are the graphic (which you've already defined in the existing code), True for horizontal, and False for a vertical flip.

Update your code:

        # moving left
        if self.movex < 0:
            self.frame += 1
            if self.frame > 3*ani:
                self.frame = 0
            self.image = pygame.transform.flip(self.images[self.frame // ani], True, False)

Notice that the transform function is inserted into your existing code. The variable self.image is still getting defined as an image from your list of hero images, but it's getting "wrapped" in the transform function.

Try your code now, and watch as your hero does an about-face each time you point it in a different direction.

That's enough of a lesson for now. Until the next article, you might try exploring other ways to control your hero. For intance, should you have access to a joystick, try reading Pygame's documentation for its joystick module and see if you can make your sprite move that way. Alternately, see if you can get the mouse to interact with your sprite.

Most importantly, have fun!

All the code used in this tutorial

For your reference, here is all the code used in this series of articles so far.

#!/usr/bin/env python3
# by Seth Kenlon

# GPLv3
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
from typing import Tuple

import pygame
import sys
import os

'''
Variables
'''

worldx = 960
worldy = 720
fps = 40
ani = 4
world = pygame.display.set_mode([worldx, worldy])

BLUE = (25, 25, 200)
BLACK = (23, 23, 23)
WHITE = (254, 254, 254)
ALPHA = (0, 255, 0)

'''
Objects
'''


class Player(pygame.sprite.Sprite):
    """
    Spawn a player
    """

    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.movex = 0
        self.movey = 0
        self.frame = 0
        self.images = []
        for i in range(1, 5):
            img = pygame.image.load(os.path.join('images', 'hero' + str(i) + '.png')).convert()
            img.convert_alpha()  # optimise alpha
            img.set_colorkey(ALPHA)  # set alpha
            self.images.append(img)
            self.image = self.images[0]
            self.rect = self.image.get_rect()

    def control(self, x, y):
        """
        control player movement
        """
        self.movex += x
        self.movey += y

    def update(self):
        """
        Update sprite position
        """

        self.rect.x = self.rect.x + self.movex
        self.rect.y = self.rect.y + self.movey

        # moving left
        if self.movex < 0:
            self.frame += 1
            if self.frame > 3*ani:
                self.frame = 0
            self.image = pygame.transform.flip(self.images[self.frame // ani], True, False)

        # moving right
        if self.movex > 0:
            self.frame += 1
            if self.frame > 3*ani:
                self.frame = 0
            self.image = self.images[self.frame//ani]


'''
Setup
'''

backdrop = pygame.image.load(os.path.join('images', 'stage.png'))
clock = pygame.time.Clock()
pygame.init()
backdropbox = world.get_rect()
main = True

player = Player()  # spawn player
player.rect.x = 0  # go to x
player.rect.y = 0  # go to y
player_list = pygame.sprite.Group()
player_list.add(player)
steps = 10

'''
Main Loop
'''

while main:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            try:
                sys.exit()
            finally:
                main = False

        if event.type == pygame.KEYDOWN:
            if event.key == ord('q'):
                pygame.quit()
                try:
                    sys.exit()
                finally:
                    main = False
            if event.key == pygame.K_LEFT or event.key == ord('a'):
                player.control(-steps, 0)
            if event.key == pygame.K_RIGHT or event.key == ord('d'):
                player.control(steps, 0)
            if event.key == pygame.K_UP or event.key == ord('w'):
                print('jump')

        if event.type == pygame.KEYUP:
            if event.key == pygame.K_LEFT or event.key == ord('a'):
                player.control(steps, 0)
            if event.key == pygame.K_RIGHT or event.key == ord('d'):
                player.control(-steps, 0)

    world.blit(backdrop, backdropbox)
    player.update()
    player_list.draw(world)
    pygame.display.flip()
    clock.tick(fps)

You've come far and learned much, but there's a lot more to do. In the next few articles, you'll add enemy sprites, emulated gravity, and lots more. In the mean time, practice with Python!

Tags
Seth Kenlon
Seth Kenlon is a UNIX geek, free culture advocate, independent multimedia artist, and D&D nerd. He has worked in the film and computing industry, often at the same time.
User profile image.
Jess Weichler is a digital artist using open source software and hardware to create works digitally and in the physical world at CyanideCupcake.com.

5 Comments

I'm not sure if I'm doing something wrong, but the following line doesn't work:

self.image = self.images[self.frame//ani+4]

Unless I change it to:

self.image = self.images[self.frame//ani]

Thanks for the bug report, Jay. Are you using an animated sprite or a static image? If you're not using an animated sprite (one with a walk cycle), then cycling through the images is not necessary. If you ARE using an animated image, then yes, this needs to get fixed.

If you want, you can try pasting in your code here: https://paste.fedoraproject.org/

Get back to me with the link, and i'll take a look at it.

In reply to by jlacroix

Please, don't write

while main == True:

It's redundant. main is a boolean var, so

while main:

is enough.

My eyes bleed when I read it.

It's great to get insight into the specifics of the language. Thank you.

My argument for how I structure these lessons, however, is that things like "while Main:" are "more correct" than "while Main is True:" or "== True" or whatever, it's more difficult to teach up front. Teaching a programming language means hitting readers and students with *a lot* of information, and the more often that information is consistent, the better.

In my experience (gained from about 5 years of teaching coding to students ranging from 11 to 64), it's confusing to teach someone how to compare values, and then to tell them that oh, by the way, making no comparison is the same as a Boolean comparison.

It's not a concept that's beyond any student, but early on in the training, in my very humble opinion, is not the time to adhere to super-optimisation or style standards.

There's a valid counter argument that teaching the most adherent method from the start is best. However, in my real-world experience, it detracts from the lesson and in fact confuses what you're trying to teach.

I have lots of controversial thoughts on education. I should write a series of articles, some day.

With all of that said, sorry for making your eyes bleed. :-) I do feel your pain, honest.

In reply to by David Aponte (not verified)

Hi, I just wanna say thanks!
I just learned a few new tricks for my circus. ^^

Creative Commons LicenseThis work is licensed under a Creative Commons Attribution-Share Alike 4.0 International License.