Balloon Ninja: Popping balloons

Back to Tutorial Contents

Adding a sword

In the previous section we created a balloon and made it rise to the top of the screen. Let’s add a sword that the player can use to pop balloons. To do this, we will need to create a file similar to our Balloon.py file, which will describe the behavior of the sword. Save the following, in a file called Sword.py:

import pygame
from pygame.sprite import Sprite

class Sword(Sprite):

    def __init__(self, screen):

        Sprite.__init__(self)
        self.screen = screen
        self.image = pygame.image.load('sword_75px.png').convert_alpha()
        self.image_w, self.image_h = self.image.get_size()

        self.x_position = self.screen.get_width()/2
        self.y_position = self.image_h/2

    def blitme(self):
        draw_pos = self.image.get_rect().move(self.x_position-self.image_w/2, self.y_position-self.image_h/2)
        self.screen.blit(self.image, draw_pos)

If you have understood everything in the Balloon.py file, you should be able to make pretty good sense of this file. We import the pygame Sprite module, because our sword is a sprite. We let Sword know what screen it will be drawn on, and we tell it where to find the image. We get the image width and height, and we position the sword so that it is centered at the top of the screen initially. The blitme function for Sword is identical to the Balloon blitme function; it just draws the sword image centered on the current x and y position.

I found a public domain image of a sword that should work well for our game. You can right-click and save a small version of the image right here. Save it as “sword_75px.png”, and save it in your balloon_ninja directory:

A sword for our game.

A sword for our game.

Let’s modify our game file to display the sword. Make the following changes to balloon_ninja.py:

import pygame
import sys
from Balloon import Balloon
from Sword import Sword

def run_game():
    # screen and game parameters
    screen_width, screen_height = 800, 600
    bg_color = 150, 150, 150

    # initialize game
    pygame.init()
    screen = pygame.display.set_mode( (screen_width, screen_height), 0, 32)
    clock = pygame.time.Clock()

    # Create a list to hold our balloons, and include our first balloon in the list
    balloons = [Balloon(screen)]

    # Create our sword
    sword = Sword(screen)

    # main event loop
    while True:
        time_passed = clock.tick(50)

        # Check for game events
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()

        # redraw the screen, every pass through the event loop
        screen.fill(bg_color)

        # Draw our sword on the screen
        sword.blitme()

        # Update our balloons, and draw them on the screen
        for balloon in balloons:
            balloon.update(time_passed)
            balloon.blitme()
            if balloon.y_position < -balloon.image_h/2:
                balloons.remove(balloon)
                balloons.append(Balloon(screen))

        pygame.display.flip()

run_game()

When you run this, you should see the sword appear:

A sword appears at the top of the screen.

A sword appears at the top of the screen.

Controlling the sword

Now we need to give the player a way to control the sword. We will start letting the user control the sword by responding to the mouse (or trackpad) position. Pygame has a module called mouse that has a number of useful methods. We will start with the pygame.mouse.get_pos() function, which tells us where the mouse is on our screen. Let’s put a print statement in balloon_ninja.py which will help us understand how we can use the mouse position to give our player control over the sword:

import pygame
import sys
from Balloon import Balloon
from Sword import Sword

def run_game():
    # screen and game parameters
    screen_width, screen_height = 800, 600
    bg_color = 200, 200, 200

    # initialize game
    pygame.init()
    screen = pygame.display.set_mode( (screen_width, screen_height), 0, 32)
    clock = pygame.time.Clock()

    # Create a list to hold our balloons, and include our first balloon in the list
    balloons = [Balloon(screen)]

    # Create our dagger
    sword = Sword(screen)

    # main event loop
    while True:
        time_passed = clock.tick(50)

        print pygame.mouse.get_pos()

        # Check for game events
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()

        # redraw the screen, every pass through the event loop
        screen.fill(bg_color)

        # Draw our sword on the screen
        sword.blitme()

        # Update our balloons, and draw them on the screen
        for balloon in balloons:
            balloon.update(time_passed)
            balloon.blitme()
            if balloon.y_position < -balloon.image_h/2:
                balloons.remove(balloon)
                balloons.append(Balloon(screen))

        pygame.display.flip()

run_game()

Within our event loop, we print the mouse position right after ticking our clock. (Note that we have also lightened the background, to make the sword a little more visible as well.) If we run the game and move the mouse around a bit, we can see that our game is keeping track of the mouse’s screen coordinates:

