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

Squirrel Eat Squirrel

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

The "Active Area"

The "active area" is just a name I came up with to describe the area of the game world that the camera views plus an area around it the size of the camera area:

Рис. 8.5.

Calculating if something is in the active area or not is explained in the isOutsideActiveArea() function’s explanation later in this chapter. When we create new enemy squirrel or grass objects, we don’t want them to be created inside the view of the camera, since it’ll appear that they just pop out of nowhere.

But we also don’t want to create them too far away from the camera, because then they may never wander into the camera’s view. Inside the active area but outside the camera is where squirrel and grass objects can safely be created.

Also, when squirrel and grass objects are beyond the border of the active area then they are far away enough to delete so that they don’t take up memory any more. Objects that far away aren’t needed since it is much less likely that they’ll come back into view of the camera.

If you have ever played Super Mario World on the Super Nintendo, there is a good YouTube video explaining how Super Mario World’s camera system works. You can find this video at http://invpy.com/mariocamera.

Keeping Track of the Location of Things in the Game World

109.     grassObjs = []    # stores all the grass objects in the game
110.     squirrelObjs = [] # stores all the non-player squirrel objects
111.     # stores the player object:
112.     playerObj = {'surface': pygame.transform.scale(L_SQUIR_IMG,
113.                  'facing': LEFT,
114.                  'size': STARTSIZE,
115.                  'x': HALF_WINWIDTH,
116.                  'y': HALF_WINHEIGHT,
117.                  'bounce':0,
118.                  'health': MAXHEALTH}
120.     moveLeft  = False
121.     moveRight = False
122.     moveUp    = False
123.     moveDown  = False

The grassObjs variable holds a list of all the grass objects in the game. As new grass objects are created, they are added to this list. As grass objects are deleted, they are removed from this list. The same goes for the squirrelObjs variable and the enemy squirrel objects.

The playerObj variable is not a list, but just the dictionary value itself.

The move variables on lines 120 to 123 track which of arrow keys (or WASD keys) are being held down, just like in a few of the previous game programs.

Starting Off with Some Grass

125.     # start off with some random grass images on the screen
126.     for i in range(10):
127.         grassObjs.append(makeNewGrass(camerax, cameray))
128.         grassObjs[i]['x'] = random.randint(0, WINWIDTH)
129.         grassObjs[i]['y'] = random.randint(0, WINHEIGHT)

The active area should start off with a few grass objects visible on the screen. The makeNewGrass() function will create and return a grass object that is randomly located somewhere in the active area but outside the camera view. This is what we normally want when we call makeNewGrass(), but since we want to make sure the first few grass objects are on the screen, the X and Y coordinates are overwritten.

The Game Loop

131.     while True: # main game loop

The game loop, like the game loops in the previous game programs, will do event handling, updating the game state, and drawing everything to the screen.

Checking to Disable Invulnerability

132.         # Check if we should turn off invulnerability
133.         if invulnerableMode and time.time() - invulnerableStartTime >
134.             invulnerableMode = False

When the player gets hit by an enemy squirrel and does not die, we make the player invulnerable for a couple seconds (since the INVULNTIME constant is set to 2). During this time, the player’s squirrel flashes and the won’t take any damage from other squirrels. If the "invulnerability mode" time is over, line 134 will set invulnerableMode to False.

Moving the Enemy Squirrels

136.         # move all the squirrels
137.         for sObj in squirrelObjs:
138.             # move the squirrel, and adjust for their bounce
139.             sObj['x'] += sObj['movex']
140.             sObj['y'] += sObj['movey']

The enemy squirrels all move according to the values in their 'movex' and 'movey' keys. If these values are positive, the squirrels move right or down. If these values are negative, they move left or up. The larger the value, the farther they move on each iteration through the game loop (which means they move faster).

The for loop on line 137 will apply this moving code to each of the enemy squirrel objects in the squirrelObjs list. First, line 139 and 140 will adjust their 'x' and 'y' keys’ values.

141.             sObj['bounce']  += 1
142.             if sObj['bounce']  > sObj['bouncerate']:
143.                 sObj['bounce'] = 0 # reset bounce amount

The value in sObj['bounce'] is incremented on each iteration of the game loop for each squirrel. When this value is 0, the squirrel is at the very beginning of its bounce. When this value is equal to the value in sObj['bouncerate'] the value is at its end. (This is why a smaller sObj['bouncerate'] value makes for a faster bounce. If sObj['bouncerate'] is 3, then it only takes three iterations through the game loop for the squirrel to do a full bounce. If sObj['bouncerate'] were 10, then it would take ten iterations.)

When sObj['bounce'] gets larger than sObj['bouncerate'], then it needs to be reset to 0. This is what lines 142 and 143 do.

145.             # random chance they change direction
146.             if random.randint(0, 99) < DIRCHANGEFREQ:
147.                 sObj['movex'] = getRandomVelocity()
148.                 sObj['movey'] = getRandomVelocity()
149.                 if sObj['movex'] > 0: # faces right
150.                     sObj['surface'] = pygame.transform.scale(R_SQUIR_IMG,
(sObj['width'], sObj['height']))
151.                 else: # faces left
152.                     sObj['surface'] = pygame.transform.scale(L_SQUIR_IMG,
(sObj['width'], sObj['height']))

There is a 2% chance on each iteration through the game loop that the squirrel will randomly change speed and direction. On line 146 the random.randint(0, 99) call randomly selects an integer out of 100 possible integers. If this number is less than DIRCHANGEFREQ (which we set to 2 on line 33) then a new value will be set for sObj['movex'] and sObj['movey'].

Because this means the squirrel might have changed direction, the Surface object in sObj['surface'] should be replaced by a new one that is properly facing left or right and scaled to the squirrel’s size. This is what lines 149 to 152 determine. Note that line 150 gets a Surface object scaled from R_SQUIR_IMG and line 152 gets one scaled from L_SQUIR_IMG.

Removing the Far Away Grass and Squirrel Objects

155.         # go through all the objects and see if any need to be deleted.
156.         for i in range(len(grassObjs) - 1, -1, -1):
157.             if isOutsideActiveArea(camerax, cameray, grassObjs[i]):
158.                 del grassObjs[i]
159.         for i in range(len(squirrelObjs) - 1, -1, -1):
160.             if isOutsideActiveArea(camerax, cameray, squirrelObjs[i]):
161.                 del squirrelObjs[i]

During each iteration of the game loop, the code will check all of the grass and enemy squirrel objects to see if they are outside the "active area". The isOutsideActiveArea() function takes the current coordinates of the camera (which are stored in camerax and cameray) and the grass/enemy squirrel object, and returns True if the object is not located in the active area.

If this is the case, this object is deleted on line 158 (for grass objects) or line 161 (for squirrel objects). This is how squirrel and grass objects get deleted when the player moves far enough away from them (or when the enemy squirrels move away far enough from the player). This ensures that there is always a number of squirrels and grass objects near the player.

When Deleting Items in a List, Iterate Over the List in Reverse

Deleting squirrel and grass objects is done with the del operator. However, notice that the for loop on line 156 and 159 pass arguments to the range() function so that the numbering starts at the index of the last item and then decrements by -1 (unlike incrementing by 1 as it normally does) until it reaches the number -1. We are iterating backwards over the list’s indexes compared to how it is normally done. This is done because we are iterating over the list that we are also deleting items from.

To see why this reverse order is needed, say we had the following list value:

animals = ['cat', 'mouse', 'dog', 'horse']

So we wanted to write code to delete any instances of the string 'dog' from this list. We might think to write out code like this:

for i in range(len(animals)):
  if animals[i] == 'dog':
    del animals[i]

But if we ran this code, we would get an IndexError error that looks like this:

Traceback (most recent call last):
File "<stdin>
", line 2, in <module>
IndexError: list index out of range

To see why this error happens, let’s walk through the code. First, the animals list would be set to ['cat', 'mouse', 'dog', 'horse'] and len(animals) would return 4. This means that the call to range(4) would cause the for loop to iterate with the values 0, 1, 2, and 3.

When the for loop iterates with i set to 2, the if statement’s condition will be True and the del animals[i] statement will delete animals[2]. This means that afterwards the animals list will be ['cat', 'mouse', 'horse']. The indexes of all the items after 'dog' are all shifted down by one because the 'dog' value was removed.

But on the next iteration through the for loop, i is set to 3. But animals[3] is out of bounds because the valid indexes of the animals list is no longer 0 to 3 but 0 to 2. The original call to range() was for a list with 4 items in it. The list changed in length, but the for loop is set up for the original length.

However, if we iterate from the last index of the list to 0, we don’t run into this problem. The following program deletes the 'dog' string from the animals list without causing an IndexError error:

animals = ['cat', 'mouse', 'dog', 'horse']
for i in range(len(animals) - 1, -1, -1):
  if animals[i] == 'dog':
    del animals[i]

The reason this code doesn’t cause an error is because the for loop iterates over 3, 2, 1, and 0. On the first iteration, the code checks if animals[3] is equal to 'dog'. It isn’t (animals[3] is 'horse') so the code moves on to the next iteration. Then animals[2] is checked if it equals 'dog'. It does, so animals[2] is deleted.

After animals[2] is deleted, the animals list is set to ['cat', 'mouse', 'horse']. On the next iteration, i is set to 1. There is a value at animals[1] (the 'mouse' value), so no error is caused. It doesn’t matter that all the items in the list after 'dog' have shifted down by one, because since we started at the end of the list and are going towards the front, all of those items have already been checked.

Similarly, we can delete grass and squirrel objects from the grassObjs and squirrelObjs lists without error because the for loop on lines 156 and 159 iterate in reverse order.

Adding New Grass and Squirrel Objects

163.         # add more grass & squirrels if we don't have enough.
164.         while len(grassObjs) < NUMGRASS:
165.             grassObjs.append(makeNewGrass(camerax, cameray))
166.         while len(squirrelObjs) < NUMSQUIRRELS:
167.             squirrelObjs.append(makeNewSquirrel(camerax, cameray))

Remember that the NUMGRASS constant was set to 80 and the NUMSQUIRRELS constant was set to 30 at the beginning of the program? These variables are set so that we can be sure there are always plenty of grass and squirrel objects in the active area at all times. If the length of the grassObjs or squirrelObjs drops below NUMGRASS or NUMSQUIRRELS respectively, then new grass and squirrel objects are created. The makeNewGrass() and makeNewSquirrel() functions that create these objects are explained later in this chapter.

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