Warning - This site is moving to https://getthecodingbug.anvil.app
Topics covered
Collisions
Bounding boxes
Colliding enemies with player
Shooting bullets
Keypress event
Spawning the bullet
Bullet collisions
Collisions
Collisions are a fundamental part of game development. Collision detection means that you want to detect whether one object in the game world is touching another object. Collision response is deciding what you want to happen when the collision occurs - does Mario pick up the coin, does Link’s sword damage the enemy, etc.
In our game we currently have a number of enemy sprites flying down the screen towards our player, and we’d like to know when one of those sprites hits. For this stage of our game, we’ll just say that an enemy hitting the player means the game is over.
Bounding boxes
Remember, each sprite in Pygame has a rect attribute that defines its coordinates and its size. A Rect object in Pygame is in the format [x, y, width, height], where x and y represent the upper left corner of the rectangle. Another word for this rectangle is bounding box, because it represents the bounds of the object.
This kind of collision detection is called AABB, which stands for “Axis Aligned Bounding Box”, because the rectangles are aligned with the screen axes - they’re not tilted at an angle. AABB collision is very popular because it’s fast - the computer can compare the coordinates of rectangles very quickly, which is very helpful if you have a large number of objects to compare.
To detect a collision we need to look at the rect of the player and compare it with the rect of each of the enemies. Now we could do this by looping through the enemies and for each one performing this comparison:
In this picture, you can see that only rectangle #3 is colliding with the large black rectangle. #1 is overlapping in the x axis, but not the y; #2 is overlapping in y, but not in x. In order for two rectangles to be overlapping, their bounds must overlap in each axis.
This can be quite complex to code, but fortunately for us, Pygame has a built-in way of doing the above, by using the "spritecollide()" function.
Colliding enemies with player
We’re going to add this command to the “update” section of our game loop:
#Update all_sprites.update() #check to see if a mob hit the player hits = pygame.sprite.spritecollide(player, enemies, False) if hits: running = False
The "spritecollide()" function takes 3 arguments: the name of the sprite you want to check, the name of a group you want to compare against, and a "True / False" parameter called dokill. The dokill parameter lets you set whether the object should be deleted when it gets hit. If, for example, we were trying to see if the player picked up a coin, we would want to set this to True so the coin would disappear.
The result of the "spritecollide()" command is a list of the sprites that were hit (remember, it’s possible the player collided with more than one enemy at a time). We’re assigning that list to the variable hits.
If the hits list is not empty, the "if" statement will be "True", and we set "running" to "False" so the game will end.
Run "main.py" and see what happens when your ship is hit by an enemy.
Shooting bullets
Now we’re ready to add a new sprite: the bullet. This will be a sprite that is spawned when we shoot, appears at the top of the player sprite, and moves upwards at some fairly high speed. Defining a sprite should be starting to look familiar to you by now, so create a new file and save it as "bullet.py", and add this code:
from common import * class Bullet(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) self.image = pygame.Surface((10, 20)) self.image.fill(YELLOW) self.rect = self.image.get_rect() self.rect.bottom = y self.rect.centerx = x self.speedy = -10 def update(self): self.rect.y += self.speedy # kill if it moves off the top of the screen if self.rect.bottom < 0: self.kill()
In the "__init__()" method of the bullet sprite, we’re passing x and y values, so that we can tell the sprite where to appear. Since the player sprite can move, this will be set to where the player is at the time the player shoots. We set speedy to a negative value, so that it will be going upward.
Finally, we check to see if the bullet has gone off the top of the screen, and if so, we delete it.
Keypress event
To keep things simple at first, we’re going to make it so that each time the player presses the spacebar, a bullet will be fired. We need to add that to the events checking in "main.py", after:
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN: if event.key == pygame.K_SPACE: player.shoot(all_sprites, bullets)
Our new code checks for a KEYDOWN event, and if there is one, checks to see if it was the K_SPACE key.
If it was, we’re going to run the player sprite’s "shoot()" method.
Spawning the bullet
First we need to tell the main program about bullets, by adding this line after "from enemy import Enemy" in "main.py":
from bullet import Bullet
bullets = pygame.sprite.Group()
from bullet import Bullet
def shoot(self, all_sprites, bullets): bullet = Bullet(self.rect.centerx, self.rect.top) all_sprites.add(bullet) bullets.add(bullet)
Now, we can add the "shoot" function to the Player class, after the end of the "def update(self):" function in "player.py":
The Player class does not yet know about bullets, either, so add this line at the top of "player.py":
Next we need to add a new group to hold all of the bullets. Add this line after "enemies.add(m)" in "main.py":
All the "shoot()" method does, is spawn a bullet, using the top centre of the player as the spawn point. Then we make sure to add the bullet to "all_sprites" (so it will be drawn and updated) and to "bullets", which we’re going to use for the collisions.
Save all your files and run "main.py" and see what happens when you press the space-bar.
Bullet collisions
Now we need to check whether a bullet hits an enemy. The difference here is we have multiple bullets (in the bullets group) and multiple enemies (in the enemies group), so we can’t use "spritecollide()" like before because that only compares one sprite against a group. Instead, we’re going to use "groupcollide()".
In "main.py", add these lines after:
# Update
all_sprites.update()
# check to see if a bullet hit an enemy hits = pygame.sprite.groupcollide(enemies, bullets, True, True) for hit in hits: m = Enemy() all_sprites.add(m) enemies.add(m)
The "groupcollide()" function is similar to "spritecollide()", except that you name two groups to compare, and what you will get back is a list of enemies that were hit. There are two dokill options, one for each of the groups.
If we just delete the enemies, we’ll have a problem: running out of enemies! So what we do is loop through hits and for each enemy that we destroy, another new one will be spawned.
Now it’s actually starting to feel like a game:
In the next section, we will convert the plain coloured rectangles into images.
Click this link to move on to the next section: Lesson 9 e