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

Squirrel Eat Squirrel

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

Backwards Compatibility with Python Version 2

The reason we call float() to convert bounceRate to a floating point number is simply so that this program will work in Python version 2. In Python version 3, the division operator will evaluate to a floating point value even if both of the operands are integers, like this:

>>> # Python version 3
>>> 10 / 5
>>> 10 / 4

However, in Python version 2, the / division operator will only evaluate to a floating point value if one of the operands is also a floating point value. If both operands are integers, then Python 2’s division operator will evaluate to an integer value (rounding down if needed), like this:

>>> # Python version 2
>>> 10 / 5
>>> 10 / 4
>>> 10 / 4.0
>>> 10.0 / 4
>>> 10.0 / 4.0

But if we always convert one of the values to a floating point value with the float() function, then the division operator will evaluate to a float value no matter which version of Python runs this source code. Making these changes so that our code works with older versions of software is called backwards compatibility. It is important to maintain backwards compatibility, because not everyone will always be running the latest version of software and you want to ensure that the code you write works with as many computers as possible.

You can’t always make your Python 3 code backwards compatible with Python 2, but if it’s possible then you should do it. Otherwise, when people with Python 2 try to run your games will get error messages and think that your program is buggy.

A list of some differences between Python 2 and Python 3 can be found at http://inventwithpython.com/appendixa.html .

The getRandomVelocity() Function

335. def getRandomVelocity():
336.     speed = random.randint(SQUIRRELMINSPEED, SQUIRRELMAXSPEED)
337.     if random.randint(0, 1) == 0:
338.         return speed
339.     else:
340.         return -speed

The getRandomVelocity() function is used to randomly determine how fast an enemy squirrel will move. The range of this velocity is set in the SQUIRRELMINSPEED and SQUIRRELMAXSPEED constants, but on top of that, the speed is either negative (indicating the squirrel goes to the left or up) or positive (indicating the squirrel goes to the right or down). There is a fifty-fifty chance for the random speed to be positive or negative.

Finding a Place to Add New Squirrels and Grass

343. def getRandomOffCameraPos(camerax, cameray, objWidth, objHeight):
344.     # create a Rect of the camera view
345.     cameraRect = pygame.Rect(camerax, cameray, WINWIDTH, WINHEIGHT)
346.     while True:
347.         x = random.randint(camerax - WINWIDTH, camerax + (2 * WINWIDTH))
348.         y = random.randint(cameray - WINHEIGHT, cameray + (2 * WINHEIGHT))
349.         # create a Rect object with the random coordinates and use
350.         # to make sure the right edge isn't in the camera view.
351.         objRect = pygame.Rect(x, y, objWidth, objHeight)
352.         if not objRect.colliderect(cameraRect):
353.             return x, y

When a new squirrel or grass object is created in the game world, we want it to be within the active area (so that it is near the player’s squirrel) but not within the view of the camera (so that it doesn’t just suddenly pop into existence on the screen). To do this, we create a Rect object that represents the area of the camera (using camerax, cameray, WINWIDTH, and WINHEIGHT constants).

Next, we randomly generate numbers for the XY coordinates that would be within the active area. The active area’s left and top edge are WINWIDTH and WINHEIGHT pixels to the left and up of camerax and cameray. So the active area’s left and top edge are at camerax - WINWIDTH and cameray - WINHEIGHT. The active area’s width and height are also three times the size of the WINWIDTH and WINHEIGHT, as you can see in this image (where WINWIDTH is set to 640 pixels and WINHEIGHT set to 480 pixels):

Рис. 8.11.

This means the right and bottom edges will be at camerax + (2 * WINWIDTH) and cameray + (2 * WINHEIGHT). Line 352 will check if the random XY coordinates would collide with the camera view’s Rect object. If not, then those coordinates are returned. If so, then the while loop on line 346 will keep generating new coordinates until it finds acceptable ones.

Creating Enemy Squirrel Data Structures

356. def makeNewSquirrel(camerax, cameray):
357.     sq = {}
358.     generalSize = random.randint(5, 25)
359.     multiplier = random.randint(1, 3)
360.     sq['width']  = (generalSize + random.randint(0, 10)) * multiplier
361.     sq['height'] = (generalSize + random.randint(0, 10)) * multiplier
362.     sq['x'], sq['y'] = getRandomOffCameraPos(camerax, cameray,
sq['width'], sq['height'])
363.     sq['movex'] = getRandomVelocity()
364.     sq['movey'] = getRandomVelocity()

Creating enemy squirrel game objects is similar to making the grass game objects. The data for each enemy squirrel is also stored in a dictionary. The width and height are set to random sizes on line 360 and 361. The generalSize variable is used so that the width and height of each squirrel aren’t too different from each other. Otherwise, using completely random numbers for width and height could give us very tall and skinny squirrels or very short and wide squirrels. The width and height of the squirrel are this general size with a random number from 0 to 10 added to it (for slight variation), and then multiplied by the multiplier variable.

