Source

pygame / docs / tut / MoveIt.html

Full commit
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
<html><head> <!--
TUTORIAL:Help! How Do I Move An Image?
-->
  <title>Pygame Tutorials - Help! How Do I Move An Image?</title>
</head>
  <body>
             
<h1 align="center"><font size="-1">Pygame Tutorials</font><br>
     Help! How Do I Move An Image?</h1>
           
<h2 align="center">by Pete Shinners<br>
     <font size="-1">pete@shinners.org</font></h2>
           
<h3 align="center">Revision 1.2, August 20, 2002</h3>
      <br>
     <br>
     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.<br>
<br>
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.<br>
             
<h2>Just Pixels On The Screen</h2>
     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.<br>
    <br>
    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 aready
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.<br>
    <br>
    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.<br>
    <br>
    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.<br>
    <br>
       
<h2>Let's Go Back A Step</h2>
    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.<br>
    <br>
    So let's begin by creating our screen list and fill it with a beautiful 
 landscape of 1s and 2s.<br>
       
<blockquote><pre>
<b>&gt;&gt;&gt;</b> screen = [1, 1, 2, 2, 2, 1]
<b>&gt;&gt;&gt;</b> print screen
<b>[1, 1, 2, 2, 2, 1]</b>
</pre></blockquote>
    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.<br>
       
<blockquote><pre>
<b>&gt;&gt;&gt;</b> screen[3] = 8
<b>&gt;&gt;&gt;</b> print screen
<b>[1, 1, 2, 8, 2, 1]</b>
</pre></blockquote>
    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?<br>
    <br>
       
<h2>Making The Hero Move</h2>
    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.<br>
       
<blockquote><pre>
<b>&gt;&gt;&gt;</b> playerpos = 3
<b>&gt;&gt;&gt;</b> screen[playerpos] = 8
<b>&gt;&gt;&gt;</b> print screen
<b>[1, 1, 2, 8, 2, 1]</b>
</pre></blockquote>
    Now it is pretty easy to move him to a new position. We simple change 
the  value of playerpos, and draw him on the screen again.<br>
       
<blockquote><pre>
<b>&gt;&gt;&gt;</b> playerpos = playerpos - 1
<b>&gt;&gt;&gt;</b> screen[playerpos] = 8
<b>&gt;&gt;&gt;</b> print screen
<b>[1, 1, 8, 8, 2, 1]</b>
</pre></blockquote>
     Whoops. Now we can see two heros. 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.<br>
    <br>
       
<h2>Creating A Map</h2>
    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.<br>
       
<blockquote><pre>
<b>&gt;&gt;&gt;</b> background = [1, 1, 2, 2, 2, 1]
<b>&gt;&gt;&gt;</b> screen = [0]*6                     <i>#a new blank screen</i>
<b>&gt;&gt;&gt;</b> for i in range(6):
<b>...</b>    screen[i] = background[i]
<b>&gt;&gt;&gt;</b> print screen
<b>[1, 1, 2, 2, 2, 1]</b>
<b>&gt;&gt;&gt;</b> playerpos = 3
<b>&gt;&gt;&gt;</b> screen[playerpos] = 8
<b>&gt;&gt;&gt;</b> print screen
<b>[1, 1, 2, 8, 2, 1]</b>
</pre></blockquote>
   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.<br>
    <br>
       
<h2>Making The Hero Move (Take 2)</h2>
    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<br>
       
<blockquote><pre>
<b>&gt;&gt;&gt;</b> print screen
<b>[1, 1, 2, 8, 2, 1]</b>
<b>&gt;&gt;&gt;</b> screen[playerpos] = background[playerpos]
<b>&gt;&gt;&gt;</b> playerpos = playerpos - 1
<b>&gt;&gt;&gt;</b> screen[playerpos] = 8
<b>&gt;&gt;&gt;</b> print screen
<b>[1, 1, 8, 2, 2, 1]</b>
</pre></blockquote>
    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.<br>
       
<blockquote><pre>
<b>&gt;&gt;&gt;</b> screen[playerpos] = background[playerpos]
<b>&gt;&gt;&gt;</b> playerpos = playerpos - 1
<b>&gt;&gt;&gt;</b> screen[playerpos] = 8
<b>&gt;&gt;&gt;</b> print screen
<b>[1, 8, 2, 2, 2, 1]</b>
</pre></blockquote>
    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.<br>
    <br>


<h2>Definition: "blit"</h2>
   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 <u>blit</u> frequently. If you are new to doing graphics
 work, you are probably unframiliar with this common term.<br>
 <br>
<b>BLIT:</b> 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
<i>"assigning"</i> pixels. Much like setting values in our screen-list
above, blitting assigns the color of pixels in our image.<br>
 <br>
Other graphics libraries will use the word <i>bitblt</i>, or just <i>blt</i>,
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 transparancy and other special effects.<br>
 <br>

       
<h2>Going From The List To The Screen</h2>
   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.<br>
       
<blockquote><pre>
<b>&gt;&gt;&gt;</b> background = [terrain1, terrain1, terrain2, terrain2, terrain2, terrain1]
<b>&gt;&gt;&gt;</b> screen = create_graphics_screen()
<b>&gt;&gt;&gt;</b> for i in range(6):
<b>...</b>    screen.blit(background[i], (i*10, 0))
<b>&gt;&gt;&gt;</b> playerpos = 3
<b>&gt;&gt;&gt;</b> screen.blit(playerimage, (playerpos*10, 0))
</pre></blockquote>
   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.<br>
   
