Опубликован: 06.08.2013 | Доступ: свободный | Студентов: 850 / 15 | Длительность: 27:51:00
Лекция 8:

Squirrel Eat Squirrel

< Лекция 7 || Лекция 8: 12345 || Лекция 9 >

Camera Slack, and Moving the Camera View

169.         # adjust camerax and cameray if beyond the "camera slack"
170.         playerCenterx = playerObj['x'] + int(playerObj['size'] / 2)
171.         playerCentery = playerObj['y'] + int(playerObj['size'] / 2)
172.         if (camerax + HALF_WINWIDTH) - playerCenterx > CAMERASLACK:
173.             camerax = playerCenterx + CAMERASLACK - HALF_WINWIDTH
174.         elif playerCenterx – (camerax + HALF_WINWIDTH) > CAMERASLACK:
175.             camerax = playerCenterx – CAMERASLACK - HALF_WINWIDTH
176.         if (cameray + HALF_WINHEIGHT) - playerCentery > CAMERASLACK:
177.             cameray = playerCentery + CAMERASLACK - HALF_WINHEIGHT
178.         elif playerCentery – (cameray + HALF_WINHEIGHT) > CAMERASLACK:
179.             cameray = playerCentery – CAMERASLACK - HALF_WINHEIGHT

The camera’s position (which is stored as integers in the camerax and cameray variables) needs to be updated when the player moves over. I’ve called the number of pixels the player can move before the camera gets updated the "camera slack". Line 19 set the CAMERASLACK constant to 90, which our program will take to mean that the player squirrel can move 90 pixels from the center before the camera position gets updated to follow the squirrel.

In order to understand the equations used in the if statements on lines 172, 174, 176, and 178, you should note that (camerax + HALF_WINWIDTH) and ( cameray + HALF_WINHEIGHT) are the XY game world coordinates currently at the center of the screen. The playerCenterx and playerCentery is set to the middle of the player’s squirrel’s position, also in game world coordinates.

For line 172, if the center X coordinate minus the player’s center X coordinate is greater than the CAMERASLACK value, that means the player is more pixels to the right of the center of the camera than the camera slack should allow. The camerax value needs to be updated so that the player squirrel is just at the edge of the camera slack. This is why line 173 sets camerax to playerCenterx + CAMERASLACK – HALF_WINWIDTH. Note that the camerax variable is changed, not the playerObj['x'] value. We want to move the camera, not the player.

The other three if statements follow similar logic for the left, up and down sides.

Drawing the Background, Grass, Squirrels, and Health Meter

181.         # draw the green background

Line 182 begins the code that starts drawing the contents of the display Surface object. First, line 182 draws a green color for the background. This will paint over all of the previous contents of the Surface so that we can start drawing the frame from scratch.

184.         # draw all the grass objects on the screen
185.         for gObj in grassObjs:
186.             gRect = pygame.Rect( (gObj['x'] - camerax,
187.                                   gObj['y'] - cameray,
188.                                   gObj['width'],
189.                                   gObj['height']) )
190.             DISPLAYSURF.blit(GRASSIMAGES[gObj['grassImage']], gRect)

The for loop on line 185 goes through all the grass objects in the grassObjs list and creates a Rect object from the x, y, width, and height information stored in it. This Rect object is stored in a variable named gRect. On line 190, gRect is used in the blit() method call to draw the grass image on the display Surface. Note that gObj['grassImage'] only contains an integer that is an index to GRASSIMAGES. GRASSIMAGES is a list of Surface objects that contain all the grass images. Surface objects take up much more memory than just a single integer, and all the grass objects with similar gObj['grassImage'] values look identical. So it makes sense to only have each grass image stored once in GRASSIMAGES and simply store integers in the grass objects themselves.

193.         # draw the other squirrels
194.         for sObj in squirrelObjs:
195.             sObj['rect'] = pygame.Rect( (sObj['x'] - camerax,
196.                                          sObj['y'] - cameray -
getBounceAmount(sObj['bounce'], sObj['bouncerate'], sObj['bounceheight']),
197.                                          sObj['width'],
198.                                          sObj['height']) )
199.             DISPLAYSURF.blit(sObj['surface'], sObj['rect'])

