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
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.
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 - self.bounds.width // 2, self.pos) 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)
Pygame can draw anything. The
pygame.draw module has functions for drawing the following shapes:
- 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.
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)
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.