This is part three of a five-part series of tutorials about making games with Python 3 and Pygame. In part two, we covered the
TextObject class used to render text on the screen, created the main window, and learned how to draw objects like bricks, the ball, and the paddle.
In this part, we will dive into the heart of Breakout and learn how to handle events, meet the main Breakout class, and see how to move the different game objects.
In Breakout, there are three types of events: key press events, mouse events, and timer events. The main loop in the Game class takes care of the key press and mouse events and delivers them to subscribers (by calling a handler function).
While the Game class is very generic and has no Breakout specific knowledge, the subscription itself and how to handle each event are very specific.
The Breakout Class
The Breakout class is where most of the domain knowledge about the Breakout game is managed. We will meet the Breakout class several times during this series. Here are the lines that register the various event handlers.
Note that all key events for both the left and right arrow keys will go to the same handler method of the paddle.
# Register the handle_mouse_event() method of a button object self.mouse_handlers.append(b.handle_mouse_event) # Register the handle() method of the paddle to handle key events self.keydown_handlers[pygame.K_LEFT].append(paddle.handle) self.keydown_handlers[pygame.K_RIGHT].append(paddle.handle) self.keyup_handlers[pygame.K_LEFT].append(paddle.handle) self.keyup_handlers[pygame.K_RIGHT].append(paddle.handle)
Handling Key Presses
The Game class will call the registered handlers for each key event and pass the key. Note that it doesn't the Paddle class. In Breakout, the only object that is interested in these events is the paddle. When a key is pressed or released, its
handle() method is called.
The Paddle doesn't need to know if it was a key down or key up event because it manages the current state through a pair of boolean variables:
moving_left is True then it means the left arrow key is pressed and the next event will be key up, which will release it. The same goes for the right arrow key. The logic is as simple as toggling these variables in response to any event.
def handle(self, key): if key == pygame.K_LEFT: self.moving_left = not self.moving_left else: self.moving_right = not self.moving_right
Handling Mouse Events
Breakout has a game menu you'll meet soon. The button on the menu handles various mouse events such as movement and clicks (mouse down and mouse up events). In response to these events, the button updates an internal state variable. Here is the mouse handling code:
def handle_mouse_event(self, type, pos): if type == pygame.MOUSEMOTION: self.handle_mouse_move(pos) elif type == pygame.MOUSEBUTTONDOWN: self.handle_mouse_down(pos) elif type == pygame.MOUSEBUTTONUP: self.handle_mouse_up(pos) def handle_mouse_move(self, pos): if self.bounds.collidepoint(pos): if self.state != 'pressed': self.state = 'hover' else: self.state = 'normal' def handle_mouse_down(self, pos): if self.bounds.collidepoint(pos): self.state = 'pressed' def handle_mouse_up(self, pos): if self.state == 'pressed': self.on_click(self) self.state = 'hover'
Note that the
handle_mouse_event() method that is registered to receive mouse events checks the type of the event and forwards it to the specific method that handles that event type.
Handling Timer Events
Timer events are not processed through the main loop. However, since the main loop is called every frame, it's easy to check if a certain timed event is due. You will see an example of this later when discussing timed special effects.
Another situation is when we want to freeze the game—for example, when displaying a message that the player should be able to read without distraction. The
show_message() method of the Breakout class uses this approach and calls
time.sleep(). Here is the relevant code:
import config as c class Breakout(Game): def show_message(self, text, color=colors.WHITE, font_name='Arial', font_size=20, centralized=False): message = TextObject(c.screen_width // 2, c.screen_height // 2, lambda: text, color, font_name, font_size) self.draw() message.draw(self.surface, centralized) pygame.display.update() time.sleep(c.message_duration)
The gameplay part is where the rules of Breakout come to play (see what I did there?). Gameplay is about moving the different objects in response to the events and changing the game state based on their interactions.
Moving the Paddle
You saw earlier that the Paddle class responds to arrow keys by updating its
moving_right fields. The actual movement happens in the
update() method. There is some computation going on here if the paddle is close to the left or right edge of the screen. We don't want the paddle to move beyond the edges of the screen (including a predefined offset).
So if the movement would have taken it beyond the edge, the code adjusts the movement to stop exactly at the edge. Since the paddle is only moving horizontally, the vertical component of the movement is always zero.
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 update(self): if self.moving_left: dx = -(min(self.offset, self.left)) elif self.moving_right: dx = min(self.offset, c.screen_width - self.right) else: return self.move(dx, 0)
Moving the Ball
The ball just uses the functionality of the
GameObject base class, which moves game objects based on their speed (with horizontal and vertical components). The speed of the ball is determined by many factors in the Breakout class that you'll see soon. Since moving is just adding the speed to the current position, the direction the ball is moving is fully determined by its speed along the horizontal and vertical axes.
Setting the Initial Ball Speed
The ball in Breakout appears out of nowhere at the beginning of the game every time the player loses a life. It just materializes out of the ether and starts dropping either straight down or at a slight angle. When the ball is created in the
create_ball() method, it receives a speed with a random horizontal component between -2 and 2 and a vertical component, which is determined in the config.py module (currently set to 3).
def create_ball(self): speed = (random.randint(-2, 2), c.ball_speed) self.ball = Ball(c.screen_width // 2, c.screen_height // 2, c.ball_radius, c.ball_color, speed) self.objects.append(self.ball)
In this part, we covered event handling such as key presses, mouse movement, and mouse clicks. We also dove into some of the gameplay elements of Breakout such moving the paddle, moving the ball, and controlling the ball's speed.
Remember also to see what we have available for sale and for study in the Envato Market if you're looking to study more Python-related material.
In part four, we will deal with the important topic of collision detection and see what happens when the ball hits various game objects like the paddle, bricks, and even the walls, ceiling, and the floor.
Then we'll turn our attention to the game menu. We'll create custom buttons that we'll use as a menu we can show and hide as necessary.