Россия |
Graphics and Animation
How the Animation Program Works
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.
Moving and Bouncing the Blocks
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:
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.
We can represent the blocks with a Rect 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 Rect 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.
Creating and Setting Up Pygame and the Main Window
1. import pygame, sys, time
In this program, we also want to import the time module.
7. # set up the window 8. WINDOWWIDTH = 400 9. WINDOWHEIGHT = 400 10. windowSurface = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT), 0, 32)
In this program the size of the window's width and height is used for more than just the call to set_mode(). 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.
If we did not use the constant variable, we would have to change ever occurance of the int value 400. If any unrelated values in the program were also 400, 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.
11. pygame.display.set_caption('Animation')
For this program, we will set the caption at the top of the window to 'Animation' with a call to pygame.display.set_caption().
13. Setting Up Constant Variables for Direction 14. # set up direction variables 15. DOWNLEFT = 1 16. DOWNRIGHT = 3 17. UPLEFT = 7 18. UPRIGHT = 9
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. 1 is down and left, 3 is down and right, 7 is up and left, and 'Animation' 9 is up and right. However, it may be hard to remember this, so instead we will use constant variables instead of these integer values.
We could use any values we wanted to for these directions, as long as we had different values for each direction. For example, we could use the string 'downleft' to represent the down and left diagonal direction. However, if we ever mistype the 'downleft' string (for example, as 'fownleft'), the computer would not recognize that we meant to type 'downleft' instead of 'fownleft'. This bug would cause our program to behave strangely.
But if we use constant variables, and accidentally type the variable name FOWNLEFT instead of the name DOWNLEFT, Python would notice that there is no such variable named FOWNLEFT 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.
19. MOVESPEED = 4
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.
Setting Up Constant Variables for Color
21. # set up the colors 22. BLACK = (0, 0, 0) 23. RED = (255, 0, 0) 24. GREEN = (0, 255, 0) 25. BLUE = (0, 0, 2 55)
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 0 to 255. Unlike our "Hello World" program, this program doesn't use the white color, so we left it out.
Again, the use of constant variables is for readability. The computer doesn't care if we use a variable named GREEN for the color green. But if we later look at this program, it is easier to know that GREEN stands for the color green rather than a bunch of int values in a tuple.
Setting Up The Block Data Structures
27. # set up the block data structure 28. b1 = {'rect' :pygame.Rect(300 , 80, 50, 100), 'color' :RED, 'dir':UPRIGHT}
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 'rect' (with a Rect object for a value), 'color' (with a tuple of three ints for a value), and 'dir' (with one of our direction constant variables for a value).
We will store one of these data structures in a variable named b1. 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 RED constant variable, which has the tuple (255, 0, 0) stored in it). And its direction will be set to UPRIGHT.
29. b2 = {'rect':pygame.Rect(200, 200, 20, 20), 'color':GREEN, 'dir':UPLEFT} 30. b3 = {'rect':pygame.Rect(10 0, 150, 60, 60), 'color':BLUE, 'dir':DOWNLEFT}
Here we create two more similar data structures for blocks that will be different sizes, positions, colors, and directions.
31. blocks = [b1, b2, b3]
On line 31 we put all of these data structures in a list, and store the list in a variable named rectangles.
rectangles is a list. rectangles[0] would be the dictionary data structure in r1. rectangles[0]['color'] would be the 'color' key in r1 (which we stored the value in RED in), so the expression rectangles[0]['color'] would evaluate to (255, 0, 0). In this way we can refer to any of the values in any of the block data structures by starting with rectangles.
Running the Game Loop
33 . # run the game loop 34. while True:
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 windowSurface surface, and finally call
pygame.display.update() to draw the surface to the screen. Also, we will call pygame.event.get() to check if the QUIT event has been generated by the user closing the window.
The for loop to check all of the events in the list returned by pygame.event.get() is the same as in our "Hello World!" program, so we will skip its explanation and go on to line 44.
41. # draw the black background onto the surface 42. windowSurface.fill(BLACK)
Before we draw any of the blocks on the windowSurface 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.
Moving Each Block
44. for b in blocks:
We want to update the position of each block, so we must loop through the rectangles 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.
45. # move the block data structure 46. if b['dir'] == DOWNLEFT: 47. b['rect'].left -= MOVESPEED 48. b [ 'rect'] .top += MOVESPEED 49. if b['dir'] == DOWNRIGHT: 50. b['rect'].left += MOVESPEED 51. b [ 'rect'] .top += MOVESPEED 52. if b['dir'] == UPLEFT: 53. b['rect'].left -= MOVESPEED 54. b [ 'rect'] .top -= MOVESPEED 55. if b['dir'] == UPRIGHT: 56. b ['rect'] .left += MOVESPEED 57. b['rect'].top -= MOVESPEED
The new value that we want to set the left and top 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 'dir' key) is either DOWNLEFT or DOWNRIGHT, we want to increase the top attribute. If the direction is UPLEFT or UPRIGHT, we want to decrease the top attribute.
If the direction of the block is DOWNRIGHT or UPRIGHT, we want to increase the left attribute. If the direction is DOWNLEFT or UPLEFT, we want to decrease the left attribute.
We could have also modified right instead of the left attribute, or the bottom attribute instead of the top attribute, because Pygame will update the Rect object either way. Either way, we want to change the value of these attributes by the integer stored in MOVESPEED, which stores how many pixels over we will move the block.
Checking if the Block has Bounced
59. # check if the block has move out of the window 60. if b ['rect'] .top < 0: 61. # block has moved past the top 62. if b['dir'] == UPLEFT: 63. b['dir'] = DOWNLEFT 64. if b['dir'] == UPRIGHT: 65. b['dir'] = DOWNRIGHT
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 'dir' key. When the direction is set, the block will move in the new direction on the next iteration of the game loop.
We need to check if the block has moved passed each of the four edges of the window. In the above if statement, we decide the block has moved past the top edge of the window if the block's Rect object's top attribute is less than 0. If it is, then we need to change the direction based on what direction the block was moving.
Changing the Direction of the Bouncing Block
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 UPLEFT or UPRIGHT directions. If the block was moving in the UPLEFT direction, the new direction (according to our bounce diagram) will be DOWNLEFT. If the block was moving in the UPRIGHT direction, the new direction will be DOWNRIGHT.
66. if b['rect'].bottom > WINDOWHEIGHT: 67. # block has moved past the bottom 68. if b['dir'] == DOWNLEFT: 69. b['dir'] = UPLEFT 70. if b['dir'] == DOWNRIGHT: 71. b['dir'] = UPRIGHT
Here we see if the block has moved past the bottom edge of the window by checking if the bottom attribute (not the top attribute) is greater than the value in WINDOWHEIGHT. Remember that the Y-coordinates start at 0 at the top of the window and increase to WINDOWHEIGHT because we passed WINDOWHEIGHT as the height in our call to pygame.display.set_mode().
The rest of the code changes the direction based on what our bounce diagram says.
72. if b ['rect'] .left < 0: 73. # block has moved past the left side 74. if b['dir'] == DOWNLEFT: 75. b['dir'] = DOWNRIGHT 76. if b['dir'] == UPLEFT: 77. b['dir'] = UPRIGHT
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 WINDOWWIDTH on the right edge of the window.
78. if b [ 'rect'] .right > WINDOWWIDTH: 79. # block has moved past the right side 80. if b['dir'] == DOWNRIGHT: 81. b['dir'] = DOWNLEFT 82. if b['dir'] == UPRIGHT: 83. b['dir'] = UPLEFT
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.
Drawing the Blocks on the Window in Their New Positions
85. # draw the block onto the surface 86. pygame.draw.rect(windowSurface, b['color'], b ['rect' ])
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 windowSurface surface. We can draw this using the pygame.draw.rect() function. We pass windowSurface, because that is the Surface object we want to draw on. We pass the b['color'] value, because this is the color we want to use. Then we pass b['rect'], because that Rect object has the information about the position and size of the rectangle we want to draw.
This is the last line of the for 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.
Drawing the Window on the Screen
88. # draw the window onto the screen 89. pygame.display.update() 90. time.sleep(0.02)
After we have run this code on each of the blocks in the blocks list, we want to finally call pygame.display.update() so that the windowSurface 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 QUIT 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.
The call to the time.sleep() 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 time.sleep(0.02) line and running the program to see this.) This call to time.sleep() 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.