(126, 48)
(126, 48)
(115, 41)
(99, 32)
(84, 24)
(74, 21)
(68, 18)
(63, 16)
(59, 14)
(55, 13)
(50, 12)
(46, 10)
(40, 7)
(37, 5)
(35, 4)
(32, 0)
(32, 0)
(32, 0)

Note that pygame only detects mouse positions on the actual game screen. If you move the mouse off the pygame screen, the position stays constant at the point where the mouse left the screen. Let’s use the mouse position to allow the user to move the sword back and forth across the top of the screen:

import pygame
import sys
from Balloon import Balloon
from Sword import Sword

def run_game():
    # screen and game parameters
    screen_width, screen_height = 800, 600
    bg_color = 200, 200, 200

    # initialize game
    pygame.init()
    screen = pygame.display.set_mode( (screen_width, screen_height), 0, 32)
    clock = pygame.time.Clock()

    # Create a list to hold our balloons, and include our first balloon in the list
    balloons = [Balloon(screen)]

    # Create our dagger
    sword = Sword(screen)

    # main event loop
    while True:
        time_passed = clock.tick(50)

        # Check for game events
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()

        # redraw the screen, every pass through the event loop
        screen.fill(bg_color)

        # Update the sword's x-position, and draw the sword on the screen
        sword.x_position = pygame.mouse.get_pos()[0]
        sword.blitme()

        # Update our balloons, and draw them on the screen
        for balloon in balloons:
            balloon.update(time_passed)
            balloon.blitme()
            if balloon.y_position < -balloon.image_h/2:
                balloons.remove(balloon)
                balloons.append(Balloon(screen))

        pygame.display.flip()

run_game()

We can do this in one line! We use the x-value of the mouse position to update the sword’s x_position right before we draw the sword to the screen. When you run the program now, the sword moves back and forth horizontally, with your mouse movement. Note that we have also removed the print statement from the screen.

Popping balloons

Later on we will let the player “grab” the sword, but for now we need to make the balloon disappear if the sword hits it. To do this, we need to think about what game developers call “collision detection”. Basically, we want to know when our sword “collides” with a balloon. Pygame does this, in the simplest way, by thinking of objects as rectangles. So we need to define a “rect” parameter for our sprites. Let’s take care of Balloon.py first:

import pygame
from pygame.sprite import Sprite
from random import randint

class Balloon(Sprite):

    def __init__(self, screen):

        Sprite.__init__(self)
        self.screen = screen
        self.image = pygame.image.load('green_balloon_50px.png').convert_alpha()
        self.image_w, self.image_h = self.image.get_size()
        self.speed = 0.1

        self.x_position = randint(self.image_w/2, self.screen.get_width()-self.image_w/2)
        self.y_position = self.screen.get_height() + self.image_h/2

        self.update_rect()

    def update(self, time_passed):
        self.y_position -= self.speed * time_passed
        self.update_rect()

    def blitme(self):
        draw_pos = self.image.get_rect().move(self.x_position-self.image_w/2, self.y_position-self.image_h/2)
        self.screen.blit(self.image, draw_pos)

    def update_rect(self):
        self.rect = pygame.Rect(self.x_position-self.image_w/2, self.y_position-self.image_h/2,
                                self.image_w, self.image_h)

All we really want to do is give our Balloon objects a “self.rect” attribute. But we also need to make sure this rect attribute is always updated to reflect the balloon’s current position. So we create a function in lines 28-30, which defines a pygame.Rect object using the current x and y positions. Pygame Rect objects are defined by the upper left corner of the rectangular area we are interested in, and the width and height of the rectangle. Since the balloon’s x and y positions are at the center of the balloon, we need to deal with half-widths and half-heights again. The upper left of our rectangle needs the x-position minus half the balloon’s image width, and the y-position minus half the balloon’s image height.

We need to update the Balloons self.rect attribute any time a balloon is created, and any time its position changes. So we call our new update_rect function when we create a balloon, and when we update a balloon.

We need to do something similar for our Sword.py file:

import pygame
from pygame.sprite import Sprite

class Sword(Sprite):

    def __init__(self, screen):

        Sprite.__init__(self)
        self.screen = screen
        self.image = pygame.image.load('sword_75px.png').convert_alpha()
        self.image_w, self.image_h = self.image.get_size()

        self.x_position = self.screen.get_width()/2
        self.y_position = self.image_h/2

        self.update_rect()

    def blitme(self):
        draw_pos = self.image.get_rect().move(self.x_position-self.image_w/2, self.y_position-self.image_h/2)
        self.screen.blit(self.image, draw_pos)

    def update_rect(self):
        self.rect = pygame.Rect(self.x_position-self.image_w/2, self.y_position-self.image_h/2,
                                self.image_w, self.image_h)

