
Today was the first snow of the winter season here in Hangzhou, China. It was so nice to see snow again.
Most space shooters rely on a way to either improve the ship’s capabilities or on random power-ups that enemy characters pick up when they are destroyed. So for our space shooter we are going to create two types of power-ups: a shield and missiles. Each will have its own set of capabilities and restrictions.
Today we will focus on creating power-ups, and creating the functions to use the bonuses during our game.
Types of power-ups
Shield: When an enemy is destroyed, there is a random chance that a shield will drop. The shield will then begin to move towards our ship and if our player collides with it, we can pick it up and earn extra points towards our score (score feature added later). A shield is displayed around our ship until our health is below 30, at which point it disappears to indicate that we are low on health.

Missile: Similar to the shield, enemy ships have a chance to drop missiles. Our ship will have missiles for 4.5 seconds, at which point they will “run out”. If the player already has collected one missile power-up, they can collect another for a total of two missile power-ups.

Let’s get started loading images and building classes for the power-ups.
CODE CAN BE FOUND HERE ON GITHUB.
Loading images
In def main( ), let’s locate and load the images for both our missile and shield images. We will use two separate images for the power-ups dropped by our enemies and for when our player uses them.
1 2 |
missile_img = pygame.image.load('images/missile.png').convert_alpha() energy_shield = pygame.image.load('images/energy_shield.png').convert_alpha() |
These missile_img and energy_shield images are used by our player when either the player fires a missile, or displays their shield power.
1 2 3 4 5 6 |
# load powerup images powerup_images = {} powerup_images['shield'] = pygame.image.load(path.join(img_dir, 'shield.png')).convert_alpha() powerup_images['shield'] = pygame.transform.scale(powerup_images['shield'], (35, 35)) powerup_images['missile'] = pygame.image.load(path.join(img_dir, 'missile_powerup.png')).convert_alpha() powerup_images['missile'] = pygame.transform.scale(powerup_images['missile'], (45, 45)) |
Here, we load and resize the images for the items dropped by the enemies and asteroids. We create a dictionary called power_images in line 2 that we will use later to determine which kind of power-up has been dropped.
In the while loop, create a group to keep track of all of the power-ups.
1 2 |
# create group for power ups powerups = pygame.sprite.Group() |
Create power-up classes
Let’s first create the Missile class which creates missile objects at the x and y location given, and updates the movement.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Missile(pygame.sprite.Sprite): '''create Missile class''' def __init__(self, image, x, y): super().__init__() self.image = image self.image = pygame.transform.scale(image, (25, 38)) self.rect = self.image.get_rect() self.rect.centerx = x self.rect.bottom = y self.speedy = -10 def update(self): '''update missiles''' self.rect.y += self.speedy if self.rect.bottom < 35: self.kill() |
The Shield class will create a shield object that will be located at the player’s center, and whenever the player moves the shield needs to update with the player’s location so it looks like it is surrounding the player.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class Shield(pygame.sprite.Sprite): '''create Shield class''' def __init__(self, image, center, player): super().__init__() self.image = pygame.transform.scale(image, (85, 85)) self.center = center self.rect = self.image.get_rect(center=(self.center)) self.player = player def update(self): '''update shield location''' self.rect.centerx = self.player.rect.centerx self.rect.centery = self.player.rect.centery if self.player.shield <= 30: self.rect.center = (WINDOWWIDTH/2, WINDOWHEIGHT + 115) elif self.player.shield > 30: self.rect.centerx = self.player.rect.centerx self.rect.centery = self.player.rect.centery |
In lines 15 – 19, if the player’s shield is below 30, the shield will disappear, but if the player picks up another shield power-up then the shield will come back if their shield is over 30.
Finally, we need to create a PowerUp class that we will use when the enemy gets destroyed to generate randomly one of the power-ups.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class PowerUp(pygame.sprite.Sprite): '''create PowerUp class''' def __init__(self, center, powerup_images): super().__init__() self.type = random.choice(['shield', 'missile']) self.image = powerup_images[self.type] self.rect = self.image.get_rect() # spawn the powerup according to current position of enemy self.rect.center = center self.speedy = 6 def update(self): '''power up movement''' self.rect.y += self.speedy # destroy sprite if we do not collect it and it moves past WINDOWHEIGHT if self.rect.top > WINDOWHEIGHT + 10: self.kill() |
In lines 5 and 6 we choose randomly between one of the power-ups in the dictionary, and then use that type of power-up. That’s it for the classes. Next, we need to update the Player class.
Player Class Updates
It’s been awhile since we updated the Player class, and there is quite a little bit we need to add. As the Player class is a bit long, I am going to omit certain parts and only show the updates. If you are interested in the whole Player class, either check out this tutorial or my GitHub.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
class Player(pygame.sprite.Sprite): '''create Player class''' def __init__(self, player_image, bullet_image, missile_image, sprites_list, bullet_list): super().__init__() ###OMITTED### # bullet attributes related to player self.bullet_image = bullet_image self.missile_image = missile_image self.bullets = bullet_list self.shoot_delay = 250 # milliseconds self.last_shot = pygame.time.get_ticks() # other player attributes self.shield = 100 self.upgrade = 1 self.upgrade_timer = pygame.time.get_ticks() def update(self): '''update the player''' # timer for upgrades if self.upgrade >= 2 and pygame.time.get_ticks() - self.upgrade_timer > 4500: self.upgrade -= 1 self.upgrade_timer = pygame.time.get_ticks() ###OMITTED### # fire weapons by holding the 'space' key if keys[pygame.K_SPACE]: self.shoot() ###OMITTED### def shoot(self): '''fire bullets''' current_time = pygame.time.get_ticks() if current_time - self.last_shot > self.shoot_delay: self.last_shot = current_time if self.upgrade == 1: bullet = Bullet(self.bullet_image, self.rect.centerx, self.rect.top) self.sprites.add(bullet) self.bullets.add(bullet) if self.upgrade == 2: bullet = Bullet(self.bullet_image, self.rect.centerx, self.rect.top) self.sprites.add(bullet) self.bullets.add(bullet) missile1 = Missile(self.missile_image, self.rect.left, self.rect.centery) self.sprites.add(missile1) self.bullets.add(missile1) if self.upgrade == 3: bullet = Bullet(self.bullet_image, self.rect.centerx, self.rect.top) self.sprites.add(bullet) self.bullets.add(bullet) missile1 = Missile(self.missile_image, self.rect.left, self.rect.centery) self.sprites.add(missile1) self.bullets.add(missile1) missile2 = Missile(self.missile_image, self.rect.right, self.rect.centery) self.sprites.add(missile2) self.bullets.add(missile2) def upgrade_power(self): if self.upgrade >= 3: self.upgrade = 3 elif self.upgrade < 3: self.upgrade += 1 self.upgrade_timer = pygame.time.get_ticks() |
In line 3, let’s add the missile_image to our Player’s arguments, and then assign it to the class in line 8. We create the shield variable and set it equal to 100.
We need a method to keep track of whether or not we have picked up any missiles. Since our missiles only last for a short period of time, we will also need to use the game timer to keep track of how long we have had them. self.upgrade in line 15 will increment by 1 if we have picked up the missile, and decrement by 1 if the timer has passed 4.5 seconds without picking up a new power-up. We check for this in lines 21 – 24.
Next, let’s update the shoot( ) function that is called in line 28 when we press the spacebar. If upgrade is still equal to 1, the player only fires bullets. If the player has collided with a missile upgrade, then upgrade equals 2 and we start shooting missiles from the left wing of the ship until the timer checks that 4.5 seconds has passed. We do a similar thing if upgrade equals 3, and we start to fire two missiles.
upgrade_power( ) in lines 58 – 63 checks to make sure we can’t get more than 2 missile upgrades.
Finally, let’s update the player object and add a shield object in the main game while loop.
1 2 3 |
player = Player(player_img, bullet_img, missile_img, all_active_sprites, bullets) shield = Shield(energy_shield, player.rect.center, player) all_active_sprites.add(player, shield) |
Collision Detection
Now, we need to check if our ship has collided with a power-up and also check if enemies were destroyed and may randomly drop an upgrade. I will keep this short and try and to only include the important parts.
Collision detection for player’s bullet and asteroids:
1 2 3 4 5 6 7 |
for hit in asteroid_hit: ###OMITTED### if random.random() > 0.92: powerup = PowerUp(hit.rect.center, powerup_images) all_active_sprites.add(powerup) powerups.add(powerup) ###OMITTED### |
If asteroid is hit, there is small chance that a power-up will be dropped. If it is dropped we add it to the all_active_sprites list and powerups list.
Collision detection for player’s bullet and enemy ships:
1 2 3 4 5 6 7 |
for hit in enemy_hit: ###OMITTED### if random.random() > 0.85: powerup = PowerUp(hit.rect.center, powerup_images) all_active_sprites.add(powerup) powerups.add(powerup) ###OMITTED### |
I just increased the chance that an item will drop if an enemy ship is destroyed.
Check if enemy bullet hit player:
1 2 3 4 5 6 |
for hit in player_hit_by_bullet: player.shield -= 5 if player.shield <= 0: expl_ship = Explosion(player.rect.center, 'ship', explosion_anim) all_active_sprites.add(expl_ship) player.shield = 100 |
Here, we begin to subtract shield from the player. As of now, it won’t do anything except subtract until shield reaches 0, then refill it.
Check for collision between asteroids and player:
1 2 3 4 5 6 7 |
for hit in player_hit: player.shield -= random.randint(10, 25) ###OMITTED### if player.shield <= 0: expl_ship = Explosion(player.rect.center, 'ship', explosion_anim) all_active_sprites.add(expl_ship) player.shield = 100 |
If a player is hit by an asteroid, we deduct a random amount between 10 and 24 from the player’s shield. We also check here if the player’s shield is less than 0.
Check for collision between enemy ships and player:
1 2 3 4 5 6 7 8 |
for hit in player_hit_by_ship: player.shield -= 35 ###OMITTED### if player.shield <= 0: ship_expl.play() expl_ship = Explosion(player.rect.center, 'ship', explosion_anim) all_active_sprites.add(expl_ship) player.shield = 100 |
In line 2, if the player is hit by an enemy ship, we subtract 35 points from the shield.
And finally the most important one, check for collision between power ups and player:
1 2 3 4 5 6 7 8 9 10 11 |
# check for collisions between player and power ups powerup_hit = pygame.sprite.spritecollide(player, powerups, True) # check if player hits power up for hit in powerup_hit: if hit.type == 'shield': player.shield += 20 if player.shield >= 100: player.shield = 100 if hit.type == 'missile': player.upgrade_power() |
In line 2, call the spritecollide function to check for collisions between the player and powerups. In lines 6 and 10, check what type of upgrade we hit. If it’s the ‘shield’, then add 20 to shield, but if the shield is already >= 100, force it to 100. If it is a ‘missile’, then call the player.upgrade_power function.
Results:

Summary
We talked about two simple kinds of power-ups that we could added to our space shooter. You could always add homing missiles, or other kinds of defense weapons if you want. We also covered how to implement them in our game.
Next time, I will discuss how to create the player window that displays our shield level, lives and the score.
Reference
Images used in this tutorial can be found at kisspng.com.