Help! How Do I Move An Image?¶
- Author
Pete Shinners
- Contact
Many people new to programming and graphics have a hard time figuring out how to make an image move around the screen. Without understanding all the concepts, it can be very confusing. You're not the first person to be stuck here, I'll do my best to take things step by step. We'll even try to end with methods of keeping your animations efficient.
Note that we won't be teaching you to program with python in this article, just introduce you to some of the basics with pygame.
Just Pixels On The Screen¶
Pygame has a display Surface. This is basically an image that is visible on the screen, and the image is made up of pixels. The main way you change these pixels is by calling the blit() function. This copies the pixels from one image onto another.
This is the first thing to understand. When you blit an image onto the screen, you are simply changing the color of the pixels on the screen. Pixels aren't added or moved, we just change the colors of the pixels already on the screen. These images you blit to the screen are also Surfaces in pygame, but they are in no way connected to the display Surface. When they are blitted to the screen they are copied into the display, but you still have a unique copy of the original.
With this brief description. Perhaps you can already understand what is needed to "move" an image. We don't actually move anything at all. We simply blit the image in a new position. But before we draw the image in the new position, we'll need to "erase" the old one. Otherwise the image will be visible in two places on the screen. By rapidly erasing the image and redrawing it in a new place, we achieve the "illusion" of movement.
Through the rest of this tutorial we will break this process down into simpler steps. Even explaining the best ways to have multiple images moving around the screen. You probably already have questions. Like, how do we "erase" the image before drawing it in a new position? Perhaps you're still totally lost? Well hopefully the rest of this tutorial can straighten things out for you.
Let's Go Back A Step¶
Perhaps the concept of pixels and images is still a little foreign to you? Well good news, for the next few sections we are going to use code that does everything we want, it just doesn't use pixels. We're going to create a small python list of 6 numbers, and imagine it represents some fantastic graphics we could see on the screen. It might actually be surprising how closely this represents exactly what we'll later be doing with real graphics.
So let's begin by creating our screen list and fill it with a beautiful landscape of 1s and 2s.
>>> screen = [1, 1, 2, 2, 2, 1]
>>> print(screen)
[1, 1, 2, 2, 2, 1]
Now we've created our background. It's not going to be very exciting unless we also draw a player on the screen. We'll create a mighty hero that looks like the number 8. Let's stick him near the middle of the map and see what it looks like.
>>> screen[3] = 8
>>> print(screen)
[1, 1, 2, 8, 2, 1]
This might have been as far as you've gotten if you jumped right in doing some graphics programming with pygame. You've got some nice looking stuff on the screen, but it cannot move anywhere. Perhaps now that our screen is just a list of numbers, it's easier to see how to move him?
Making The Hero Move¶
Before we can start moving the character. We need to keep track of some sort of position for him. In the last section when we drew him, we just picked an arbitrary position. Let's do it a little more officially this time.
>>> playerpos = 3
>>> screen[playerpos] = 8
>>> print(screen)
[1, 1, 2, 8, 2, 1]
Now it is pretty easy to move him to a new position. We simply change the value of playerpos, and draw him on the screen again.
>>> playerpos = playerpos - 1
>>> screen[playerpos] = 8
>>> print(screen)
[1, 1, 8, 8, 2, 1]
Whoops. Now we can see two heroes. One in the old position, and one in his new position. This is exactly the reason we need to "erase" the hero in his old position before we draw him in the new position. To erase him, we need to change that value in the list back to what it was before the hero was there. That means we need to keep track of the values on the screen before the hero replaced them. There's several way you could do this, but the easiest is usually to keep a separate copy of the screen background. This means we need to make some changes to our little game.
Creating A Map¶
What we want to do is create a separate list we will call our background. We will create the background so it looks like our original screen did, with 1s and 2s. Then we will copy each item from the background to the screen. After that we can finally draw our hero back onto the screen.
>>> background = [1, 1, 2, 2, 2, 1]
>>> screen = [0]*6 #a new blank screen
>>> for i in range(6):
... screen[i] = background[i]
>>> print(screen)
[1, 1, 2, 2, 2, 1]
>>> playerpos = 3
>>> screen[playerpos] = 8
>>> print(screen)
[1, 1, 2, 8, 2, 1]
It may seem like a lot of extra work. We're no farther off than we were before the last time we tried to make him move. But this time we have the extra information we need to move him properly.
Making The Hero Move (Take 2)¶
This time it will be easy to move the hero around. First we will erase the hero from his old position. We do this by copying the correct value from the background onto the screen. Then we will draw the character in his new position on the screen
>>> print(screen)
[1, 1, 2, 8, 2, 1]
>>> screen[playerpos] = background[playerpos]
>>> playerpos = playerpos - 1
>>> screen[playerpos] = 8
>>> print(screen)
[1, 1, 8, 2, 2, 1]
There it is. The hero has moved one space to the left. We can use this same code to move him to the left again.
>>> screen[playerpos] = background[playerpos]
>>> playerpos = playerpos - 1
>>> screen[playerpos] = 8
>>> print(screen)
[1, 8, 2, 2, 2, 1]
Excellent! This isn't exactly what you'd call smooth animation. But with a couple small changes, we'll make this work directly with graphics on the screen.
Definition: "blit"¶
In the next sections we will transform our program from using lists to using real graphics on the screen. When displaying the graphics we will use the term blit frequently. If you are new to doing graphics work, you are probably unfamiliar with this common term.
BLIT: Basically, blit means to copy graphics from one image to another. A more formal definition is to copy an array of data to a bitmapped array destination. You can think of blit as just "assigning" pixels. Much like setting values in our screen-list above, blitting assigns the color of pixels in our image.
Other graphics libraries will use the word bitblt, or just blt, but they are talking about the same thing. It is basically copying memory from one place to another. Actually, it is a bit more advanced than straight copying of memory, since it needs to handle things like pixel formats, clipping, and scanline pitches. Advanced blitters can also handle things like transparency and other special effects.
Going From The List To The Screen¶
To take the code we see in the above to examples and make them work with pygame is very straightforward. We'll pretend we have loaded some pretty graphics and named them "terrain1", "terrain2", and "hero". Where before we assigned numbers to a list, we now blit graphics to the screen. Another big change, instead of using positions as a single index (0 through 5), we now need a two dimensional coordinate. We'll pretend each of the graphics in our game is 10 pixels wide.
>>> background = [terrain1, terrain1, terrain2, terrain2, terrain2, terrain1]
>>> screen = create_graphics_screen()
>>> for i in range(6):
... screen.blit(background[i], (i*10, 0))
>>> playerpos = 3
>>> screen.blit(playerimage, (playerpos*10, 0))
Hmm, that code should seem very familiar, and hopefully more importantly;
the code above should make a little sense. Hopefully my illustration of setting
simple values in a list shows the similarity of setting pixels on the screen
(with blit). The only part that's really extra work is converting the player position
into coordinates on the screen. For now we just use a crude (playerpos*10, 0)
,
but we can certainly do better than that. Now let's move the player
image over a space. This code should have no surprises.
>>> screen.blit(background[playerpos], (playerpos*10, 0))
>>> playerpos = playerpos - 1
>>> screen.blit(playerimage, (playerpos*10, 0))
There you have it. With this code we've shown how to display a simple background with a hero's image on it. Then we've properly moved that hero one space to the left. So where do we go from here? Well for one the code is still a little awkward. First thing we'll want to do is find a cleaner way to represent the background and player position. Then perhaps a bit of smoother, real animation.
Screen Coordinates¶
To position an object on the screen, we need to tell the blit() function where to put the image. In pygame we always pass positions as an (X,Y) coordinate. This represents the number of pixels to the right, and the number of pixels down to place the image. The top-left corner of a Surface is coordinate (0, 0). Moving to the right a little would be (10, 0), and then moving down just as much would be (10, 10). When blitting, the position argument represents where the topleft corner of the source should be placed on the destination.
Pygame comes with a convenient container for these coordinates, it is a Rect. The Rect basically represents a rectangular area in these coordinates. It has topleft corner and a size. The Rect comes with a lot of convenient methods which help you move and position them. In our next examples we will represent the positions of our objects with the Rects.
Also know that many functions in pygame expect Rect arguments. All of these functions can also accept a simple tuple of 4 elements (left, top, width, height). You aren't always required to use these Rect objects, but you will mainly want to. Also, the blit() function can accept a Rect as its position argument, it simply uses the topleft corner of the Rect as the real position.
Changing The Background¶
In all our previous sections, we've been storing the background as a list of different types of ground. That is a good way to create a tile-based game, but we want smooth scrolling. To make that a little easier, we're going to change the background into a single image that covers the whole screen. This way, when we want to "erase" our objects (before redrawing them) we only need to blit the section of the erased background onto the screen.
By passing an optional third Rect argument to blit, we tell blit to only use that subsection of the source image. You'll see that in use below as we erase the player image.
Also note, now when we finish drawing to the screen, we call pygame.display.update() which will show everything we've drawn onto the screen.
Smooth Movement¶
To make something appear to move smoothly, we only want to move it a couple pixels at a time. Here is the code to make an object move smoothly across the screen. Based on what we already now know, this should look pretty simple.
>>> screen = create_screen()
>>> clock = pygame.time.Clock() #get a pygame clock object
>>> player = load_player_image()
>>> background = load_background_image()
>>> screen.blit(background, (0, 0)) #draw the background
>>> position = player.get_rect()
>>> screen.blit(player, position) #draw the player
>>> pygame.display.update() #and show it all
>>> for x in range(100): #animate 100 frames
... screen.blit(background, position, position) #erase
... position = position.move(2, 0) #move player
... screen.blit(player, position) #draw new player
... pygame.display.update() #and show it all
... clock.tick(60) #update 60 times per second
There you have it. This is all the code that is needed to smoothly animate an object across the screen. We can even use a pretty background character. Another benefit of doing the background this way, the image for the player can have transparency or cutout sections and it will still draw correctly over the background (a free bonus).
We also throw in a call to pygame.time.Clock() to grab the clock element. With it, we can call clock.tick() to set the framerate in frames per second. This slows down our program a little, otherwise it might run so fast you might not see it.
So, What Next?¶
Well there we have it. Hopefully this article has done everything it promised to do. But, at this point the code really isn't ready for the next best-selling game. How do we easily have multiple moving objects? What exactly are those mysterious functions like load_player_image()? We also need a way to get simple user input, and loop for more than 100 frames. We'll take the example we have here, and turn it into an object oriented creation that would make momma proud.
First, The Mystery Functions¶
Full information on these types of functions can be found in other tutorials and reference. The pygame.image module has a load() function which will do what we want. The lines to load the images should become this.
>>> player = pygame.image.load('player.bmp').convert()
>>> background = pygame.image.load('liquid.bmp').convert()
We can see that's pretty simple, the load function just takes a filename and returns a new Surface with the loaded image. After loading we make a call to the Surface method, convert(). Convert returns us a new Surface of the image, but now converted to the same pixel format as our display. Since the images will be the same format at the screen, they will blit very quickly. If we did not convert, the blit() function is slower, since it has to convert from one type of pixel to another as it goes.
You may also have noticed that both the load() and convert() return new Surfaces. This means we're really creating two Surfaces on each of these lines. In other programming languages, this results in a memory leak (not a good thing). Fortunately Python is smart enough to handle this, and pygame will properly clean up the Surface we end up not using.
The other mystery function we saw in the above example was create_screen(). In pygame it is simple to create a new window for graphics. The code to create a 640x480 surface is below. By passing no other arguments, pygame will just pick the best color depth and pixel format for us.
>>> screen = pygame.display.set_mode((640, 480))
Handling Some Input¶
We desperately need to change the main loop to look for any user input, (like when the user closes the window). We need to add "event handling" to our program. All graphical programs use this Event Based design. The program gets events like "keyboard pressed" or "mouse moved" from the computer. Then the program responds to the different events. Here's what the code should look like. Instead of looping for 100 frames, we'll keep looping until the user asks us to stop.
>>> while True:
... for event in pygame.event.get():
... if event.type == pygame.QUIT:
... sys.exit()
... move_and_draw_all_game_objects()
What this code simply does is, first loop forever, then check if there are any events from the user. We exit the program if the user presses the close button on the window. After we've checked all the events we move and draw our game objects. (We'll also erase them before they move, too)
Moving Multiple Images¶
Here's the part where we're really going to change things around. Let's say we want 10 different images moving around on the screen. A good way to handle this is to use python's classes. We'll create a class that represents our game object. This object will have a function to move itself, and then we can create as many as we like. The functions to draw and move the object need to work in a way where they only move one frame (or one step) at a time. Here's the python code to create our class.
>>> class GameObject:
... def __init__(self, image, height, speed):
... self.speed = speed
... self.image = image
... self.pos = image.get_rect().move(0, height)
... def move(self):
... self.pos = self.pos.move(self.speed, 0)
... if self.pos.right > 600:
... self.pos.left = 0
So we have two functions in our class. The init function constructs our object. It positions the object and sets its speed. The move method moves the object one step. If it's gone too far, it moves the object back to the left.
Putting It All Together¶
Now with our new object class, we can put together the entire game. Here is what the main function for our program will look like.
>>> screen = pygame.display.set_mode((640, 480))
>>> clock = pygame.time.Clock() #get a pygame clock object
>>> player = pygame.image.load('player.bmp').convert()
>>> background = pygame.image.load('background.bmp').convert()
>>> screen.blit(background, (0, 0))
>>> objects = []
>>> for x in range(10): #create 10 objects</i>
... o = GameObject(player, x*40, x)
... objects.append(o)
>>> while True:
... for event in pygame.event.get():
... if event.type == pygame.QUIT:
... sys.exit()
... for o in objects:
... screen.blit(background, o.pos, o.pos)
... for o in objects:
... o.move()
... screen.blit(o.image, o.pos)
... pygame.display.update()
... clock.tick(60)
And there it is. This is the code we need to animate 10 objects on the screen. The only point that might need explaining is the two loops we use to clear all the objects and draw all the objects. In order to do things properly, we need to erase all the objects before drawing any of them. In our sample here it may not matter, but when objects are overlapping, using two loops like this becomes important.
Preparing for Improved User Input¶
With all keyboard input terminating the program, that's not very interactive. Let's add some extra user input!
First we should create a unique character that the player will control. We can do that in much the same way we created the other movable entities. Let's call the player object p. We can already move any object, but, a player should have more input than simply moving right. To accommodate this, let's revamp our move function under our GameObject class.
>>> def move(self, up=False, down=False, left=False, right=False):
... if right:
... self.pos.right += self.speed
... if left:
... self.pos.right -= self.speed
... if down:
... self.pos.top += self.speed
... if up:
... self.pos.top -= self.speed
... if self.pos.right > WIDTH:
... self.pos.left = 0
... if self.pos.top > HEIGHT-SPRITE_HEIGHT:
... self.pos.top = 0
... if self.pos.right < SPRITE_WIDTH:
... self.pos.right = WIDTH
... if self.pos.top < 0:
... self.pos.top = HEIGHT-SPRITE_HEIGHT
There's certainly a lot more going on here, so let's take it one step at a time. First, we've added some default values into the move function, declared as up, down, left, and right. These booleans will allow us to specifically select a direction that the object is moving in. The first part, where we go through and check True for each variable, is where we will add to the position of the object, much like before. Right controls horizontal, and top controls vertical positions.
Additionally, we've removed the magic number present previously, and replaced it with the constants WIDTH, HEIGHT, SPRITE_WIDTH, and SPRITE_HEIGHT. These values represent the screen width and height, along with the width and height of the object displayed on the screen.
The second part, where the position is being checked, ensures that the position is within the confines of our screen. With this in place, we need to make sure that when one of our other objects calls move, we set right to true.
Adding the User Input¶
We've already seen that pygame has event handling, and we know that KEYDOWN is an event in this loop. We could, under KEYDOWN, assert the key press matches an arrow key, where we would then call move. However, this movement will only occur once every time a key is pressed, and it therefore will be extremely choppy and unpleasant.
For this, we can use pygame.key.get_pressed(), which returns a list of all keys, and whether or not they are currently pressed. Since we want these key presses to be maintained whether an event is currently happening or not, we should put it outside of the main event handling loop, but still within our game loop. Our functionality will look like this.
>>> keys = pygame.key.get_pressed()
>>> if keys[pygame.K_UP]:
... p.move(up=True)
>>> if keys[pygame.K_DOWN]:
... p.move(down=True)
>>> if keys[pygame.K_LEFT]:
... p.move(left=True)
>>> if keys[pygame.K_RIGHT]:
... p.move(right=True)
We simply get our list of keys pressed, called keys. We can then check the index at the key code position to see if it is held down. For more key codes, I recommend checking out the documentation on pygame.key.
When up is held, we move our object, p, up. When down is held, we move down. Rinse and repeat for all cases, and we're good to go!
Putting it all Together One More time¶
Now that we're finished with the player functionality, let's take one last look to make sure we understand everything.
>>> screen = pygame.display.set_mode((640, 480))
>>> clock = pygame.time.Clock() #get a pygame clock object
>>> player = pygame.image.load('player.bmp').convert()
>>> entity = pygame.image.load('alien1.bmp').convert()
>>> background = pygame.image.load('background.bmp').convert()
>>> screen.blit(background, (0, 0))
>>> objects = []
>>> p = GameObject(player, 10, 3) #create the player object
>>> for x in range(10): #create 10 objects</i>
... o = GameObject(entity, x*40, x)
... objects.append(o)
>>> while True:
... screen.blit(background, p.pos, p.pos)
... for o in objects:
... screen.blit(background, o.pos, o.pos)
... keys = pygame.key.get_pressed()
... if keys[pygame.K_UP]:
... p.move(up=True)
... if keys[pygame.K_DOWN]:
... p.move(down=True)
... if keys[pygame.K_LEFT]:
... p.move(left=True)
... if keys[pygame.K_RIGHT]:
... p.move(right=True)
... for event in pygame.event.get():
... if event.type == pygame.QUIT:
... sys.exit()
... screen.blit(p.image, p.pos)
... for o in objects:
... o.move()
... screen.blit(o.image, o.pos)
... pygame.display.update()
... clock.tick(60)
A few things not mentioned earlier: we load in a second image and call it entity, and we use that for all objects that aren't the player, which uses the player image defined earlier.
And that's all there is to it! Now we have a fully functional player object that is controlled using the arrow keys!
You Are On Your Own From Here¶
So what would be next on your road to learning? Well first playing around
with this example a bit. The full running version of this example is available
in the pygame examples directory. It is the example named
moveit.py
.
Take a look at the code and play with it, run it, learn it.
Things you may want to work on is maybe having more than one type of object. Finding a way to cleanly "delete" objects when you don't want to show them any more. Also updating the display.update() call to pass a list of the areas on-screen that have changed.
There are also other tutorials and examples in pygame that cover these issues. So when you're ready to keep learning, keep on reading. :-)
Lastly, you can feel free to come to the pygame mailing list or chatroom with any questions on this stuff. There's always folks on hand who can help you out with this sort of business.
Lastly, have fun, that's what games are for!
Edit on GitHub