Home  >  Article  >  Backend Development  >  Creating interactive games with Python 3 and Pygame: Part 4

Creating interactive games with Python 3 and Pygame: Part 4

WBOY
WBOYOriginal
2023-08-27 15:01:141264browse

使用 Python 3 和 Pygame 创建互动游戏:第 4 部分

Overview

This is the fourth in a five-part tutorial series on making games with Python 3 and Pygame. In Part Three, we delved into the core of Breakout, learned how to handle events, looked at the main Breakout classes, and saw how to move different game objects.

In this part, we'll learn how to detect collisions and what happens when the ball hits various objects such as paddles, bricks, walls, ceilings, and floors. Finally, we'll review important topics of game UI, specifically how to create menus with our own custom buttons.

Impact checking

In the game, things collide with each other. Breakthrough is no exception. Most of the time it's the ball hitting something. The handle_ball_collisions() method has a nested function called intersect() that tests whether the ball hits an object and where it hits the object. If the ball misses an object, returns "Left", "Right", "Up", "Down", or "None".

def handle_ball_collisions(self):
    def intersect(obj, ball):
        edges = dict(
            left=Rect(obj.left, obj.top, 1, obj.height),
            right=Rect(obj.right, obj.top, 1, obj.height),
            top=Rect(obj.left, obj.top, obj.width, 1),
            bottom=Rect(obj.left, obj.bottom, obj.width, 1))
        collisions = set(edge for edge, rect in edges.items() if
                         ball.bounds.colliderect(rect))
        if not collisions:
            return None

        if len(collisions) == 1:
            return list(collisions)[0]

        if 'top' in collisions:
            if ball.centery >= obj.top:
                return 'top'
            if ball.centerx < obj.left:
                return 'left'
            else:
                return 'right'

        if 'bottom' in collisions:
            if ball.centery >= obj.bottom:
                return 'bottom'
            if ball.centerx < obj.left:
                return 'left'
            else:
                return 'right'

hit the ball with a racket

When the ball hits the racket, it bounces away. If it hits the top of the paddle, it will bounce back but maintain the same horizontal velocity component.

However, if it hits one side of the paddle, it will bounce to the other side (left or right) and continue its downward motion until it hits the floor. This code uses the intersect function().

# Hit paddle
s = self.ball.speed
edge = intersect(self.paddle, self.ball)
if edge is not None:
    self.sound_effects['paddle_hit'].play()
if edge == 'top':
	speed_x = s[0]
	speed_y = -s[1]
	if self.paddle.moving_left:
		speed_x -= 1
	elif self.paddle.moving_left:
		speed_x += 1
	self.ball.speed = speed_x, speed_y
elif edge in ('left', 'right'):
	self.ball.speed = (-s[0], s[1])

Landing

When the racket misses the ball on its way down (or the ball hits the side of the racket), the ball will continue to fall and eventually hit the floor. At this point, the player loses a life and the ball is recreated so the game can continue. The game ends when the player runs out of lives.

# Hit floor
if self.ball.top > c.screen_height:
    self.lives -= 1
	if self.lives == 0:
		self.game_over = True
	else:
		self.create_ball()

Hit the ceiling and walls

When the ball hits a wall or ceiling, it just bounces back.

# Hit ceiling
if self.ball.top < 0:
    self.ball.speed = (s[0], -s[1])

# Hit wall
if self.ball.left < 0 or self.ball.right > c.screen_width:
	self.ball.speed = (-s[0], s[1])

Brick Break

When the ball hits a brick, this is a major event in Breakout - the brick disappears, the player gets a point, the ball bounces back, and a few other things happen (sound effects, and possibly special effects ), which I will discuss later.

To determine if a brick was hit, the code checks to see if any bricks intersect the ball:

# Hit brick
for brick in self.bricks:
    edge = intersect(brick, self.ball)
	if not edge:
		continue

	self.bricks.remove(brick)
	self.objects.remove(brick)
	self.score += self.points_per_brick

	if edge in ('top', 'bottom'):
		self.ball.speed = (s[0], -s[1])
	else:
		self.ball.speed = (-s[0], s[1])