The sword does not move on its own, so in this file we only call the update_rect function when a sword is initially created. But we will call it from within balloon_ninja.py whenever the player moves the sword with the mouse. Let’s look at the code for balloon_ninja.py. We need to update the sword’s rectangle whenever it is moved, and we need to check for collisions for each balloon:

import pygame
import sys
from Balloon import Balloon
from Sword import Sword

def run_game():
    # screen and game parameters
    screen_width, screen_height = 800, 600
    bg_color = 200, 200, 200

    # initialize game
    pygame.init()
    screen = pygame.display.set_mode( (screen_width, screen_height), 0, 32)
    clock = pygame.time.Clock()

    # Create a list to hold our balloons, and include our first balloon in the list
    balloons = [Balloon(screen)]

    # Create our dagger
    sword = Sword(screen)

    # main event loop
    while True:
        time_passed = clock.tick(50)

        # Check for game events
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()

        # redraw the screen, every pass through the event loop
        screen.fill(bg_color)

        # Update the sword's x-position, and draw the sword on the screen
        sword.x_position = pygame.mouse.get_pos()[0]
        sword.update_rect()
        sword.blitme()

        # Update our balloons, and draw them on the screen
        for balloon in balloons:
            balloon.update(time_passed)

            if balloon.rect.colliderect(sword.rect):
                balloons.remove(balloon)
                balloons.append(Balloon(screen))
                continue

            if balloon.y_position < -balloon.image_h/2:
                balloons.remove(balloon)
                balloons.append(Balloon(screen))
                continue

            balloon.blitme()

        pygame.display.flip()

run_game()

In line 36, we update the sword.rect attribute after we move the sword. In lines 43-46, we check whether the current balloon is in contact with the sword. Pygame does this by checking to see if the balloon’s rectangle has collided with the sword’s rectangle, in line 43. If it has we remove the balloon from the list, just like we did earlier when a balloon disappeared off the top of the screen. We then create a new balloon and continue at the beginning of the loop, because there is no need to redraw a balloon that has been removed from the game. We will add a scoring function to this test a little later.

We make one other slight adjustment at this point. We move the call to blitme after our two tests, and add a continue statement to the code testing whether a balloon has disappeared off the top of the screen. We only call the blitme function if the balloon has not popped or disappeared off the top of the screen.

You may notice, if you watch carefully, that using the pygame.rect attribute creates slightly inaccurate behavior. The balloon is a round image, but it is represented by a rectangle. So you can pop a balloon by bringing the tip of the sword near the balloon, without actually touching the balloon. There are more advanced ways to use pygame, where this does not happen. The effect is hardly noticeable, though, and for many games we are fine to just go on using the sprite.rect.colliderect function.

Grabbing the sword

The next section will focus on game logic, but let’s add one more feature in this section. Let’s give the player the ability to “grab” the sword, and sweep it across the screen to pop a bunch of balloons. We will do this by checking to see whether the user has clicked on the sword. If so, we will draw the sword wherever the mouse is, as long as the mouse button is pressed. When the player lets go, the sword will return to the top of the screen. To do this, we first need to modify Sword.py:

import pygame
from pygame.sprite import Sprite

class Sword(Sprite):

    def __init__(self, screen):

        Sprite.__init__(self)
        self.screen = screen
        self.image = pygame.image.load('sword_75px.png').convert_alpha()
        self.image_w, self.image_h = self.image.get_size()

        self.x_position = self.screen.get_width()/2
        self.y_position = self.image_h/2

        self.grabbed = False

        self.update_rect()

    def blitme(self):
        draw_pos = self.image.get_rect().move(self.x_position-self.image_w/2, self.y_position-self.image_h/2)
        self.screen.blit(self.image, draw_pos)

    def update_rect(self):
        self.rect = pygame.Rect(self.x_position-self.image_w/2, self.y_position-self.image_h/2,
                                self.image_w, self.image_h)

We have added one line, an attribute to keep track of whether the sword has been grabbed or not. It is false by default

Now we need to modify the balloon_ninja file to use this attribute effectively:

import pygame
import sys
from Balloon import Balloon
from Sword import Sword