<blockquote><pre>
<b>&gt;&gt;&gt;</b> screen.blit(background[playerpos], (playerpos*10, 0))
<b>&gt;&gt;&gt;</b> playerpos = playerpos - 1
<b>&gt;&gt;&gt;</b> screen.blit(playerimage, (playerpos*10, 0))
</pre></blockquote>
  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.<br>
  <br>
 
<h2>Screen Coordinates</h2>
 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.<br>
 <br>
 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.<br>
 <br>
 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 it's position 
argument, it simply uses the topleft corner of the Rect as the real position.<br>
 <br>
 <br>
 
<h2>Changing The Background</h2>
 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.<br>
 <br>
 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.<br>
 <br>
 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.<br>
 <br>
 
<h2>Smooth Movement</h2>
 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.<br>
 <br>
 
<blockquote><pre>
<b>&gt;&gt;&gt;</b> screen = create_screen()
<b>&gt;&gt;&gt;</b> player = load_player_image()
<b>&gt;&gt;&gt;</b> background = load_background_image()
<b>&gt;&gt;&gt;</b> screen.blit(background, (0, 0))       <i>#draw the background</i>
<b>&gt;&gt;&gt;</b> position = player.get_rect()
<b>&gt;&gt;&gt;</b> screen.blit(player, position)         <i>#draw the player</i>
<b>&gt;&gt;&gt;</b> pygame.display.update()               <i>#and show it all</i>
<b>&gt;&gt;&gt;</b> for x in range(100):                  <i>#animate 100 frames</i>
<b>...</b>    screen.blit(background, position, position) <i>#erase</i>
<b>...</b>    position = position.move(2, 0)     <i>#move player</i>
<b>...</b>    screen.blit(player, position)      <i>#draw new player</i>
<b>...</b>    pygame.display.update()            <i>#and show it all</i>
<b>...</b>    pygame.time.delay(100)             <i>#stop the program for 1/10 second</i>
</pre></blockquote>
 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 transparancy or cutout sections and it will still draw correctly 
over the background (a free bonus).<br>
 <br>
 We also throw in a call to pygame.time.delay() at the end of our loop above. 
This slows down our program a little, otherwise it might run so fast you might
not see it.<br>
 <br>
 
<h2>So, What Next?</h2>
 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 bestselling 
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.<br>
 <br>
 
<h2>First, The Mystery Functions</h2>
 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.<br>
 
<blockquote><pre>
<b>&gt;&gt;&gt;</b> player = pygame.image.load('player.bmp').convert()
<b>&gt;&gt;&gt;</b> background = pygame.image.load('liquid.bmp').convert()
</pre></blockquote>
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.<br>
 <br>
 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.<br>
 <br>
 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 colordepth and pixel format for us.<br>
 <br>
 
<blockquote><pre>
<b>&gt;&gt;&gt;</b> screen = pygame.display.set_mode((640, 480))
</pre></blockquote>
<br>
<h2>Handling Some Input</h2>
We desparately 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.<br>
<blockquote><pre>
<b>&gt;&gt;&gt;</b> while 1:
<b>...</b>    for event in pygame.event.get():
<b>...</b>        if event.type in (QUIT, KEYDOWN):
<b>...</b>            sys.exit()
<b>...</b>    move_and_draw_all_game_objects()
</pre></blockquote>
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 keyboard
or 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)<br>
 
<h2>Moving Multiple Images</h2>
 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.<br>
<blockquote><pre>
<b>&gt;&gt;&gt;</b> class GameObject:
<b>...</b>    def __init__(self, image, height, speed):
<b>...</b>        self.speed = speed
<b>...</b>        self.image = image
<b>...</b>        self.pos = image.get_rect().move(0, height)
<b>...</b>    def move(self):
<b>...</b>        self.pos = self.pos.move(0, self.speed)
<b>...</b>        if self.pos.right &gt; 600:
<b>...</b>            self.pos.left = 0
</pre></blockquote>
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.<br>
<br>
<h2>Putting It All Together</h2>
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.<br>
<blockquote><pre>
<b>&gt;&gt;&gt;</b> screen = pygame.display.set_mode((640, 480))
<b>&gt;&gt;&gt;</b> player = pygame.image.load('player.bmp').convert()
<b>&gt;&gt;&gt;</b> background = pygame.image.load('background.bmp').convert()
<b>&gt;&gt;&gt;</b> screen.blit(background, (0, 0))
<b>&gt;&gt;&gt;</b> objects = []
<b>&gt;&gt;&gt;</b> for x in range(10): 		<i>#create 10 objects</i>
<b>...</b>    o = GameObject(player, x*40, x)
<b>...</b>    objects.append(o)
<b>&gt;&gt;&gt;</b> while 1:
<b>...</b>    for event in pygame.event.get():
<b>...</b>        if event.type in (QUIT, KEYDOWN):
<b>...</b>            sys.exit()
<b>...</b>    for o in objects:
<b>...</b>        screen.blit(background, o.pos, o.pos)
<b>...</b>    for o in objects:
<b>...</b>        o.move()
<b>...</b>        screen.draw(o.image, o.pos)
<b>...</b>    pygame.display.update()
<b>...</b>    pygame.time.delay(100)
</pre></blockquote>
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.<br>
<br>
<h2>You Are On Your Own From Here</h2>
<p>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.</p>
<p>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
anymore. Also updating the display.update() call to pass a list of the areas
onscreen that have changed.</p>
<p>There are also other tutorials and examples in pygame that cover these
issues. So when you're ready to keep learning, keep on reading. :-)</p>
<p>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.</p>
<p>Lastly, have fun, that's what games are for!<br>
</p>
</body>
</html>