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:
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:
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