The original XY coordinate position of the squirrel will be a random location that the camera cannot see, to prevent the squirrels from just "popping" into existence on the screen.

The speed and direction are also randomly selected by the getRandomVelocity() function.

Flipping the Squirrel Image

365.     if sq['movex'] < 0: # squirrel is facing left
366.         sq[''] = pygame.transform.scale(L_SQUIR_IMG, (sq['width'],
367.     else: # squirrel is facing right
368.         sq[''] = pygame.transform.scale(R_SQUIR_IMG, (sq['width'],
369.     sq['bounce'] = 0
370.     sq['bouncerate'] = random.randint(10, 18)
371.     sq['bounceheight'] = random.randint(10, 50)
372.     return sq

The L_SQUIR_IMG and R_SQUIR_IMG constants contain Surface objects with left-facing and right-facing squirrel images on them. New Surface objects will be made using the pygame.transform.scale() function to match the squirrel’s width and height (stored in sq['width'] and sq['height'] respectively).

After that, the three bounce-related values are randomly generated (except for sq['bounce'] which is 0 because the squirrel always starts at the beginning of the bounce) and the dictionary is returned on line 372.

Creating Grass Data Structures

375. def makeNewGrass(camerax, cameray):
376.     gr = {}
377.     gr['grassImage'] = random.randint(0, len(GRASSIMAGES) - 1)
378.     gr['width']  = GRASSIMAGES[0].get_width()
379.     gr['height'] = GRASSIMAGES[0].get_height()
380.     gr['x'], gr['y'] = getRandomOffCameraPos(camerax, cameray,
gr['width'], gr['height'])
381.     gr['rect'] = pygame.Rect( (gr['x'], gr['y'], gr['width'],
gr['height']) )
382.     return gr

The grass game objects are dictionaries with the usual 'x', 'y', 'width', 'height', and 'rect' keys but also a 'grassImage' key which is a number from 0 to one less than the length of the GRASSIMAGES list. This number will determine what image the grass game object has. For example, if the value of the grass object’s 'grassImage' key is 3, then it will use the Surface object stored at GRASSIMAGES[3] for its image.

Checking if Outside the Active Area

385. def isOutsideActiveArea(camerax, cameray, obj):
386.     # Return False if camerax and cameray are more than
387.     # a half-window length beyond the edge of the window.
388.     boundsLeftEdge = camerax - WINWIDTH
389.     boundsTopEdge = cameray - WINHEIGHT
390.     boundsRect = pygame.Rect(boundsLeftEdge, boundsTopEdge, WINWIDTH * 3,
391.     objRect = pygame.Rect(obj['x'], obj['y'], obj['width'], obj['height'])
392.     return not boundsRect.colliderect(objRect)

The isOutsideActiveArea() will return True if the object you pass it is outside of the "active area" that is dictated by the camerax and cameray parameters. Remember that the active area is an area around the camera view the size of the camera view (which has a width and height set by WINWIDTH and WINHEIGHT), like this:

Рис. 8.12.

We can create a Rect object that represents the active area by passing camerax - WINWIDTH for the left edge value and cameray - WINHEIGHT for the top edge value, and then WINWIDTH * 3 and WINHEIGHT * 3 for the width and height. Once we have the active area represented as a Rect object, we can use the colliderect() method to determine if the object in the obj parameter is collides with (that is, is inside of) the active area Rect object.

Since the player squirrel, enemy squirrel and grass objects all have 'x', 'y', 'width' and 'height' keys, the isOutsideActiveArea() code can work with any type of those game objects.

395. if __name__ == '__main__':
396.     main()

Finally, after all the functions have been defined, the program will run the main() function and start the game.


Squirrel Eat Squirrel was our first game to have multiple enemies moving around the board at once. The key to having several enemies was using a dictionary value with identical keys for each enemy squirrel, so that the same code could be run on each of them during an iteration through the game loop.

The concept of the camera was also introduced. Cameras weren’t needed for our previous games because the entire game world fit onto one screen. However, when you make your own games that involve a player moving around a large game world, you will need code to handle converting between the game world’s coordinate system and the screen’s pixel coordinate system.

Finally, the mathematical sine function was introduced to give realistic squirrel hops (no matter how tall or long each hop was). You don’t need to know a lot of math to do programming. In most cases, just knowing addition, multiplication, and negative numbers is fine. However, if you study mathematics, you’ll often find several uses for math to make your games cooler.

For additional programming practice, you can download buggy versions of Squirrel Eat Squirrel from http://invpy.com/buggy/squirrel and try to figure out how to fix the bugs.

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