Programming the Game Menu

Most games have some kind of user interface. Breakout has a simple menu with two buttons, "PLAY" and "QUIT." This menu appears when the game starts and disappears when the player clicks "Start". Let's see how buttons and menus are implemented and how they integrate with the game.

Create button

Pygame does not have a built-in UI library. There are third party extensions, but I decided to build my own button for the menu. Buttons are game objects with three states: normal, hover, and pressed. The normal state is when the mouse is not over the button, and the hover state is when the mouse is over the button but the left mouse button is not pressed. The pressed state is when the mouse is over the button and the player presses the left mouse button.

The button is implemented as a rectangle with a background color and text displayed on it. The button also receives an on_click function (defaults to a noop lambda function), which is called when the button is clicked.

import pygame

from game_object import GameObject
from text_object import TextObject
import config as c


class Button(GameObject):
    def __init__(self, 
                 x, 
                 y, 
                 w, 
                 h, 
                 text, 
                 on_click=lambda x: None, 
                 padding=0):
        super().__init__(x, y, w, h)
        self.state = 'normal'
        self.on_click = on_click

        self.text = TextObject(x + padding, 
                               y + padding, lambda: text, 
                               c.button_text_color, 
                               c.font_name, 
                               c.font_size)

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

Buttons handle their own mouse events and change their internal state based on those events. When the button is pressed, the MOUSEBUTTONUP event is received, indicating that the player clicked the button, and the on_click() function is called.

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'

The back_color property used to draw the background rectangle always returns a color that matches the button's current state, so the player can clearly see that the button is active:

@property
def back_color(self):
    return dict(normal=c.button_normal_back_color,
                hover=c.button_hover_back_color,
                pressed=c.button_pressed_back_color)[self.state]

Create Menu

create_menu() Function creates a menu with two buttons containing the text "PLAY" and "QUIT". It has two nested functions named on_play() and on_quit() which are fed to the corresponding buttons. Each button is added to the objects list (to be drawn) and the menu_buttons field.

def create_menu(self):
    for i, (text, handler) in enumerate((('PLAY', on_play), 
                                         ('QUIT', on_quit))):
        b = Button(c.menu_offset_x,
                   c.menu_offset_y + (c.menu_button_h + 5) * i,
                   c.menu_button_w,
                   c.menu_button_h,
                   text,
                   handler,
                   padding=5)
        self.objects.append(b)
        self.menu_buttons.append(b)
        self.mouse_handlers.append(b.handle_mouse_event)

When the PLAY button is clicked, on_play() is called, which removes the button from the objects list so that they are no longer drawn. Additionally, the boolean fields is_game_running and start_level that trigger the start of the game are set to True.

When the exit button is clicked, is_game_running is set to False (effectively pausing the game) and game_over is set to True, triggering the final game sequence.

def on_play(button):
    for b in self.menu_buttons:
		self.objects.remove(b)

	self.is_game_running = True
	self.start_level = True

def on_quit(button):
	self.game_over = True
	self.is_game_running = False

Show and hide game menu

Showing and hiding menus is implicit. When the buttons are in the objects list, the menu is visible; when they are removed, it is hidden. It's that easy.

It's possible to create a nested menu with its own surface that renders subcomponents like buttons and then just add/remove that menu component, but this simple menu doesn't need it.

in conclusion

In this part, we cover collision detection and what happens when the ball hits various objects such as paddles, bricks, walls, ceilings, and floors. Additionally, we created our own menu with custom buttons that hide and show based on commands.

In this final part of the series, we'll look at the final game, paying close attention to scores and lives, sound effects, and music.

We will then develop a complex special effects system to add interest to the game. Finally, we discuss future directions and potential improvements.

The above is the detailed content of Creating interactive games with Python 3 and Pygame: Part 4. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn