You might be thinking that, if we know the position and size of every sprite, we could write some code to check those against the position and size of every other sprite, to see where they overlap. But the folks who wrote Pygame have already done that for us. Pygame has what's called collision detection built in.
Collision detection simply means knowing when two sprites are touching or overlapping. When two things that are moving run into each other, it's called a collision.
Pygame also has a way of grouping sprites together. For example, in a bowling game, all the pins might be in one group, and the ball would be in a group of its own.
Groups and collision detection go hand in hand. In the bowling example, you'd want to detect when the ball hits any of the pins, so you'd look for collisions between the ball sprite and any sprites in the pins group. You can also detect collisions within a group (like the pins hitting each other).
Let's work through an example. We'll start with our bouncing beach balls, but to make it easier to see what's going on, we'll start with just four balls instead of nine. And instead of making a list of the balls like we did in the last example, we'll use Pygame's group class.
We'll also clean up the code a bit by putting the part that animates the balls (the last few lines in listing 17.2) into a function, which we'll call animate(). The animate() function will also have the code for collision detection. When two balls collide, we'll make them reverse direction.
Listing 17.3 shows the code.
Listing 17.3 Using a sprite group instead of a list import sys, pygame from random import *
def _init_(self, image_file, location, speed):
pygame.sprite.Sprite._init_(self) #call Sprite initializer self.image = pygame.image.load(image_file) self.rect = self.image.get_rect() self.rect.left, self.rect.top = location self.speed = speed
The ball class definition def move(self):
self.rect = self.rect.move(self.speed) if self.rect.left < 0 or self.rect.right > width:
self.speed = -self .speed if self.rect.top < 0 or self.rect.bottom > height: self.speed = -self.speed
screen.fill([255,255,255]) for ball in group:
Removes sprite from the group if pygame.sprite.spritecollide(ball, group, False):
Adds ball back into the group ball.move()
screen.blit(ball.image, ball.rect) pygame.display.flip() pygame.time.delay(2 0)
Checks for collisions between the sprite and the group
The main program starts here
The new animate()
Creates only four balls this time size = width, height = 640, 480 screen = pygame.display.set_mode(size) screen.fill([255, 255, 255]) img_file = "beach_ball.png" group = pygame.sprite.Group() for row in range (0, 2):
location = [column * 180 + 10, row * 180 + 10] speed = [choice([-2, 2]), choice([-2, 2])] ball = MyBallClass(img_file, location, speed) group.add(ball)
—— Adds each ball while True: to the group for event in pygame.event.get():
if event.type == pygame.QUIT: sys.exit() animate(group) -a-
Calls animate() function, passing the group to it
The most interesting new thing here is how the collision detection works. The Pygame sprite module has a function called spritecollide(), which looks for collisions between a single sprite and any sprite in a group. If you're checking for collisions between sprites in the same group, you have to do it in three steps:
■ First, you remove the sprite from the group.
■ Next, you check for collisions between the sprite and the rest of the group.
■ Finally, you add the sprite back to the group.
This happens in the for loop in lines 23 to 29 (in the middle part of the animate() function). If we don't remove the sprite from the group first, spritecollide() will detect a collision between the sprite and itself, because it's in the group. This might seem kind of odd at first, but it makes sense if you think about it for a while.
Run the program and see how it looks. Did you notice any strange behavior? I noticed two things:
■ When the balls collide, they do a "stutter" or a double bump.
■ Sometimes a ball gets "stuck" along the edge of the window and stutters along for a while.
Why does this happen? Well, it has to do with the way we wrote the animate() function. Notice that we move one ball, then we check its collisions, then we move another ball, then we check its collisions, and so on. We should probably do all the moving first, and then do all the collision checking after that.
So we want to take line 31, ball.move(), and put it in its own loop, like this:
This is easier to see if you the animation steps bigger. You can do this by
-creasing the speed fr£
¿ toJ5 and also increasing he delay between each g step from 20 to 50.
screen.fill([255,255,255]) for ball in group:
for ball in group:
Moves all the balls first if pygame.sprite.spritecollide(ball, group, False) ball.speed — -ball.speed ball.speed — -ball.speed
Then does collision detection and bounces them screen.blit(ball.image, ball.rect) pygame.display.flip() pygame.time.delay(2 0)
You can play with the code, changing things like the speed (the time.delay() number), number of balls, original location of the balls, randomness, and so on to see what happens to the balls.
Was this article helpful?
Playing bowling with your friends can help you decide if it is indeed the hobby that you want to invest your time on today. Aside from that, it can help you get a better feel of the sport. More importantly, when you play with your friends, it would become a more fun activity, which you can look forward to each week.