Pygame Shmup Part 9: Shields

Tags: python tutorial gamedev pygame

This is part 9 of our “Shmup” project. If you haven’t already read through the previous parts, please start with Part 1. In this lesson we’ll add a shield to the player as well as a bar to display the shield’s level.

About this series

In this series of lessons we’ll build a complete game using Python and Pygame. It’s intended for beginning programmers who already understand the basics of Python and are looking to deepen their Python understanding and learn the fundamentals of programming games.

You can watch a video version of this lesson here:

Adding shields

Right now, our player is destroyed by a single meteor hit. This is not very much fun, so we’re going to add a property to our player, shield, which will just be number from 0 - 100.

class Player(pygame.sprite.Sprite):
    def __init__(self):
        self.speedx = 0
        self.shield = 100

Now, each time the player is hit by a meteor, we can subtract a bit from the shield. When the shield reaches 0, the player is destroyed. To make things a little more interesting, we can make large meteors do more damage than tiny ones by using the meteor’s radius property.

In the next video, we’ll add some shields to the player so we don’t die so easily.

Damaging the player

Currently, our mob-versus-player collision code is very simple:

    # check to see if a mob hit the player
    hits = pygame.sprite.spritecollide(player, mobs, False, pygame.sprite.collide_circle)
    if hits:
        running = False

We need to make a few changes:

    # check to see if a mob hit the player
    hits = pygame.sprite.spritecollide(player, mobs, True, pygame.sprite.collide_circle)
    for hit in hits:
        player.shield -= hit.radius * 2
        if player.shield <= 0:
            running = False

We change the False to True in the spritecollide because we want the meteor to be deleted when it hits. If we don’t do this, then the meteor would continue to exist, and when it moves, there would be another collision the next frame, and so on. Also, it’s possible that we could hit more than one meteor at the same time, so hits can have more than one item in it. We loop through hits and deduct some shield for each hit, based on its radius. Finally, if the player’s shield reaches 0, the game will be over.

One thing you may have noticed is that since we are deleting any mob that hits the player, we are reducing the total number of mobs in the game. We’d like that to remain constant, so we need to spawn a new mob whenever we remove one. We spawned the original mobs like this:

for i in range(8):
    m = Mob()
    all_sprites.add(m)
    mobs.add(m)

We would need to do the same thing to spawn a new mob, and that’s repeating code, which is a bad thing. Instead, we’ll move the mob creation code to a function, which we can use whenever we need it:

def newmob():
    m = Mob()
    all_sprites.add(m)
    mobs.add(m)

Now we can just use that function at the beginning of the game, when we shoot a mob, and when we need to replace the mob that hit the player:

for i in range(8):
    newmob()

# check to see if a bullet hit a mob
hits = pygame.sprite.groupcollide(mobs, bullets, True, True)
for hit in hits:
    score += 50 - hit.radius
    random.choice(expl_sounds).play()
    newmob()

# check to see if a mob hit the player
hits = pygame.sprite.spritecollide(player, mobs, True, pygame.sprite.collide_circle)
for hit in hits:
    player.shield -= hit.radius * 2
    newmob()
    if player.shield <= 0:
        running = False

Shield bar

We have a value for shield, and it’s working, but it’s not very useful unless the player can see the value. We need to create a display, but rather than just show the number, we’re going to make a bar that will show how full/empty the shield is:

def draw_shield_bar(surf, x, y, pct):
    if pct < 0:
        pct = 0
    BAR_LENGTH = 100
    BAR_HEIGHT = 10
    fill = (pct / 100) * BAR_LENGTH
    outline_rect = pygame.Rect(x, y, BAR_LENGTH, BAR_HEIGHT)
    fill_rect = pygame.Rect(x, y, fill, BAR_HEIGHT)
    pygame.draw.rect(surf, GREEN, fill_rect)
    pygame.draw.rect(surf, WHITE, outline_rect, 2)

This function will work similarly to the draw_text function we defined earlier. It will draw a bar of size BAR_LENGTH by BAR_HEIGHT, located at (x, y) and filled by pct amount. We will draw two rectangles: the first will be a white outline and the second will be the green filled portion. We add a call to this function in the draw section of the game loop:

draw_text(screen, str(score), 18, WIDTH / 2, 10)
draw_shield_bar(screen, 5, 5, player.shield)

Now we can see how much shield we have left as it goes down:

Full code for this part

Part 10: Explosions

Comments