Building Games With Python 3 and Pygame: Part 2

Overview

This is part two of a five-part series of tutorials about making games with Python 3 and Pygame. In part one, I introduced the series, covered the basics of game programming, introduced Pygame, and examined the game architecture. 

In this part, we’ll look at the TextObject class used to render text on the screen. We’ll create the main window, including a background image, and then we’ll learn how to draw objects like bricks, the ball, and the paddle. 

The TextObject Class

The TextObject class is designed to display text on the screen. The case could be made from a design point of view that it should be a sub-class of GameObject as it is also a visual object and you may want to move it. But I didn’t want to introduce deep class hierarchies, when all the text that Breakout displays on the screen stays put. 

The TextObject creates a font object. It renders the text into a separate text surface that is then blitted (rendered) onto the main surface. An interesting aspect of the TextObject is that it doesn’t have any fixed text. Instead, it gets a function called text_func() that is called every time it renders. 

This allows us to update the display of lives and score in Breakout just by providing a function that returns the current lives and current score, instead of keeping track of which text objects display lives and score and updating their text every time they change. This is a neat trick from functional programming, and for larger games it can help you keep everything nice and tidy. 

import pygame


class TextObject:
    def __init__(self, 
                 x, 
                 y, 
                 text_func, 
                 color, 
                 font_name, 
                 font_size):
        self.pos = (x, y)
        self.text_func = text_func
        self.color = color
        self.font = pygame.font.SysFont(font_name, font_size)
        self.bounds = self.get_surface(text_func())

    def draw(self, surface, centralized=False):
        text_surface, self.bounds = \
            self.get_surface(self.text_func())
        if centralized:
            pos = (self.pos[0] - self.bounds.width // 2,
                   self.pos[1])
        else:
            pos = self.pos
        surface.blit(text_surface, pos)

    def get_surface(self, text):
        text_surface = self.font.render(text, 
                                        False, 
                                        self.color)
        return text_surface, text_surface.get_rect()

    def update(self):
        pass

Creating the Main Window

Pygame games run in windows. You can make them run fullscreen too. Here is how you display an empty Pygame window. You can already see many of the elements I discussed earlier. First, Pygame init() is called, and then the main drawing surface and the clock are created. 

Next is the main loop, which consistently fills the screen with uniform gray and calls the clock tick() method with the frame rate.

import pygame

pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()

while True:
    screen.fill((192, 192, 192))
    pygame.display.update()
    clock.tick(60)

Using a Background Image

Usually, a uniform color background is not very exciting. Pygame does images very well. For Breakout, I splurged and went for a fancy real space image from NASA. The code is very similar. First, just before the main loop, it loads the background image using the pygame.image.load() function. Then, instead of filling the screen with color, it “blits” (copy the bits) the image to the screen at position (0,0). The effect is that the image is displayed on the screen.

import pygame

pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()
background_image = pygame.image.load('images/background.jpg')

while True:
    screen.blit(background_image, (0, 0))
    pygame.display.update()
    clock.tick(60)
Using a Background Image

Drawing Shapes

Pygame can draw anything. The pygame.draw module has functions for drawing the following shapes:

  • rect 
  • polygon
  • circle
  • ellipse
  • arc
  • line
  • lines
  • anti-aliased line 
  • anti-aliased lines

In Breakout, all the objects (except the text) are just shapes. Let’s look at the draw() method of the various Breakout objects.

Drawing Bricks

Bricks are bricks. They are just rectangles. Pygame provides the pygame.draw.rect() function, which takes a surface, a color, and a Rect object (left, top, width and height) and renders a rectangle. If the optional width parameter is greater than zero, it draws the outline. If the width is zero (which is the default), it draws a solid rectangle.

Note that the Brick class is a subclass of GameObject and gets all its properties, but it also has a color it manages itself (because there may be game objects that don’t have a single color). Ignore the special_effect field for now.

import pygame

from game_object import GameObject


class Brick(GameObject):
    def __init__(self, x, y, w, h, color, special_effect=None):
        GameObject.__init__(self, x, y, w, h)
        self.color = color
        self.special_effect = special_effect

    def draw(self, surface):
        pygame.draw.rect(surface, self.color, self.bounds)

Drawing the Ball

The ball in Breakout is just a circle. Pygame provides the pygame.draw.circle() function that takes the color, center, radius and the options width parameter that defaults to zero. As with the pygame.draw.rect() function, if the width is zero then a solid circle is drawn. The Ball is also a derived class of GameObject. 

Since the ball is always moving (unlike the bricks), it also has a speed that is passed on the GameObject base class to be managed. The Ball class has a little twist because its x and y parameters denote its center, while the x and y parameters passed to the GameObject base class are the top-left corner of the bounding box. To convert from center to top-left corner, all it takes is subtracting the radius. 

import pygame

from game_object import GameObject


class Ball(GameObject):
    def __init__(self, x, y, r, color, speed):
        GameObject.__init__(self, 
                            x - r, 
                            y - r, 
                            r * 2, 
                            r * 2, 
                            speed)
        self.radius = r
        self.diameter = r * 2
        self.color = color

    def draw(self, surface):
        pygame.draw.circle(surface, 
                           self.color, 
                           self.center, 
                           self.radius)

Drawing the Paddle

The paddle is yet another rectangle that is indeed moving left and right in response to the player’s pressing the arrow keys. That means that the position of the paddle may change from one frame to the next, but as far as drawing goes, it is just a rectangle that has to be rendered at the current position, whatever that is. Here is the relevant code:

import pygame

import config as c
from game_object import GameObject


class Paddle(GameObject):
    def __init__(self, x, y, w, h, color, offset):
        GameObject.__init__(self, x, y, w, h)
        self.color = color
        self.offset = offset
        self.moving_left = False
        self.moving_right = False

    def draw(self, surface):
        pygame.draw.rect(surface, self.color, self.bounds)

Conclusion

In this part, you’ve learned about the TextObject class and how to render text on the screen. You’ve also got familiar with drawing objects like bricks, the ball, and the paddle.

In the meantime, remember we have plenty of Python content available for sale and for study in the Envato Market.

In part three, you’ll see how event handling works and how Pygame lets you intercept and react to events like key presses, mouse movement, and mouse clicks. Then, we’ll cover gameplay topics like moving the ball, setting the ball’s speed, and moving the paddle.  

Leave a Reply

Your email address will not be published. Required fields are marked *