def run_game():
    # screen and game parameters
    screen_width, screen_height = 800, 600
    bg_color = 200, 200, 200

    # initialize game
    pygame.init()
    screen = pygame.display.set_mode( (screen_width, screen_height), 0, 32)
    clock = pygame.time.Clock()

    # Create a list to hold our balloons, and include our first balloon in the list
    balloons = [Balloon(screen)]

    # Create our dagger
    sword = Sword(screen)

    # main event loop
    while True:
        time_passed = clock.tick(50)
        mouse_x, mouse_y = pygame.mouse.get_pos()[0], pygame.mouse.get_pos()[1]

        # Check for game events
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
            if event.type == pygame.MOUSEBUTTONDOWN:
                if sword.rect.collidepoint(mouse_x, mouse_y):
                    sword.grabbed = True
            if event.type == pygame.MOUSEBUTTONUP:
                sword.grabbed = False

        # redraw the screen, every pass through the event loop
        screen.fill(bg_color)

        # Update the sword's position, and draw the sword on the screen
        sword.x_position = mouse_x
        if sword.grabbed:
            sword.y_position = mouse_y
        else:
            sword.y_position = sword.image_h/2
        sword.update_rect()
        sword.blitme()

        # Update our balloons, and draw them on the screen
        for balloon in balloons:
            balloon.update(time_passed)

            if balloon.rect.colliderect(sword.rect):
                balloons.remove(balloon)
                balloons.append(Balloon(screen))
                continue

            if balloon.y_position < -balloon.image_h/2:
                balloons.remove(balloon)
                balloons.append(Balloon(screen))
                continue

            balloon.blitme()

        pygame.display.flip()

run_game()

In line 25 we get the mouse’s x and y coordinates, since we will be using both now. Starting on line 31, we watch for mouse events. When the mouse button is pressed, the MOUSEBUTTONDOWN event is fired. When this happens, we check to see if the mouse position has collided with the sword’s rect object (line 32). If it has, we set the sword’s “grabbed” attribute to True. In lines 34 and 35, we set the grabbed attribute back to False as soon as the mouse button is released.

The sword’s x-position is always controlled by the mouse, so line 41 is always executed. But the sword’s y-position depends on whether the sword is grabbed or not. If it is, then it’s y-position corresponds with the mouse position. If it is not grabbed, the sword is positioned at the top of the screen.

When you run the game now, you should be able to grab the sword and swish it around at the balloons that rise up.

Cleaning our code

To wrap up this section, we will clean up our main file a bit. Let’s clean up our imports, and moving our event-checking to a separate function:

import pygame, sys
from Balloon import Balloon
from Sword import Sword

def run_game():
    # screen and game parameters
    screen_width, screen_height = 800, 600
    bg_color = 200, 200, 200

    # initialize game
    pygame.init()
    screen = pygame.display.set_mode( (screen_width, screen_height), 0, 32)
    clock = pygame.time.Clock()

    # Create a list to hold our balloons, and include our first balloon in the list
    balloons = [Balloon(screen)]

    # Create our dagger
    sword = Sword(screen)

    # main event loop
    while True:
        time_passed = clock.tick(50)
        mouse_x, mouse_y = pygame.mouse.get_pos()[0], pygame.mouse.get_pos()[1]

        # Check for game events
        check_events(sword, mouse_x, mouse_y)

        # redraw the screen, every pass through the event loop
        screen.fill(bg_color)

        # Update the sword's position, and draw the sword on the screen
        sword.x_position = mouse_x
        if sword.grabbed:
            sword.y_position = mouse_y
        else:
            sword.y_position = sword.image_h/2
        sword.update_rect()
        sword.blitme()

        # Update our balloons, and draw them on the screen
        for balloon in balloons:
            balloon.update(time_passed)

            if balloon.rect.colliderect(sword.rect):
                balloons.remove(balloon)
                balloons.append(Balloon(screen))
                continue

            if balloon.y_position < -balloon.image_h/2:
                balloons.remove(balloon)
                balloons.append(Balloon(screen))
                continue

            balloon.blitme()

        pygame.display.flip()

def check_events(sword, mouse_x, mouse_y):
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
        if event.type == pygame.MOUSEBUTTONDOWN:
            if sword.rect.collidepoint(mouse_x, mouse_y):
                sword.grabbed = True
        if event.type == pygame.MOUSEBUTTONUP:
            sword.grabbed = False

run_game()

In line 1, we consolidate some of our imports. In line 27, we call our new check_events function. In lines 59-67, we define our check_events function. We will do more of this kind of cleanup before adding more game logic, but first let’s add a scoreboard to the game.

Next: Adding a scoreboard

Back to Tutorial Contents

About ehmatthes

I'm a writer and a programmer, and I like to spend time outside.
This entry was posted in programming and tagged , , , , , . Bookmark the permalink.

Leave a comment