The for loop that draws all the enemy squirrel game objects is similar to the previous for loop, except that the Rect object it creates is saved in the 'rect' key’s value of the squirrel dictionary. The reason the code does this is because we will use this Rect object later to check if the enemy squirrels have collided with the player squirrel.

Note that the top parameter for the Rect constructor is not just sObj['y'] - cameray but sObj['y'] - cameray - getBounceAmount(sObj['bounce'], sObj['bouncerate'], sObj['bounceheight']). The getBounceAmount() function will return the number of pixels that the top value should be raised.

Also, there is no common list of Surface objects of the squirrel images, like there was with grass game objects and GRASSIMAGES. Each enemy squirrel game object has its own Surface object stored in the 'surface' key. This is because the squirrel images can be scaled to different sizes.

202.         # draw the player squirrel
203.         flashIsOn = round(time.time(), 1) * 10 % 2 == 1

After drawing the grass and enemy squirrels, the code will draw the player’s squirrel. However, there is one case where we would skip drawing the player’s squirrel. When the player collides with a larger enemy squirrel, the player takes damage and flashes for a little bit to indicate that the player is temporarily invulnerable. This flashing effect is done by drawing the player squirrel on some iterations through the game loop but not on others.

The player squirrel will be drawn on game loop iterations for a tenth of a second, and then not drawn on the game loop iterations for a tenth of second. This repeats over and over again as long as the player is invulnerable (which, in the code, means that the invulnerableMode variable is set to True). Our code will make the flashing last for two seconds, since 2 was stored in the INVULNTIME constant variable on line 25.

To determine if the flash is on or not, line 202 grabs the current time from time.time(). Let’s use the example where this function call returns 1323926893.622. This value is passed to round(), which rounds it to one digit past the decimal point (since 1 is passed as round()’s second parameter). This means round() will return the value 1323926893.6.

This value is then multiplied by 10, to become 13239268936. Once we have it as an integer, we can do the "mod two" trick first discussed in the Memory Puzzle chapter to see if it is even or odd. 13239268936 % 2 evaluates to 0, which means that flashIsOn will be set to False, since 0 == 1 is False.

In fact, time.time() will keep returning values that will end up putting False into flashIsOn until 1323926893.700, which is the next tenth second. This is why the flashIsOn variable will constantly have False for one tenth of a second, and then True for the next one tenth of a second (no matter how many iterations happen in that tenth of a second).

204.         if not gameOverMode and not (invulnerableMode and flashIsOn):
205.             playerObj['rect'] = pygame.Rect( (playerObj['x'] - camerax,
206.                                               playerObj['y'] – cameray -
getBounceAmount(playerObj['bounce'], BOUNCERATE, BOUNCEHEIGHT),
207.                                               playerObj['size'],
208.                                               playerObj['size']) )
209.             DISPLAYSURF.blit(playerObj['surface'], playerObj['rect'])

There are three things that must be True before we draw the player’s squirrel. The game must currently be going on (which happens while gameOverMode is False) and the player is not invulnerable and not flashing (which happens while invulnerableMode and flashIsOn are False).

The code for drawing the player’s squirrel is almost identical to the code for drawing the enemy squirrels.

212.         # draw the health meter
213.         drawHealthMeter(playerObj['health'])

The drawHealthMeter() function draws the indicator at the top left corner of the screen that tells the player how many times the player squirrel can be hit before dying. This function will be explained later in this chapter.

The Event Handling Loop

215.         for event in pygame.event.get(): # event handling loop
216.             if event.type == QUIT:
217.                 terminate()

The first thing that is checked in the event handling loop is if the QUIT event has been generated. If so, then the program should be terminated.

219.             elif event.type == KEYDOWN:
220.                 if event.key in (K_UP, K_w):
221.                     moveDown = False
222.                     moveUp = True
223.                 elif event.key in (K_DOWN, K_s):

If the up or down arrow keys have been pressed (or their WASD equivalents), then the move variable (moveRight, moveDown, etc.) for that direction should be set to True and the move variable for the opposite direction should be set to False.

