-
Notifications
You must be signed in to change notification settings - Fork 11
/
chapter17.html
879 lines (646 loc) · 78.2 KB
/
chapter17.html
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
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<script type="text/javascript">
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">
try {
var pageTracker = _gat._getTracker("UA-5459430-3");
pageTracker._trackPageview();
} catch(err) {}</script>
<meta http-equiv="Content-Type" content="text/html;charset=us-ascii" />
<title>IYOCGwP, Chapter 17 - Graphics and Animation</title>
<link rel="stylesheet" href="inventbook.css" type="text/css" media="all" />
</head>
<body class='chapter17body'>
<table border='0' width='100%'><tr><td><a href='chapter16.html'>Go to Chapter 16 - AI Simulation</a></td><td align='right'><a href='chapter18.html'>Go to Chapter 18 - Collision Detection and Input</a></td></tr></table>
<div style='height: 310px;'><a href='http://www.amazon.com/Invent-Your-Computer-Games-Python/dp/0982106017/'><img src='images/buyad.png' align='right'></a></div>
<div style='height: 350px;'><img src='images/chap17.png'></div>
<div class='inthischapter'><h3 id="TopicsCoveredInThisChapter">Topics Covered In This Chapter:</h3>
<ul>
<li>Software Libraries</li>
<li>Installing Pygame</li>
<li>Graphical user interfaces (GUI)</li>
<li>Drawing primitives</li>
<li>Creating a GUI window with Pygame</li>
<li>Color in Pygame</li>
<li>Fonts in Pygame</li>
<li>Aliased and Anti-Aliased Graphics</li>
<li>Attributes</li>
<li>The <span class='m'>pygame.font.Font</span> Data Type</li>
<li>The <span class='m'>pygame.Surface</span> Data Type</li>
<li>The <span class='m'>pygame.Rect</span> Data Type</li>
<li>The <span class='m'>pygame.PixelArray</span> Data Type</li>
<li>Constructor Functions</li>
<li>The <span class='m'>type()</span> Function</li>
<li>Pygame's Drawing Functions</li>
<li>The <span class='m'>blit()</span> Method for <span class='m'>Surface</span> Objects</li>
<li>Events</li>
<li>The Game Loop</li>
<li>Animation</li>
</ul></div>
<p>So far, all of our games have only used text. Text is displayed on the screen as output, and the player types in text from the keyboard as input. This is simple, and an easy way to learn programming. But in this chapter, we will make some more exciting games with advanced graphics and sound using the Pygame library. Chapters 17, 18, and 19 will teach you how to use the Pygame library to make games with graphics, animation, mouse input, and sound. In these chapters we'll write source code for simple programs that are not games but demonstrate the Pygame concepts we've learned. Chapter 20 will present the source code for a complete Pygame game using all the concepts you've learned.</p>
<p>A <span class='term'>software library</span> is code that is not meant to be run by itself, but included in other programs to add new features. By using a library a programmer doesn't have to write the entire program, but can make use of the work that another programmer has done before them. Pygame is a software library that has modules for graphics, sound, and other features that games commonly use.</p>
<h2 id="InstallingPython26andPygame">Installing Pygame</h2>
<p>Pygame does not come with Python. Like Python, Pygame is available for free. You will have to download and install Pygame, which is as easy as downloading and installing the Python interpreter. In a web browser, go to the URL <a href='http://pygame.org'>http://pygame.org</a> and click on the "Downloads" link on the left side of the web site. This book assumes you have the Windows operating system, but Pygame works the same for every operating system. You need to download the Pygame installer for your operating system and the version of Python you have installed (3.1).</p>
<p>You do not want to download the "source" for Pygame, but rather the Pygame "binary" for your operating system. For Windows, download the <i>pygame-1.9.1.win32-py3.1.msi</i> file. (This is Pygame for Python 3.1 on Windows. If you installed a different version of Python (such as 2.5 or 2.4) download the .msi file for your version of Python.) The current version of Pygame at the time this book was written is 1.9.1. If you see a newer version on the website, download and install the newer Pygame. For Mac OS X and Linux, follow the directions on the download page for installation instructions.</p>
<p class='centeredImageP'><img src='images/17-1.png' alt='' class='centeredImage' /><br />Figure 17-1: The pygame.org website.
</p>
<p>On Windows, double click on the downloaded file to install Pygame. To check that Pygame is install correctly, type the following into the interactive shell:</p>
<div class='sourceblurb'>
>>> import pygame<br />
</div>
<p>If nothing appears after you hit the Enter key, then you know Pygame has successfully been installed. If the error <span class='m'>ImportError: No module named pygame</span> appears, then try to install Pygame again (and make sure you typed <span class='m'>import pygame</span> correctly).</p>
<p>This chapter has five small programs that demonstrate how to use the different features that Pygame provides. In the last chapter, you will use these features for a complete game written in Python with Pygame.</p>
<p>A video tutorial of how to install Pygame is available from this book's website at <a href='http://inventwithpython.com/videos/'>http://inventwithpython.com/videos/</a>.</p>
<h2 id="HelloWorldinPygame">Hello World in Pygame</h2>
<p>We are going to create a new "Hello World!" program, just like you created at the beginning of the book. This time, we will use Pygame to make "Hello world!" appear in a <span class='term'>graphical user interface</span> (GUI, which is pronounced "gooey") window. A graphical user interface gives you a window that color, shapes, and images can be drawn on by your program, as well as accepting mouse input (and not just keyboard input). The basic shapes that we draw on the screen are called <span class='term'>drawing primitives</span>. GUI windows are used instead of the text window (also called a <span class='term'>console window</span> or a <span class='term'>terminal window</span>) that we used for all our previous games.</p>
<p>Pygame does not work well with the interactive shell because it relies on a game loop (we will describe game loops later). Because of this, you can only write Pygame programs and cannot send instructions to Pygame one at a time through the interactive shell.</p>
<p>Pygame programs also do not use the <span class='m'>input()</span> function. There is no text input and output. Instead, the program displays output in a window by drawing graphics and text to the window. Pygame program's input comes from the keyboard and the mouse through things called events, which we will go over in the next chapter. However, if our program has bugs that cause Python to display an error message, the error message will show up in the console window.</p>
<p>You can also look up information about how to use the Pygame library by visiting the web site <a href='http://pygame.org/docs/ref/'>http://pygame.org/docs/ref/</a>.</p>
<h2 id="HelloWorldsSourceCode">Hello World's Source Code</h2>
<p>Type in the following code into the file editor, and save it as <i>pygameHelloWorld.py</i>. Or you can download this source code by going to this book's website at <a href='http://inventwithpython.com/chapter17'>http://inventwithpython.com/chapter17</a></p>
<div class='sourcecode'><span class='sourcecodeHeader'>pygameHelloWorld.py</span><br /><span class='sourcecodeSubHeader'>This code can be downloaded from <a href='http://inventwithpython.com/pygameHelloWorld.py'>http://inventwithpython.com/pygameHelloWorld.py</a><br />If you get errors after typing this code in, compare it to the book's code with the online diff tool at <a href='http://inventwithpython.com/diff'>http://inventwithpython.com/diff</a> or email the author at <a href="mailto:[email protected]">[email protected]</a></span><br /><ol start='1'>
<li>import pygame, sys</li>
<li>from pygame.locals import *</li>
<li></li>
<li># set up pygame</li>
<li>pygame.init()</li>
<li></li>
<li># set up the window</li>
<li>windowSurface = pygame.display.set_mode((500, 400), 0, 32)</li>
<li>pygame.display.set_caption('Hello world!')</li>
<li></li>
<li># set up the colors</li>
<li>BLACK = (0, 0, 0)</li>
<li>WHITE = (255, 255, 255)</li>
<li>RED = (255, 0, 0)</li>
<li>GREEN = (0, 255, 0)</li>
<li>BLUE = (0, 0, 255)</li>
<li></li>
<li># set up fonts</li>
<li>basicFont = pygame.font.SysFont(None, 48)</li>
<li></li>
<li># set up the text</li>
<li>text = basicFont.render('Hello world!', True, WHITE, BLUE)</li>
<li>textRect = text.get_rect()</li>
<li>textRect.centerx = windowSurface.get_rect().centerx</li>
<li>textRect.centery = windowSurface.get_rect().centery</li>
<li></li>
<li># draw the white background onto the surface</li>
<li>windowSurface.fill(WHITE)</li>
<li></li>
<li># draw a green polygon onto the surface</li>
<li>pygame.draw.polygon(windowSurface, GREEN, ((146, 0), (291, 106), (236, 277), (56, 277), (0, 106)))</li>
<li></li>
<li># draw some blue lines onto the surface</li>
<li>pygame.draw.line(windowSurface, BLUE, (60, 60), (120, 60), 4)</li>
<li>pygame.draw.line(windowSurface, BLUE, (120, 60), (60, 120))</li>
<li>pygame.draw.line(windowSurface, BLUE, (60, 120), (120, 120), 4)</li>
<li></li>
<li># draw a blue circle onto the surface</li>
<li>pygame.draw.circle(windowSurface, BLUE, (300, 50), 20, 0)</li>
<li></li>
<li># draw a red ellipse onto the surface</li>
<li>pygame.draw.ellipse(windowSurface, RED, (300, 250, 40, 80), 1)</li>
<li></li>
<li># draw the text's background rectangle onto the surface</li>
<li>pygame.draw.rect(windowSurface, RED, (textRect.left - 20, textRect.top - 20, textRect.width + 40, textRect.height + 40))</li>
<li></li>
<li># get a pixel array of the surface</li>
<li>pixArray = pygame.PixelArray(windowSurface)</li>
<li>pixArray[480][380] = BLACK</li>
<li>del pixArray</li>
<li></li>
<li># draw the text onto the surface</li>
<li>windowSurface.blit(text, textRect)</li>
<li></li>
<li># draw the window onto the screen</li>
<li>pygame.display.update()</li>
<li></li>
<li># run the game loop</li>
<li>while True:</li>
<li> for event in pygame.event.get():</li>
<li> if event.type == QUIT:</li>
<li> pygame.quit()</li>
<li> sys.exit()</li>
</ol></div>
<h2 id="RunningtheHelloWorldProgram">Running the Hello World Program</h2>
<p>When you run this program, you should see a new GUI window appear which looks like Figure 17-2.</p>
<p>What is nice about using a GUI instead of a console is that the text can appear anywhere in the window, not just after the previous text we have printed. The text can be any color or size.</p>
<p>One thing you may notice is that Pygame uses a lot of tuples instead of lists. <span class='term'>Tuples</span> are just like lists (they can contain multiple values) except they are typed with parentheses <span class='m'>(</span> and <span class='m'>)</span>, instead of square brackets <span class='m'>[</span> and <span class='m'>]</span>. The main difference is that once you create a tuple, you cannot change, add, or remove any values in the tuple. For technical reasons, knowing that the contents of the tuple never change allows Python to handle this data more efficiently, which is why Pygame uses tuples instead of lists.</p>
<p class='centeredImageP'><img src='images/17-2.png' alt='' class='centeredImage' /><br />Figure 17-2: The "Hello World" program.</p>
<h3 id="ImportingthePygameModule">Importing the Pygame Module</h3>
<p>Let's go over each of these lines of code and find out what they do.</p>
<div class='sourcecode'><ol start='1'>
<li>import pygame, sys</li>
<li>from pygame.locals import *</li>
</ol></div>
<p>First we need to import the <span class='m'>pygame</span> module so we can call the functions in the Pygame software library. You can import several modules on the same line by delimiting the module names with commas. Line 1 imports both the <span class='m'>pygame</span> and <span class='m'>sys</span> modules.</p>
<p>The second line imports the <span class='m'>pygame.locals</span> module. This module contains many constant variables that we will use with Pygame such as <span class='m'>QUIT</span> or <span class='m'>K_ESCAPE</span> (which we will explain later). However, using the form <span class='m'>from moduleName import *</span> we can import the <span class='m'>pygame.locals</span> module but not have to type <span class='m'>pygame.locals</span> in front of each time we use the module's functions and variables in our program. The <span class='m'>*</span> symbol means we should import everything inside the module.</p>
<p>The <span class='m'>pygame.locals</span> module contains some constant variables we will use in this program. </p>
<p>If you have <span class='m'>from sys import *</span> instead of <span class='m'>import sys</span> in your program, you could call <span class='m'>exit()</span> instead of <span class='m'>sys.exit()</span> in your code. (But most of the time it is better to use the full function name so that you know which module the <span class='m'>exit()</span> is in.)</p>
<h2 id="ThepygameinitFunction">The <span class='m'>pygame.init()</span> Function</h2>
<div class='sourcecode'><ol start='4'>
<li># set up pygame</li>
<li>pygame.init()</li>
</ol></div>
<p>The Pygame software library has some initial code that needs to be run before we can use it. All Pygame programs must run this code by calling the <span class='m'>pygame.init()</span> after importing the <span class='m'>pygame</span> module but before calling any other Pygame functions.</p>
<h2 id="ThepygamedisplaysetmodeandpygamedisplaysetcaptionFunctions">The <span class='m'>pygame.display.set_mode()</span> and <span class='m'>pygame.display.set_caption()</span> Functions</h2>
<div class='sourcecode'><ol start='7'>
<li># set up the window</li>
<li>windowSurface = pygame.display.set_mode((500, 400), 0, 32)</li>
<li>pygame.display.set_caption('Hello world!')</li>
</ol></div>
<p>Line 8 creates a GUI window for our program by calling the <span class='m'>set_mode()</span> method in the <span class='m'>pygame.display</span> module. (The <span class='m'>display</span> module is a module inside the <span class='m'>pygame</span> module. Pygame is so advanced that even the pygame module has its own modules!)</p>
<p>Just to avoid confusion, you should know the difference between the window that is created is different and the Windows operating system. The graphical user interface is printed as "window" (lower case and singular) and the Microsoft operating system is "Windows" (upper case and plural).</p>
<p>There are three parameters to the <span class='m'>set_mode()</span> method. The first parameter is a tuple of two integers for the width and height of the window, in pixels. A <span class='term'>pixel</span> is the tiniest dot on your computer screen. A single pixel on your screen can turn into any color. All the pixels on your screen work together to display all the pictures you see. To see how tiny a pixel is, look at the bottom right corner of the "Hello World!" window. This program sets just one pixel as white.</p>
<p>We want the window to be 500 pixels wide and 400 pixels high, so we use the tuple <span class='m'>(500, 400)</span> for the first parameter. To get the total number of pixels in our window, multiply the width and the height. Our window is made up of 20,000 pixels, and it doesn't even take up the entire computer screen!</p>
<p>The second parameter is for advanced GUI window options. You won't really need this for your games, so you can always just pass <span class='m'>0</span> for this parameter. The third parameter is another advanced option called the color depth. You also don't need to know what this means, and can just always pass the value <span class='m'>32</span>.</p>
<p>The <span class='m'>set_caption()</span> call returns a <span class='m'>pygame.Surface</span> object (which we will call <span class='m'>Surface</span> objects for short). Objects are values of a data type that have methods as well as data. For example, strings are objects in Python because they have data (the string itself) and methods (such as <span class='m'>lower()</span> and <span class='m'>split()</span>). You can store references to objects in variables just like list reference values. The <span class='m'>Surface</span> object represents the window and we will include the <span class='m'>windowSurface</span> variable in all of our calls to drawing functions.</p>
<p>To refresh your memory about the differences between values and reference values, go back to chapter 10.</p>
<h2 id="ColorsinPygame">Colors in Pygame</h2>
<div class='sourcecode'><ol start='11'>
<li># set up the colors</li>
<li>BLACK = (0, 0, 0)</li>
<li>WHITE = (255, 255, 255)</li>
<li>RED = (255, 0, 0)</li>
<li>GREEN = (0, 255, 0)</li>
<li>BLUE = (0, 0, 255)</li>
</ol></div>
<table class='floatTable'><tr><td class='floatTable'>
<table class="simplefulltable" style='width: 230px; text-align: center; float: right;'>
<caption>Table 17-1: Colors and their RGB values.</caption>
<tr><th>Color</th><th>RGB Values</th></tr>
<tr><td>Aqua</td><td>(0, 255, 255)</td></tr>
<tr><td>Black</td><td>(0, 0, 0)</td></tr>
<tr><td>Blue</td><td>(0, 0, 255)</td></tr>
<tr><td>Cornflower Blue</td><td>(100, 149, 237)</td></tr>
<tr><td>Fuchsia</td><td>(255, 0, 255)</td></tr>
<tr><td>Gray</td><td>(128, 128, 128)</td></tr>
<tr><td>Green</td><td>(0, 128, 0)</td></tr>
<tr><td>Lime</td><td>(0, 255, 0)</td></tr>
<tr><td>Maroon</td><td>(128, 0, 0)</td></tr>
<tr><td>Navy Blue</td><td>(0, 0, 128)</td></tr>
<tr><td>Olive</td><td>(128, 128, 0)</td></tr>
<tr><td>Purple</td><td>(128, 0, 128)</td></tr>
<tr><td>Red</td><td>(255, 0, 0)</td></tr>
<tr><td>Silver</td><td>(192, 192, 192)</td></tr>
<tr><td>Teal</td><td>(0, 128, 128)</td></tr>
<tr><td>White</td><td>(255, 255, 255)</td></tr>
<tr><td>Yellow</td><td>(255, 255, 0)</td></tr>
</table>
<p>There are three primary colors of light: red, green and blue. By combining different amounts of these three colors you can form any other color. In Python, we represent colors with tuples of three integers. The first value in the tuple is how much red is in the color. A value of <span class='m'>0</span> means there is no red in this color, and a value of <span class='m'>255</span> means there is a maximum amount of red in the color. The second value is for green and the third value is for blue.</p>
<p>For example, we will create the tuple <span class='m'>(0, 0, 0)</span> and store it in a variable named <span class='m'>BLACK</span>. With no amount of red, green, or blue, the resulting color is completely black. The color black is the absence of any color.</p>
<p>On line 13, we use the tuple <span class='m'>(255, 255, 255)</span> for a maximum amount of red, green, and blue to result in white. The color white is the full combination of red, green, and blue. We store this tuple in the <span class='m'>WHITE</span> variable. The tuple <span class='m'>(255, 0, 0)</span> represents the maximum amount of red but no amount of green and blue, so the resulting color is red. Similarly, <span class='m'>(0, 255, 0)</span> is green and <span class='m'>(0, 0, 255)</span> is blue.</p>
<p>These variable names are in all capitals because they are constant variables. It's just easier to type <span class='m'>BLACK</span> in our code than <span class='m'>(0, 0, 0)</span> every time we want to specify the color black, so we set up these color variables at the start of our program.</p>
<p>If you want to make a color lighter, try adding an equal amount from all three values. For example, the RGB value for gray is <span class='m'>(128, 128, 128)</span>. You can get the RGB value for a lighter gray by adding 20 to each value to get <span class='m'>(148, 148, 148)</span>. You can get the RGB value for a darker gray by subtracting 20 from each value to get <span class='m'>(108, 108, 108)</span>. And you can get the RGB value for a slightly redder gray by adding 20 to only the red value to get <span class='m'>(148, 128, 128)</span>. Table 17-1 has some common colors and their RGB values.</p>
</td></tr></table>
<h2 id="FontsandthepygamefontSysFontFunction">Fonts, and the pygame.font.SysFont() Function</h2>
<div class='sourcecode'><ol start='18'>
<li># set up fonts</li>
<li>basicFont = pygame.font.SysFont(None, 48)</li>
</ol></div>
<table class='floatTable'><tr><td class='floatTable'>
<p style='float: right;' class='centeredImageP'><img src='images/17-3.png' alt='' class='centeredImage' /><br />Figure 17-3: Examples of different fonts.</p>
<p>A <span class='term'>font</span> is a complete set of letters, numbers, symbols, and characters drawn in a single style. Figure 17-3 is an example of the same sentence printed in different fonts.</p>
<p>In our earlier games, we only told Python to print out text. The color, size, and font that was used to display this text was completely determined by whatever font your operating system uses for console windows. Our programs could not change the font at all. However, since we will be drawing out letters to a GUI window we need to tell Pygame exactly what font to use when drawing the text.</p>
<p>On line 19 we create a <span class='m'>pygame.font.Font</span> object (which we will just call <span class='m'>Font</span> objects for short) by calling the <span class='m'>pygame.font.SysFont()</span> function. The first parameter is the name of the font, but we will pass the <span class='m'>None</span> value to use the default system font. The second parameter will be the size of the font (which is measured in units called <span class='term'>points</span>). In our call on line 19, we want the font size to be 48 points.</p>
</td></tr></table>
<h2 id="TherenderMethodforFontObjects">The <span class='m'>render()</span> Method for <span class='m'>Font</span> Objects</h2>
<div class='sourcecode'><ol start='21'>
<li># set up the text</li>
<li>text = basicFont.render('Hello world!', True, WHITE, BLUE)</li>
<li>textRect = text.get_rect()</li>
</ol></div>
<table class='floatTable'><tr><td class='floatTable'>
<p style='float: right;' class='centeredImageP'><img src='images/17-4.png' alt='' class='centeredImage' /><br />Figure 17-4: An aliased line and an anti-aliased line.</p>
<p>The <span class='m'>Font</span> object that we have stored in the <span class='m'>basicFont</span> variable has a method called <span class='m'>render()</span>. This method will create a <span class='m'>Surface</span> object with the text drawn on it. The first parameter to <span class='m'>render()</span> is the string of the text to draw. The second parameter is a Boolean for whether or not we want anti-aliasing. Anti-aliasing is a technique for making a drawing look less blocky. On line 22, we pass <span class='m'>True</span> to say we want to use anti-aliasing. Figure 17-4 is an example of what a line (when we enlarge the individual pixels) looks like with and without anti-aliasing.</p>
<p>Anti-aliasing can make your text and lines look blurry but smoother. It takes a little more computation time to do anti-aliasing, so although the graphics may look better, your program may run slower (but only just a little).</p>
</td></tr></table>
<h2 id="Attributes">Attributes</h2>
<div class='sourcecode'><ol start='24'>
<li>textRect.centerx = windowSurface.get_rect().centerx</li>
<li>textRect.centery = windowSurface.get_rect().centery</li>
</ol></div>
<p>The <span class='m'>pygame.Rect</span> data type (which we will just call <span class='m'>Rect</span> for short) makes working with rectangle-shaped things easy. To create a new <span class='m'>Rect</span> object call the function <span class='m'>pygame.Rect()</span>. The parameters are integers for the XY coordinates of the top left corner, followed by the width and height. These integers describe the size in number of pixels.</p>
<p>The function name with the parameters looks like this: <span class='m'>pygame.Rect(left, top, width, height)</span></p>
<p>Just like methods are functions that are associated with an object, <span class='term'>attributes</span> are variables that are associated with an object. <!-- Guido uses "attribute" as any name that follows a period. I left it out because it complicates the definition for the purposes here. -->The <span class='m'>Rect</span> data type (that is, the data type of all <span class='m'>Rect</span> objects) has many attributes that describe the rectangle they represent. Here is a list of attributes of a <span class='m'>Rect</span> object named <span class='m'>myRect</span>:</p>
<table class='simplefulltable'>
<tr><th><span class='m'>pygame.Rect</span> Attribute</th><th>Description</th></tr>
<tr><td><span class='m'>myRect.left</span></td><td>The int value of the X-coordinate of the left side of the rectangle.</td></tr>
<tr><td><span class='m'>myRect.right</span></td><td>The int value of the X-coordinate of the right side of the rectangle.</td></tr>
<tr><td><span class='m'>myRect.top</span></td><td>The int value of the Y-coordinate of the top side of the rectangle.</td></tr>
<tr><td><span class='m'>myRect.bottom</span></td><td>The int value of the Y-coordinate of the bottom side of the rectangle.</td></tr>
<tr><td></td></tr>
<tr><td><span class='m'>myRect.centerx</span></td><td>The int value of the X-coordinate of the center of the rectangle.</td></tr>
<tr><td><span class='m'>myRect.centery</span></td><td>The int value of the Y-coordinate of the center of the rectangle.</td></tr>
<tr><td></td></tr>
<tr><td><span class='m'>myRect.width</span></td><td>The int value of the width of the rectangle.</td></tr>
<tr><td><span class='m'>myRect.height</span></td><td>The int value of the height of the rectangle.</td></tr>
<tr><td><span class='m'>myRect.size</span></td><td>A tuple of two ints: (width, height)</td></tr>
<tr><td></td></tr>
<tr><td><span class='m'>myRect.topleft</span></td><td>A tuple of two ints: (left, top)</td></tr>
<tr><td><span class='m'>myRect.topright</span></td><td>A tuple of two ints: (right, top)</td></tr>
<tr><td><span class='m'>myRect.bottomleft</span></td><td>A tuple of two ints: (left, bottom)</td></tr>
<tr><td><span class='m'>myRect.bottomright</span></td><td>A tuple of two ints: (right, bottom)</td></tr>
<tr><td></td></tr>
<tr><td><span class='m'>myRect.midleft</span></td><td>A tuple of two ints: (left, centery)</td></tr>
<tr><td><span class='m'>myRect.midright</span></td><td>A tuple of two ints: (right, centery)</td></tr>
<tr><td><span class='m'>myRect.midtop</span></td><td>A tuple of two ints: (centerx, top)</td></tr>
<tr><td><span class='m'>myRect.midbottom</span></td><td>A tuple of two ints: (centerx, bottom)</td></tr>
</table>
<p>The great thing about <span class='m'>Rect</span> objects is that if you modify any of these variables, all the other variables will automatically modify themselves as well. For example, if you create a <span class='m'>Rect</span> object that is 20 pixels wide and 20 pixels high, and has the top left corner at the coordinates (30, 40), then the X-coordinate of the right side will automatically be set to 50 (because 20 + 30 = 50). However, if you change the <span class='m'>left</span> attribute with the line <span class='m'>myRect.left = 100</span>, then Pygame will automatically change the <span class='m'>right</span> attribute to 120 (because 20 + 100 = 120). Every other attribute for that <span class='m'>Rect</span> object will also be updated as well.</p>
<h2 id="ThegetrectMethodsforpygamefontFontandpygameSurfaceObjects">The <span class='m'>get_rect()</span> Methods for <span class='m'>pygame.font.Font</span> and <span class='m'>pygame.Surface</span> Objects</h2>
<p>Notice that both the <span class='m'>Font</span> object (stored in the <span class='m'>text</span> variable) and the <span class='m'>Surface</span> object (stored in <span class='m'>windowSurface</span> variable) both have a method called <span class='m'>get_rect()</span>. Technically, these are two different methods. But the programmers of Pygame gave them the same name because they both do the same thing and return <span class='m'>Rect</span> objects that represent the size and position of the <span class='m'>Font</span> or <span class='m'>Surface</span> object.</p>
<p>Also, remember that <span class='m'>pygame</span> is a module that we import, and inside the <span class='m'>pygame</span> module are the <span class='m'>font</span> and <span class='m'>surface</span> modules. Inside <i>those</i> modules are the <span class='m'>Font</span> and <span class='m'>Surface</span> data types. The Pygame programmers made the modules begin with a lowercase letter, and the data types begin with an uppercase letter. This makes it easier to distinguish the data types and the modules that the data types can be found in.</p>
<h2 id="ConstructorFunctionsandthetypefunction">Constructor Functions and the <span class='m'>type()</span> function.</h2>
<p>We create a <span class='m'>pygame.Rect</span> object by calling a function named <span class='m'>pygame.Rect()</span>. The <span class='m'>pygame.Rect()</span> function has the same name as the <span class='m'>pygame.Rect</span> data type. Functions that have the same name as their data type and create objects or values of this data type are called <span class='term'>constructor functions</span>.</p>
<!--<p>The <span class='m'>int()</span> and <span class='m'>str()</span> functions are also constructor functions. The <span class='m'>int()</span> function returns an int version of whatever you pass it, whether it is <span class='m'>int(5)</span> or <span class='m'>int('5')</span>. (The proper name for strings in Python is <span class='m'>str</span>.)</p>-->
<p>You can always find out what the proper name of a value's data type with the <span class='m'>type()</span> function. For example, try typing the following into the interactive shell:</p>
<div class='sourceblurb'>
>>> type('This is a string')<br />
<type 'str'><br />
>>> type(5)<br />
<type 'int'><br />
>>> spam = 'Another string'<br />
>>> type(spam)<br />
<type 'str'><br />
>>> import pygame<br />
>>> pygame.init()<br />
>>> myRect = pygame.Rect(10, 10, 40, 50)<br />
>>> type(myRect)<br />
<type 'pygame.Rect'><br />
>>> pygame.quit()<br />
</div>
<p>(You need to call the <span class='m'>pygame.quit()</span> function when you are done with typing Pygame functions into the interactive shell. Otherwise you may cause Python to crash.) Notice that the return value from the <span class='m'>type()</span> function is not a string, but a value of a data type called "type"! Try typing this into the interactive shell:</p>
<div class='sourceblurb'>
>>> type(type('This is a string'))<br />
<type 'type'><br />
</div>
<p>For the most part, you don't need to know about data types and the <span class='m'>type()</span> function when programming games. But it can be very useful if you need to find out the data type of the value stored in a variable in your program.</p>
<h2 id="ThefillMethodforSurfaceObjects">The <span class='m'>fill()</span> Method for <span class='m'>Surface</span> Objects</h2>
<div class='sourcecode'><ol start='27'>
<li># draw the white background onto the surface</li>
<li>windowSurface.fill(WHITE)</li>
</ol></div>
<p>This is the first drawing function call in our program. We want to fill the entire surface stored in <span class='m'>windowSurface</span> with the color white. The <span class='m'>fill()</span> function will completely cover the entire surface with the color we pass as the parameter. (In this case, we pass <span class='m'>WHITE</span> to make the background white.)</p>
<p>An important thing to know about Pygame is that the window on the screen will not change when we call the <span class='m'>fill()</span> method or any of the other drawing functions. These will draw on the <span class='m'>Surface</span> object, but the <span class='m'>Surface</span> object will not be drawn on the user's screen until the <span class='m'>pygame.display.update()</span> function is called. This is because drawing on the <span class='m'>Surface</span> object (which is stored in the computer's memory) is much faster than drawing to the computer screen. It is much more efficient to draw onto the screen once and only after all of our drawing functions to draw to the surface.</p>
<h2 id="ThepygamedrawpolygonFunction">The <span class='m'>pygame.draw.polygon()</span> Function</h2>
<div class='sourcecode'><ol start='30'>
<li># draw a green polygon onto the surface</li>
<li>pygame.draw.polygon(windowSurface, GREEN, ((146, 0), (291, 106), (236, 277), (56, 277), (0, 106)))</li>
</ol></div>
<p>A polygon is any multisided shape with sides that are only straight lines. The <span class='m'>pygame.draw.polygon()</span> function can draw any shape that you give it and fill the inside space of the polygon. The tuple of tuples you pass it represents the XY coordinates of the points to draw in order. The last tuple will automatically connect to the first tuple to complete the shape.</p>
<p class='centeredImageP'><img src='images/17-5.png' alt='' class='centeredImage' /><br />Figure 17-5: Examples of Polygons.</p>
<p>Polygons only have straight lines for sides (circles and ellipses are not polygons). Figure 17-5 has some examples of polygons.</p>
<h2 id="ThepygamedrawlineFunction">The <span class='m'>pygame.draw.line()</span> Function</h2>
<div class='sourcecode'><ol start='33'>
<li># draw some blue lines onto the surface</li>
<li>pygame.draw.line(windowSurface, BLUE, (60, 60), (120, 60), 4)</li>
<li>pygame.draw.line(windowSurface, BLUE, (120, 60), (60, 120))</li>
<li>pygame.draw.line(windowSurface, BLUE, (60, 120), (120, 120), 4)</li>
</ol></div>
<p>The <span class='m'>pygame.draw.line()</span> function will draw a line on the <span class='m'>Surface</span> object that you provide. Notice that the last parameter, the width of the line, is optional. If you pass <span class='m'>4</span> for the width, the line will be four pixels thick. If you do not specify the <span class='m'>width</span> parameter, it will take on the default value of <span class='m'>1</span>.</p>
<h2 id="ThepygamedrawcircleFunction">The <span class='m'>pygame.draw.circle()</span> Function</h2>
<div class='sourcecode'><ol start='38'>
<li># draw a blue circle onto the surface</li>
<li>pygame.draw.circle(windowSurface, BLUE, (300, 50), 20, 0)</li>
</ol></div>
<p>The <span class='m'>pygame.draw.circle()</span> function will draw a circle on the <span class='m'>Surface</span> object you provide. The third parameter is for the X and Y coordinates of the center of the circle as a tuple of two ints. The fourth parameter is an <span class='m'>int</span> for the radius (that is, size) of the circle in pixels. A <span class='m'>width</span> of <span class='m'>0</span> means that the circle will be filled in.</p>
<h2 id="ThepygamedrawellipseFunction">The <span class='m'>pygame.draw.ellipse()</span> Function</h2>
<div class='sourcecode'><ol start='41'>
<li># draw a red ellipse onto the surface</li>
<li>pygame.draw.ellipse(windowSurface, RED, (300, 250, 40, 80), 1)</li>
</ol></div>
<p>The <span class='m'>pygame.draw.ellipse()</span> function will draw an ellipse. It is similar to the <span class='m'>pygame.draw.circle()</span> function, except that instead of specifying the center of the circle, a tuple of four ints is passed for the left, top, width, and height of the ellipse.</p>
<h2 id="ThepygamedrawrectFunction">The <span class='m'>pygame.draw.rect()</span> Function</h2>
<div class='sourcecode'><ol start='44'>
<li># draw the text's background rectangle onto the surface</li>
<li>pygame.draw.rect(windowSurface, RED, (textRect.left - 20, textRect.top - 20, textRect.width + 40, textRect.height + 40))</li>
</ol></div>
<p>The <span class='m'>pygame.draw.rect()</span> function will draw a rectangle. The third parameter is a tuple of four ints for the left, top, width, and height of the rectangle. Instead of a tuple of four ints for the third parameter, you can also pass a <span class='m'>Rect</span> object. In line 45, we want the rectangle we draw to be 20 pixels around all the sides of the text. This is why we want the drawn rectangle's left and top to be the left and top of <span class='m'>textRect</span> minus <span class='m'>20</span>. (Remember, we subtract because coordinates decrease as you go left and up.) And the width and height will be equal to the width and height of the <span class='m'>textRect</span> plus <span class='m'>40</span> (because the left and top were moved back 20 pixels, so we need to make up for that space).</p>
<h2 id="ThepygamePixelArrayDataType">The pygame.PixelArray Data Type</h2>
<div class='sourcecode'><ol start='47'>
<li># get a pixel array of the surface</li>
<li>pixArray = pygame.PixelArray(windowSurface)</li>
<li>pixArray[480][380] = BLACK</li>
</ol></div>
<p>On line 48 we create a <span class='m'>pygame.PixelArray</span> object (which we will just call a <span class='m'>PixelArray</span> object for short). The <span class='m'>PixelArray</span> object is a list of lists of color tuples that represents the <span class='m'>Surface</span> object you passed it. We passed <span class='m'>windowSurface</span> object when we called the <span class='m'>PixelArray()</span> constructor function on line 48, so assigning <span class='m'>BLACK</span> to <span class='m'>pixArray[480][380]</span> will change the pixel at the coordinates (480, 380) to be a black pixel. Pygame will automatically modify the <span class='m'>windowSurface</span> object with this change.</p>
<p>The first index in the <span class='m'>PixelArray</span> object is for the X-coordinate. The second index is for the Y-coordinate. <span class='m'>PixelArray</span> objects make it easy to set individual pixels on a <span class='m'>PixelArray</span> object to a specific color.</p>
<div class='sourcecode'><ol start='50'>
<li>del pixArray</li>
</ol></div>
<p>Creating a <span class='m'>PixelArray</span> object from a <span class='m'>Surface</span> object will lock that <span class='m'>Surface</span> object. Locked means that no <span class='m'>blit()</span> function calls (described next) can be made on that <span class='m'>Surface</span> object. To unlock the <span class='m'>Surface</span> object, you must delete the <span class='m'>PixelArray</span> object with the del operator. If you forget to delete the <span class='m'>Surface</span> object, you will get an error message that says <span class='m'>pygame.error: Surfaces must not be locked during blit</span>.</p>
<h2 id="TheblitMethodforSurfaceObjects">The <span class='m'>blit()</span> Method for <span class='m'>Surface</span> Objects</h2>
<div class='sourcecode'><ol start='52'>
<li># draw the text onto the surface</li>
<li>windowSurface.blit(text, textRect)</li>
</ol></div>
<p>The <span class='m'>blit()</span> method will draw the contents of one <span class='m'>Surface</span> object onto another <span class='m'>Surface</span> object. Line 54 will draw the "Hello world!" text (which was drawn on the <span class='m'>Surface</span> object stored in the <span class='m'>text</span> variable) and draws it to the <span class='m'>Surface</span> object stored in the <span class='m'>windowSurface</span> variable.</p>
<p>Remember that the <span class='m'>text</span> object had the "Hello world!" text drawn on it on line 22 by the <span class='m'>render()</span> method. <span class='m'>Surface</span> objects are just stored in the computer's memory (like any other variable) and not drawn on the screen. The <span class='m'>Surface</span> object in <span class='m'>windowSurface</span> is drawn on the screen when we call the <span class='m'>pygame.display.update()</span> function on line 56 because this was the <span class='m'>Surface</span> object created by the <span class='m'>pygame.display</span>.set_mode() function. Other <span class='m'>Surface</span> objects are not drawn on the screen.</p>
<p>The second parameter to <span class='m'>blit()</span> specifies where on the <span class='m'>windowSurface</span> surface the <span class='m'>text</span> surface should be drawn. We will just pass the <span class='m'>Rect</span> object we got from calling <span class='m'>text.get_rect()</span> (which was stored in <span class='m'>textRect</span> on line 23).</p>
<h2 id="ThepygamedisplayupdateFunction">The pygame.display.update() Function</h2>
<div class='sourcecode'><ol start='55'>
<li># draw the window onto the screen</li>
<li>pygame.display.update()</li>
</ol></div>
<p>In Pygame, nothing is drawn to the screen until the <span class='m'>pygame.display.update()</span> function is called. This is done because drawing to the screen is a slow operation for the computer compared to drawing on the <span class='m'>Surface</span> objects while they are in memory. You do not want to draw to the screen after each drawing function is called, but only draw the screen once after all the drawing functions have been called.</p>
<p>You will need to call <span class='m'>pygame.display.update()</span> each time you want to update the screen to display the contents of the <span class='m'>Surface</span> object returned by <span class='m'>pygame.display.set_mode()</span>. (In this program, that object is the one stored in <span class='m'>windowSurface</span>.) This will become more important in our next program which covers animation.</p>
<h2 id="EventsandtheGameLoop">Events and the Game Loop</h2>
<p>In our previous games, all of the programs print out everything immediately until they reach a <span class='m'>input()</span> function call. At that point, the program stops and waits for the user to type something in and press Enter. Pygame programs do not work this way. Instead, Pygame programs are constantly running through a loop called the game loop. (In this program, we execute all the lines of code in the game loop about one hundred times a second.)</p>
<p>The <span class='term'>game loop</span> is a loop that constantly checks for new events, updates the state of the window, and draws the window on the screen. <span class='term'>Events</span> are objects of the <span class='m'>pygame.event.Event</span> data type that are generated by Pygame whenever the user presses a key, clicks or moves the mouse, or makes some other event occur. Calling <span class='m'>pygame.event.get()</span> retrieves any new <span class='m'>pygame.event.Event</span> objects that have been generated since the last call to <span class='m'>pygame.event.get()</span>.</p>
<div class='sourcecode'><ol start='58'>
<li># run the game loop</li>
<li>while True:</li>
</ol></div>
<p>This is the start of our game loop. The condition for the <span class='m'>while</span> statement is set to <span class='m'>True</span> so that we loop forever. The only time we exit the loop is if an event causes the program to terminate.</p>
<h2 id="ThepygameeventgetFunction">The pygame.event.get() Function</h2>
<div class='sourcecode'><ol start='60'>
<li> for event in pygame.event.get():</li>
<li> if event.type == QUIT:</li>
</ol></div>
<p>The <span class='m'>pygame.event.get()</span> function returns a list of <span class='m'>pygame.event.Event</span> objects. This list has every single event that has occurred since the last time <span class='m'>pygame.event.get()</span> was called. All <span class='m'>pygame.event.Event</span> objects have an attribute called <span class='m'>type</span> which tell us what type of event it is. (A list of event types is given in the next chapter. In this chapter we only deal with the <span class='m'>QUIT</span> event.)</p>
<p>Pygame comes supplied with its own constant variables in the <span class='m'>pygame.locals</span> module. Remember that we have imported the <span class='m'>pygame.locals</span> module with the line from <span class='m'>pygame.locals import *</span>, which means we do not have to type <span class='m'>pygame.locals</span> in front of the variables and functions in that module.</p>
<p>On line 60 we set up a <span class='m'>for</span> loop to check each <span class='m'>pygame.event.Event</span> object in the list returned by <span class='m'>pygame.event.get()</span>. If the <span class='m'>type</span> attribute of the event is equal to the value of the constant variable <span class='m'>QUIT</span> (which is provided by the <span class='m'>pygame.locals</span> module), then we know the user has closed the window and wants to terminate the program.</p>
<p>Pygame generates the <span class='m'>QUIT</span> event when the user clicks on the X button at the top right of the program's window. It is also generated if the computer is shutting down and tries to terminate all the programs running. For whatever reason the <span class='m'>QUIT</span> event was generated, we know that we should run any code that we want to happen to stop the program. You could choose to ignore the <span class='m'>QUIT</span> event entirely, but that may cause the program to be confusing to the user.</p>
<h2 id="ThepygamequitFunction">The pygame.quit() Function</h2>
<div class='sourcecode'><ol start='62'>
<li> pygame.quit()</li>
<li> sys.exit()</li>
</ol></div>
<p>If the <span class='m'>QUIT</span> event has been generated, then we can know that the user has tried to close the window. In that case, we should call the exit functions for both Pygame (<span class='m'>pygame.quit()</span>) and Python (<span class='m'>sys.exit()</span>).</p>
<p>This has been the simple "Hello world!" program from Pygame. We've covered many new topics that we didn't have to deal with in our previous games. Even though they are more complicated, the Pygame programs can also be much more fun and engaging than our previous text games. Let's learn how to create games with animated graphics that move.</p>
<h2 id="Animation">Animation</h2>
<p>In this program we have several different blocks bouncing off of the edges of the window. The blocks are different colors and sizes and move only in diagonal directions. In order to animate the blocks (that is, make them look like they are moving) we will move the blocks a few pixels over on each iteration through the game loop. By drawing new blocks that are located a little bit differently then the blocks before, we can make it look like the blocks are moving around the screen.</p>
<h2 id="TheAnimationProgramsSourceCode">The Animation Program's Source Code</h2>
<p>Type the following program into the file editor and save it as <i>animation.py</i>. You can also download this source code from <a href='http://inventwithpython.com/chapter17'>http://inventwithpython.com/chapter17</a>.</p>
<div class='sourcecode'><span class='sourcecodeHeader'>animation.py</span><br /><span class='sourcecodeSubHeader'>This code can be downloaded from <a href='http://inventwithpython.com/animation.py'>http://inventwithpython.com/animation.py</a><br />If you get errors after typing this code in, compare it to the book's code with the online diff tool at <a href='http://inventwithpython.com/diff'>http://inventwithpython.com/diff</a> or email the author at <a href="mailto:[email protected]">[email protected]</a></span><br /><ol start='1'>
<li>import pygame, sys, time</li>
<li>from pygame.locals import *</li>
<li></li>
<li># set up pygame</li>
<li>pygame.init()</li>
<li></li>
<li># set up the window</li>
<li>WINDOWWIDTH = 400</li>
<li>WINDOWHEIGHT = 400</li>
<li>windowSurface = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT), 0, 32)</li>
<li>pygame.display.set_caption('Animation')</li>
<li></li>
<li># set up direction variables</li>
<li>DOWNLEFT = 1</li>
<li>DOWNRIGHT = 3</li>
<li>UPLEFT = 7</li>
<li>UPRIGHT = 9</li>
<li></li>
<li>MOVESPEED = 4</li>
<li></li>
<li># set up the colors</li>
<li>BLACK = (0, 0, 0)</li>
<li>RED = (255, 0, 0)</li>
<li>GREEN = (0, 255, 0)</li>
<li>BLUE = (0, 0, 255)</li>
<li></li>
<li># set up the block data structure</li>
<li>b1 = {'rect':pygame.Rect(300, 80, 50, 100), 'color':RED, 'dir':UPRIGHT}</li>
<li>b2 = {'rect':pygame.Rect(200, 200, 20, 20), 'color':GREEN, 'dir':UPLEFT}</li>
<li>b3 = {'rect':pygame.Rect(100, 150, 60, 60), 'color':BLUE, 'dir':DOWNLEFT}</li>
<li>blocks = [b1, b2, b3]</li>
<li></li>
<li># run the game loop</li>
<li>while True:</li>
<li> # check for the QUIT event</li>
<li> for event in pygame.event.get():</li>
<li> if event.type == QUIT:</li>
<li> pygame.quit()</li>
<li> sys.exit()</li>
<li></li>
<li> # draw the black background onto the surface</li>
<li> windowSurface.fill(BLACK)</li>
<li></li>
<li> for b in blocks:</li>
<li> # move the block data structure</li>
<li> if b['dir'] == DOWNLEFT:</li>
<li> b['rect'].left -= MOVESPEED</li>
<li> b['rect'].top += MOVESPEED</li>
<li> if b['dir'] == DOWNRIGHT:</li>
<li> b['rect'].left += MOVESPEED</li>
<li> b['rect'].top += MOVESPEED</li>
<li> if b['dir'] == UPLEFT:</li>
<li> b['rect'].left -= MOVESPEED</li>
<li> b['rect'].top -= MOVESPEED</li>
<li> if b['dir'] == UPRIGHT:</li>
<li> b['rect'].left += MOVESPEED</li>
<li> b['rect'].top -= MOVESPEED</li>
<li></li>
<li> # check if the block has move out of the window</li>
<li> if b['rect'].top < 0:</li>
<li> # block has moved past the top</li>
<li> if b['dir'] == UPLEFT:</li>
<li> b['dir'] = DOWNLEFT</li>
<li> if b['dir'] == UPRIGHT:</li>
<li> b['dir'] = DOWNRIGHT</li>
<li> if b['rect'].bottom > WINDOWHEIGHT:</li>
<li> # block has moved past the bottom</li>
<li> if b['dir'] == DOWNLEFT:</li>
<li> b['dir'] = UPLEFT</li>
<li> if b['dir'] == DOWNRIGHT:</li>
<li> b['dir'] = UPRIGHT</li>
<li> if b['rect'].left < 0:</li>
<li> # block has moved past the left side</li>
<li> if b['dir'] == DOWNLEFT:</li>
<li> b['dir'] = DOWNRIGHT</li>
<li> if b['dir'] == UPLEFT:</li>
<li> b['dir'] = UPRIGHT</li>
<li> if b['rect'].right > WINDOWWIDTH:</li>
<li> # block has moved past the right side</li>
<li> if b['dir'] == DOWNRIGHT:</li>
<li> b['dir'] = DOWNLEFT</li>
<li> if b['dir'] == UPRIGHT:</li>
<li> b['dir'] = UPLEFT</li>
<li></li>
<li> # draw the block onto the surface</li>
<li> pygame.draw.rect(windowSurface, b['color'], b['rect'])</li>
<li></li>
<li> # draw the window onto the screen</li>
<li> pygame.display.update()</li>
<li> time.sleep(0.02)</li>
</ol></div>
<p class='centeredImageP'><img src='images/17-6.png' alt='' class='centeredImage' /><br />Figure 17-6: The Animation program.
</p>
<h2 id="HowtheAnimationProgramWorks">How the Animation Program Works</h2>
<p>In this program, we will have three different colored blocks moving around and bouncing off the walls. In order to do this, we need to first consider exactly how we want the blocks to move.</p>
<h3 id="MovingandBouncingtheBlocks">Moving and Bouncing the Blocks</h3>
<p>Each block will move in one of four diagonal directions: down and left, down and right, up and left, or up and right. When the block hits the side of the window, we want it to "bounce" off the wall and move in a new diagonal direction. The blocks will bounce as shown in this picture:</p>
<p>The new direction that a block moves after it bounces depends on two things: which direction it was moving before the bounce and which wall it bounced off of. There are a total of eight possible ways a block can bounce: two different ways for each of the four walls. For example, if a block is moving down and right, and then bounces off of the bottom edge of the window, we want the block's new direction to be up and right.</p>
<p>We can represent the blocks with a <span class='m'>Rect</span> object to represent the position and size of the block, a tuple of three ints to represent the color of the block, and an integer to represent which of the four diagonal directions the block is currently moving. On each iteration in the game loop, we will adjust the X and Y position of the block in the <span class='m'>Rect</span> object. Also in each iteration we will draw all the blocks on the screen at their current position. As the program execution loops through the game loop, the blocks will gradually move across the screen so that it looks like they are smoothly moving and bouncing around on their own.</p>
<p class='centeredImageP'><img src='images/17-7.png' alt='' class='centeredImage' /><br />Figure 17-7: The diagram of how blocks will bounce.</p>
<h3 id="CreatingandSettingUpPygameandtheMainWindow">Creating and Setting Up Pygame and the Main Window</h3>
<div class='sourcecode'><ol start='1'>
<li>import pygame, sys, time</li>
</ol></div>
<p>In this program, we also want to import the <span class='m'>time</span> module.</p>
<div class='sourcecode'><ol start='7'>
<li># set up the window</li>
<li>WINDOWWIDTH = 400</li>
<li>WINDOWHEIGHT = 400</li>
<li>windowSurface = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT), 0, 32)</li>
</ol></div>
<p>In this program the size of the window's width and height is used for more than just the call to <span class='m'>set_mode()</span>. We will use a constant variables to make the program more readable. Remember, readability is for the benefit of the programmer, not the computer. If we ever want to change the size of the window, we only have to change lines 8 and 9.</p>
<p>If we did not use the constant variable, we would have to change ever occurance of the int value <span class='m'>400</span>. If any unrelated values in the program just happen to also be <span class='m'>400</span>, we might think it was for the width or height and also accidentally change it too. This would put a bug in our program. Since the window width and height never change during the program's execution, a constant variable is a good idea.</p>
<div class='sourcecode'><ol start='11'>
<li>pygame.display.set_caption('Animation')</li>
</ol></div>
<p>For this program, we will set the caption at the top of the window to <span class='m'>'Animation'</span> with a call to <span class='m'>pygame.display.set_caption()</span>.</p>
<div class='sourcecode'><ol start='13'>
<li>Setting Up Constant Variables for Direction</li>
<li># set up direction variables</li>
<li>DOWNLEFT = 1</li>
<li>DOWNRIGHT = 3</li>
<li>UPLEFT = 7</li>
<li>UPRIGHT = 9</li>
</ol></div>
<p>We will use the keys on the number pad of the keyboard to remind us which belongs to which direction. This will be similar to our Tic Tac Toe game. <span class='m'>1</span> is down and left, <span class='m'>3</span> is down and right, <span class='m'>7</span> is up and left, and <span class='m'>9</span> is up and right. However, it may be hard to remember this, so instead we will use constant variables instead of these integer values.</p>
<p>We could use any values we wanted to for these directions instead of using a constant variable, as long as we had different values for each direction. For example, we could use the string <span class='m'>'downleft'</span> to represent the down and left diagonal direction. However, if we ever mistype the <span class='m'>'downleft'</span> string (for example, as <span class='m'>'fownleft'</span>), the computer would not recognize that we meant to type <span class='m'>'downleft'</span> instead of <span class='m'>'downleft'</span>. This bug would cause our program to behave strangely.</p>
<p>But if we use constant variables, and accidentally type the variable name <span class='m'>FOWNLEFT</span> instead of the name <span class='m'>DOWNLEFT</span>, Python would notice that there is no such variable named <span class='m'>FOWNLEFT</span> and crash the program with an error. This would still be a pretty bad bug, but at least we would know immediately about it and could fix it. Otherwise it may be hard to notice that there is a bug at all.</p>
<div class='sourcecode'><ol start='19'>
<li>MOVESPEED = 4</li>
</ol></div>
<p>We will use a constant variable to determine how fast the blocks should move. A value of 4 here means that each block will move 4 pixels on each iteration through the game loop.</p>
<h3 id="SettingUpConstantVariablesforColor">Setting Up Constant Variables for Color</h3>
<div class='sourcecode'><ol start='21'>
<li># set up the colors</li>
<li>BLACK = (0, 0, 0)</li>
<li>RED = (255, 0, 0)</li>
<li>GREEN = (0, 255, 0)</li>
<li>BLUE = (0, 0, 255)</li>
</ol></div>
<p>We set up constant variables for the colors we will use. Remember, Pygame uses a tuple of three int values for the amounts of red, green, and blue called an RGB value. The integers are from <span class='m'>0</span> to <span class='m'>255</span>. Unlike our "Hello World" program, this program doesn't use the white color, so we left it out.</p>
<p>Again, the use of constant variables is for readability. The computer doesn't care if we use a variable named <span class='m'>GREEN</span> for the color green. But if we later look at this program, it is easier to know that <span class='m'>GREEN</span> stands for the color green rather than a bunch of int values in a tuple.</p>
<h3 id="SettingUpTheBlockDataStructures">Setting Up The Block Data Structures</h3>
<div class='sourcecode'><ol start='27'>
<li># set up the block data structure</li>
<li>b1 = {'rect':pygame.Rect(300, 80, 50, 100), 'color':RED, 'dir':UPRIGHT}</li>
</ol></div>
<p>We will set up a dictionary to be the data structure that represents each block. (Dictionaries were introduced at the end of the Hangman chapter.) The dictionary will have the keys of <span class='m'>'rect'</span> (with a <span class='m'>Rect</span> object for a value), <span class='m'>'color'</span> (with a tuple of three ints for a value), and <span class='m'>'dir'</span> (with one of our direction constant variables for a value).</p>
<p>We will store one of these data structures in a variable named <span class='m'>b1</span>. This block will have its top left corner located at an X-coordinate of 300 and Y-coordinate of 80. It will have a width of 50 pixels and a height of 100 pixels. Its color will be red (so we'll use our <span class='m'>RED</span> constant variable, which has the tuple <span class='m'>(255, 0, 0)</span> stored in it). And its direction will be set to <span class='m'>UPRIGHT</span>.</p>
<div class='sourcecode'><ol start='29'>
<li>b2 = {'rect':pygame.Rect(200, 200, 20, 20), 'color':GREEN, 'dir':UPLEFT}</li>
<li>b3 = {'rect':pygame.Rect(100, 150, 60, 60), 'color':BLUE, 'dir':DOWNLEFT}</li>
</ol></div>
<p>Here we create two more similar data structures for blocks that will be different sizes, positions, colors, and directions.</p>
<div class='sourcecode'><ol start='31'>
<li>blocks = [b1, b2, b3]</li>
</ol></div>
<p>On line 31 we put all of these data structures in a list, and store the list in a variable named <span class='m'>blocks</span>.</p>
<p><span class='m'>blocks</span> is a list. <span class='m'>blocks[0]</span> would be the dictionary data structure in <span class='m'>b1</span>. <span class='m'>blocks[0]['color']</span> would be the <span class='m'>'color'</span> key in <span class='m'>b1</span> (which we stored the value in <span class='m'>RED</span> in), so the expression <span class='m'>blocks[0]['color']</span> would evaluate to <span class='m'>(255, 0, 0)</span>. In this way we can refer to any of the values in any of the block data structures by starting with <span class='m'>blocks</span>.</p>
<h3 id="RunningtheGameLoop">Running the Game Loop</h3>
<div class='sourcecode'><ol start='33'>
<li># run the game loop</li>
<li>while True:</li>
</ol></div>
<p>Inside the game loop, we want to move all of the blocks around the screen in the direction that they are going, then bounce the block if they have hit a wall, then draw all of the blocks to the <span class='m'>windowSurface</span> surface, and finally call <span class='m'>pygame.display.update()</span> to draw the surface to the screen. Also, we will call <span class='m'>pygame.event.get()</span> to check if the <span class='m'>QUIT</span> event has been generated by the user closing the window.</p>
<p>The <span class='m'>for</span> loop to check all of the events in the list returned by <span class='m'>pygame.event.get()</span> is the same as in our "Hello World!" program, so we will skip its explanation and go on to line 44.</p>
<div class='sourcecode'><ol start='41'>
<li> # draw the black background onto the surface</li>
<li> windowSurface.fill(BLACK)</li>
</ol></div>
<p>Before we draw any of the blocks on the <span class='m'>windowSurface</span> surface, we want to fill the entire surface with black so that anything we previously drew on the surface is covered. Once we have blacked out the entire surface, we can redraw the blocks with the code below.</p>
<h3 id="MovingEachBlock">Moving Each Block</h3>
<div class='sourcecode'><ol start='44'>
<li> for b in blocks:</li>
</ol></div>
<p>We want to update the position of each block, so we must loop through the <span class='m'>blocks</span> list and perform the same code on each block's data structure. Inside the loop, we will refer to the current block as simply r so it will be easy to type.</p>
<div class='sourcecode'><ol start='45'>
<li> # move the block data structure</li>
<li> if b['dir'] == DOWNLEFT:</li>
<li> b['rect'].left -= MOVESPEED</li>
<li> b['rect'].top += MOVESPEED</li>
<li> if b['dir'] == DOWNRIGHT:</li>
<li> b['rect'].left += MOVESPEED</li>
<li> b['rect'].top += MOVESPEED</li>
<li> if b['dir'] == UPLEFT:</li>
<li> b['rect'].left -= MOVESPEED</li>
<li> b['rect'].top -= MOVESPEED</li>
<li> if b['dir'] == UPRIGHT:</li>
<li> b['rect'].left += MOVESPEED</li>
<li> b['rect'].top -= MOVESPEED</li>
</ol></div>
<p>The new value that we want to set the <span class='m'>left</span> and <span class='m'>top</span> attributes to depends on the direction the block is moving. Remember that the X-coordinates start at 0 on the very left edge of the window, and increase as you go right. The Y-coordinates start at 0 on the very top of the window, and increase as you go down. So if the direction of the block (which, remember, is stored in the <span class='m'>'dir'</span> key) is either <span class='m'>DOWNLEFT</span> or <span class='m'>DOWNRIGHT</span>, we want to <i>increase</i> the <span class='m'>top</span> attribute. If the direction is <span class='m'>UPLEFT</span> or <span class='m'>UPRIGHT</span>, we want to <i>decrease</i> the <span class='m'>top</span> attribute.</p>
<p>If the direction of the block is <span class='m'>DOWNRIGHT</span> or <span class='m'>UPRIGHT</span>, we want to <i>increase</i> the <span class='m'>left</span> attribute. If the direction is <span class='m'>DOWNLEFT</span> or <span class='m'>UPLEFT</span>, we want to <i>decrease</i> the <span class='m'>left</span> attribute.</p>
<p>We could have also modified <span class='m'>right</span> instead of the <span class='m'>left</span> attribute, or the <span class='m'>bottom</span> attribute instead of the <span class='m'>top</span> attribute, because Pygame will update the <span class='m'>Rect</span> object either way. Either way, we want to change the value of these attributes by the integer stored in <span class='m'>MOVESPEED</span>, which stores how many pixels over we will move the block.</p>
<h3 id="CheckingiftheBlockhasBounced">Checking if the Block has Bounced</h3>
<div class='sourcecode'><ol start='59'>
<li> # check if the block has move out of the window</li>
<li> if b['rect'].top < 0:</li>
<li> # block has moved past the top</li>
<li> if b['dir'] == UPLEFT:</li>
<li> b['dir'] = DOWNLEFT</li>
<li> if b['dir'] == UPRIGHT:</li>
<li> b['dir'] = DOWNRIGHT</li>
</ol></div>
<p>After we have moved the block, we want to check if the block has gone past the edge of the window. If it has, we want to "bounce" the block, which in the code means set a new value for the block's <span class='m'>'dir'</span> key. When the direction is set, the block will move in the new direction on the next iteration of the game loop.</p>
<p>We need to check if the block has moved passed each of the four edges of the window. In the above <span class='m'>if</span> statement, we decide the block has moved past the top edge of the window if the block's <span class='m'>Rect</span> object's <span class='m'>top</span> attribute is less than <span class='m'>0</span>. If it is, then we need to change the direction based on what direction the block was moving.</p>
<h3 id="ChangingtheDirectionoftheBouncingBlock">Changing the Direction of the Bouncing Block</h3>
<p>Look at the bouncing diagram earlier in this chapter. In order to move past the top edge of the window, the block had to either be moving in the <span class='m'>UPLEFT</span> or <span class='m'>UPRIGHT</span> directions. If the block was moving in the <span class='m'>UPLEFT</span> direction, the new direction (according to our bounce diagram) will be <span class='m'>DOWNLEFT</span>. If the block was moving in the <span class='m'>UPRIGHT</span> direction, the new direction will be <span class='m'>DOWNRIGHT</span>.</p>
<div class='sourcecode'><ol start='66'>
<li> if b['rect'].bottom > WINDOWHEIGHT:</li>
<li> # block has moved past the bottom</li>
<li> if b['dir'] == DOWNLEFT:</li>
<li> b['dir'] = UPLEFT</li>
<li> if b['dir'] == DOWNRIGHT:</li>
<li> b['dir'] = UPRIGHT</li>
</ol></div>
<p>Here we see if the block has moved past the bottom edge of the window by checking if the <span class='m'>bottom</span> attribute (not the <span class='m'>top</span> attribute) is <i>greater</i> than the value in <span class='m'>WINDOWHEIGHT</span>. Remember that the Y-coordinates start at 0 at the top of the window and increase to <span class='m'>WINDOWHEIGHT</span> because we passed <span class='m'>WINDOWHEIGHT</span> as the height in our call to <span class='m'>pygame.display.set_mode()</span>.</p>
<p>The rest of the code changes the direction based on what our bounce diagram says.</p>
<div class='sourcecode'><ol start='72'>
<li> if b['rect'].left < 0:</li>
<li> # block has moved past the left side</li>
<li> if b['dir'] == DOWNLEFT:</li>
<li> b['dir'] = DOWNRIGHT</li>
<li> if b['dir'] == UPLEFT:</li>
<li> b['dir'] = UPRIGHT</li>
</ol></div>
<p>This is similar to the above code, but checks if the left side of the block has moved to the left of the left edge of the window. Remember, the X-coordinates start at 0 on the left edge of the window and increase to <span class='m'>WINDOWWIDTH</span> on the right edge of the window.</p>
<div class='sourcecode'><ol start='78'>
<li> if b['rect'].right > WINDOWWIDTH:</li>
<li> # block has moved past the right side</li>
<li> if b['dir'] == DOWNRIGHT:</li>
<li> b['dir'] = DOWNLEFT</li>
<li> if b['dir'] == UPRIGHT:</li>
<li> b['dir'] = UPLEFT</li>
</ol></div>
<p>This code is similar to the previous pieces of code, but it checks if the block has moved past the rightmost edge of the window.</p>
<h3 id="DrawingtheBlocksontheWindowinTheirNewPositions">Drawing the Blocks on the Window in Their New Positions</h3>
<div class='sourcecode'><ol start='85'>
<li> # draw the block onto the surface</li>
<li> pygame.draw.rect(windowSurface, b['color'], b['rect'])</li>
</ol></div>
<p>Now that we have moved the block (and set a new direction if the block has bounced off the window's edges), we want to draw it on the <span class='m'>windowSurface</span> surface. We can draw this using the <span class='m'>pygame.draw.rect()</span> function. We pass <span class='m'>windowSurface</span>, because that is the <span class='m'>Surface</span> object we want to draw on. We pass the <span class='m'>b['color']</span> value, because this is the color we want to use. Then we pass <span class='m'>b['rect']</span>, because that <span class='m'>Rect</span> object has the information about the position and size of the rectangle we want to draw.</p>
<p>This is the last line of the <span class='m'>for</span> loop. We want to run the moving, bouncing, and drawing code on each of the blocks stored in the blocks list, which is why we loop through each of them. Also, if we wanted to add new blocks or remove blocks from our program, we only have to modify the blocks list and the rest of the code still works.</p>
<h3 id="DrawingtheWindowontheScreen">Drawing the Window on the Screen</h3>
<div class='sourcecode'><ol start='88'>
<li> # draw the window onto the screen</li>
<li> pygame.display.update()</li>
<li> time.sleep(0.02)</li>
</ol></div>
<p>After we have run this code on each of the <span class='m'>blocks</span> in the blocks list, we want to finally call <span class='m'>pygame.display.update()</span> so that the <span class='m'>windowSurface</span> surface is draw on the screen. After this line, we loop back to the start of the game loop and begin the process all over again. This way, the blocks are constantly moving a little, bouncing off the walls, and being drawn on the screen in their new positions. Meanwhile, we also check if the <span class='m'>QUIT</span> event has been generated by the Pygame library (which happens if the player closes the window or shuts down their computer). In that case we terminate the program.</p>
<p>The call to the <span class='m'>time.sleep()</span> function is there because the computer can move, bounce, and draw the blocks so fast that if the program ran at full speed, all the blocks would just look like a blur. (Try commenting out the <span class='m'>time.sleep(0.02)</span> line and running the program to see this.) This call to <span class='m'>time.sleep()</span> will stop the program for 20 milliseconds. There are 1000 milliseconds in a second, so 0.001 seconds equals 1 millisecond and 0.02 equals 20 milliseconds.</p>
<h2 id="SomeSmallModifications">Some Small Modifications</h2>
<h3 id="DrawingasFastasPossible">Drawing as Fast as Possible</h3>
<p>Just for fun, let's make some small modifications to our program so we can see what it does. Try adding a <span class='m'>#</span> in front of line 90 (the <span class='m'>time.sleep(0.2)</span> line) of our animation program. This will cause Python to ignore this line because it is now a comment. Now try running the program.</p>
<p>Without the <span class='m'>time.sleep()</span> function call to intentionally slow down the program, your computer will run through the game loop as fast as possible. This will make the rectangles bounce around the screen so fast, they'll only look like a blur. Now you can see why it is important for us to slow down the program with this line.</p>
<h3 id="DrawingTrailsofBlocks">Drawing Trails of Blocks</h3>
<p>Remove the <span class='m'>#</span> from the front of line 90 so that the line is no longer a comment and becomes part of the program again. This time, comment out line 42 (the <span class='m'>windowSurface.fill(BLACK)</span> line) by adding a <span class='m'>#</span> to the front of the line. Now run the program.</p>
<p>Without the call to <span class='m'>windowSurface.fill(BLACK)</span>, we do not black out the entire window before drawing the rectangles in their new position. This will cause trails of rectangles to appear on the screen instead of individual rectangles. The trails appear because all the old rectangles that are drawn in previous iterations through the game loop don't disappear.</p>
<p>Remember that the blocks are not really moving. We are just redrawing the entire window over and over again. On each iteration through the game loop, we redraw the entire window with new blocks that are located a few pixels over each time. When the program runs very fast, we make it is just one block each time. In order to see that we are just redrawing the blocks over and over again, change line 90 to <span class='m'>time.sleep(1.0)</span>. This will make the program (and the drawing) fifty times slower than normal. You will see each drawing being replaced by the next drawing every second.</p>
<h2 id="SummaryPygameProgramming">Summary: Pygame Programming</h2>
<p>This chapter has presented a whole new way of creating computer programs. Our programs before would stop and wait for the player to enter text. However, in our animation program, we are constantly updating the data structures of things without waiting for input from the player. Remember in our Hangman and Tic Tac Toe games we had data structures that would represent the state of the board, and these data structures would be passed to a <span class='m'>drawBoard()</span> function to be displayed on the screen. Our animation program is very similar. The <span class='m'>blocks</span> variable held a list of data structures representing things to be drawn to the screen, and these are drawn to the screen inside the game loop.</p>
<p>But without calls to <span class='m'>input()</span>, how do we get input from the player? In our next chapter, we will cover how our program can know when the player presses any key on the keyboard. We will also learn of a concept called collision detection, which is used in many graphical computer games.</p>
<table border='0' width='100%'><tr><td><a href='chapter16.html'>Go to Chapter 16 - AI Simulation</a></td><td align='right'><a href='chapter18.html'>Go to Chapter 18 - Collision Detection and Input</a></td></tr></table>
<div style='height: 310px;'><a href='http://www.amazon.com/Invent-Your-Computer-Games-Python/dp/0982106017/'><img src='images/buyad.png' align='right'></a></div>
</body>
</html>