226.                 elif event.key in (K_LEFT, K_a):
227.                     moveRight = False
228.                     moveLeft = True
229.                     if playerObj['facing'] == RIGHT: # change player image
230.                         playerObj['surface'] =
pygame.transform.scale(L_SQUIR_IMG, (playerObj['size'], playerObj['size']))
231.                     playerObj['facing'] = LEFT
232.                 elif event.key in (K_RIGHT, K_d):
233.                     moveLeft = False
234.                     moveRight = True
235.                     if playerObj['facing'] == LEFT: # change player image
236.                         playerObj['surface'] =
pygame.transform.scale(R_SQUIR_IMG, (playerObj['size'], playerObj['size']))
237.                     playerObj['facing'] = RIGHT

The moveLeft and moveRight variables should also be set when the left or right arrow keys are pressed. Also, the value in playerObj['facing'] should be updated to either LEFT or RIGHT. If the player squirrel is now facing a new direction, the playerObj['surface'] value should be replaced with a correctly scaled image of the squirrel facing the new direction.

Line 229 is run if the left arrow key was pressed and checks if the player squirrel was facing right. If that was so, then a new scaled Surface object of the player squirrel image is stored in playerObj['surface']. The code in line 232’s elif statement handles the opposite case.

238.                 elif winMode and event.key == K_r:
239.                     return

If the player has won the game by growing large enough (in which case, winMode will be set to True) and the R key has been pressed, then runGame()should return. This will end the current game, and a new game will start the next time that runGame() gets called.

241.             elif event.type == KEYUP:
242.                 # stop moving the player's squirrel
243.                 if event.key in (K_LEFT, K_a):
244.                     moveLeft = False
245.                 elif event.key in (K_RIGHT, K_d):
246.                     moveRight = False
247.                 elif event.key in (K_UP, K_w):
248.                     moveUp = False
249.                 elif event.key in (K_DOWN, K_s):
250.                     moveDown = False

If the player lets up on any of the arrow or WASD keys, then the code should set the move variable for that direction to False. This will stop the squirrel from moving in that direction any more.

252.                 elif event.key == K_ESCAPE:
253.                     terminate()

If the key that was pressed was the Esc key, then terminate the program.

Moving the Player, and Accounting for Bounce

255.         if not gameOverMode:
256.             # actually move the player
257.             if moveLeft:
258.                 playerObj['x'] -= MOVERATE
259.             if moveRight:
260.                 playerObj['x'] += MOVERATE
261.             if moveUp:
262.                 playerObj['y'] -= MOVERATE
263.             if moveDown:
264.                 playerObj['y'] += MOVERATE

The code inside the if statement on line 255 will move the player’s squirrel around only if the game is not over (This is why pressing on the arrow keys after the player’s squirrel dies will have no effect). Depending on which of the move variables is set to True, the playerObj dictionary should have its playerObj['x'] and playerObj['y'] values changed by MOVERATE (This is why a larger value in MOVERATE makes the squirrel move faster).

266.             if (moveLeft or moveRight or moveUp or moveDown) or
playerObj['bounce'] != 0:
267.                 playerObj['bounce'] += 1
269.             if playerObj['bounce'] > BOUNCERATE:
270.                 playerObj['bounce'] = 0 # reset bounce amount

The value in playerObj['bounce'] keeps track of at what point in bouncing the player is at. This variable stores an integer value from 0 to BOUNCERATE. Just like the bounce value for the enemy squirrels, a playerObj['bounce'] value of 0 means the player squirrel is at the start of a bounce and a value of BOUNCERATE means the player squirrel is at the end of the bounce.

The player squirrel will bounce whenever the player is moving, or if the player has stopped moving but the squirrel hasn’t finished its current bounce. This condition is captured in the if statement on line 266. If any of the move variables is set to True or the current playerObj['bounce'] is not 0 (which means the player is currently in a bounce), then the variable should be incremented on line 267.

Because the playerObj['bounce'] variable should only be in the range of 0 to BOUNCERATE, if incrementing it makes it larger than BOUNCERATE, it should be reset back to 0.

< Лекция 7 || Лекция 8: 12345 || Лекция 9 >
Константин Шилов
Константин Шилов
Павел Лафицкий
Павел Лафицкий