Text, Buttons, & Menus
🔗 Text
We skipped over rendering text yesterday, so let’s revisit that…
Drawing text in pygame is a lot like drawing images.
'''
Rendering text
Code copied and adapted from https://inventwithpython.com/pygame/chapter2.html
'''
import sys
import pygame
pygame.init()
FPS = 60
clock = pygame.time.Clock()
screen_width = 400
screen_height = 300
display_bg_color = [255,255,255]
display_surface = pygame.display.set_mode([screen_width, screen_height])
pygame.display.set_caption('This window will have some text in it. Woo?')
# Font documentation: https://www.pygame.org/docs/ref/font.html
# use the pygame.font.get_fonts() function to get a list of available fonts on your
# system
print('Available fonts:', pygame.font.get_fonts())
# To render text with pygame, first we need to create a font
# - when we create a font, we tell it which font-style to use and what font size
# we want.
# - CHALLENGE: try changing the font size and the font type (use get_fonts() to
# figure out what is possible)
font = pygame.font.SysFont('impact', 32)
# Next, we can render the font onto a surface (which we'll eventually draw on the
# display_display surface)
# - To render text, we need to specify: (1) the string we want to write, (2) use
# anti-aliasing? (smoothing technique), (3) what color we want the text to be?,
# and (4) what background color we want? (if blank, no background color)
hello_color = [0,0,0]
hello_bg = [0,255,0]
hello_surface = font.render('Hello World!', True, hello_color, hello_bg)
# Where do we want to draw the text?
hello_x = screen_width / 2
hello_y = screen_height / 2
# CHALLENGE: add some more text to the screen, try rendering a few different fonts
# at once
goodbye_color = [255,255,255]
goodbye_bg = [0,0,0]
goodbye_surface = font.render('Goodbye forever!', True, goodbye_color, goodbye_bg)
goodbye_x = 0
goodbye_y = 0
# CHALLENGE: just like the cat animation, can you make some text move around?
moving_text_color = [0,0,0]
moving_text_surface = font.render('MOVING', True, moving_text_color)
moving_text_bounding_rect = moving_text_surface.get_rect()
moving_text_bounding_rect.x = screen_width - moving_text_surface.get_rect().width
moving_text_bounding_rect.y = 0
moving_text_dir = 'southwest'
# rot_degrees = 1
while True:
# fill the background
display_surface.fill(display_bg_color)
# blit the text onto the screen
# - The top left corner of the text should be at hello_x, hello_y
display_surface.blit(hello_surface, [hello_x, hello_y])
# blit goodbye to the screen
display_surface.blit(goodbye_surface, [goodbye_x, goodbye_y])
# Animate the moving text
if moving_text_dir == 'southwest':
moving_text_bounding_rect.x -= 1 # Move text to the left by 1
moving_text_bounding_rect.y += 1 # Move text down by 1
if (moving_text_bounding_rect.bottomleft[1] >= screen_height) or (moving_text_bounding_rect.x <= 0):
moving_text_dir = 'northeast'
elif moving_text_dir == 'northeast':
moving_text_bounding_rect.x += 1 # Move text to the right by 1
moving_text_bounding_rect.y -= 1 # Move text up by 1
if (moving_text_bounding_rect.topright[1] <= 0) or (moving_text_bounding_rect.x >= screen_width):
moving_text_dir = 'southwest'
# blit the moving text
display_surface.blit(moving_text_surface, moving_text_bounding_rect)
# Events!
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
# Update the display
pygame.display.update()
clock.tick(FPS)
|>> download text_finished.py
CHALLENGE: Make each of the three texts a different font type and font size.
🔗 Buttons
Buttons in pygame are very simple: they’re rectangles that we draw on the screen, and we detect button presses by detecting collisions between the mouse pointer and the button rectangle when the player clicks. We can also use an image as a button. You’ll need to download red-button.png into the folder containing your Python file for the code below to work.
'''
Buttons example
'''
import sys
import pygame
# Constants
SCREEN_WIDTH = 1200
SCREEN_HEIGHT = 800
FPS = 60
# colors
RED = [255, 0, 0]
BLUE = [0, 0, 255]
DARK_RED = [200, 10, 10]
BLACK = [0, 0, 0]
GREY = [230, 230, 230]
# initialize pygame
pygame.init()
screen = pygame.display.set_mode([SCREEN_WIDTH, SCREEN_HEIGHT]) # Setup the game screen surface
clock = pygame.time.Clock() # make a clock to manage FPS
font = pygame.font.SysFont('impact', 32) # Here's the font we'll use to render text
# Rect button
rect_button = pygame.Rect([10, 10], [100, 30])
rect_button_color = RED
rect_clicks = 0 # click tracker
# Image button
button_img = pygame.image.load('media/red-button.png') # Make an image surface
button_img_rect = button_img.get_rect() # Get a bounding+drawing rectangle for the surface
# (this is what we'll use to detect collisions w/mouse)
button_img_rect.x = 10 # Position the img rectangle
button_img_rect.y = 100
button_img_clicks = 0 # click tracker
# Our game loop
while True:
screen.fill(GREY)
# CHALLENGE: if mouse if hovering over the button, make the color darker/outline it
# We'll need to know the mouse position
############################################################################
# Draw the rectangle button
pygame.draw.rect(screen, rect_button_color, rect_button)
rect_clicks_surf = font.render(str(rect_clicks), True, BLACK)
screen.blit(rect_clicks_surf, [rect_button.topright[0]+10, rect_button.topright[1]-5])
############################################################################
############################################################################
# Draw the image button
screen.blit(button_img, button_img_rect)
img_button_clicks_surf = font.render(str(button_img_clicks), True, BLACK)
screen.blit(img_button_clicks_surf, [button_img_rect.topright[0]+10, button_img_rect.topright[1]-5])
############################################################################
# Event loop
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONDOWN:
# when the mouse is clicked, did the player press any buttons?
mouse_pos = pygame.mouse.get_pos()
if rect_button.collidepoint(mouse_pos):
# rectangle button was clicked
print("RECTANGLE BUTTON CLICK")
rect_clicks += 1
if button_img_rect.collidepoint(mouse_pos):
print("IMG BUTTON CLICK")
button_img_clicks += 1
pygame.display.update()
clock.tick(FPS)
|>> download buttons.py
CHALLENGE: Add another button that resets the click counts for the other two buttons.
- Extra challenge: add a button that disables the other buttons (prevents them from being clicked)
🔗 Menus
Menus are just several buttons strung together. We can make a menu screen by having multiple game modes. In the example below, we have two game modes:
- a title screen mode and
- a menu screen mode. In the menu screen, the player uses the arrow keys to navigate (we could have used the mouse here instead), and presses enter to make a selection.
'''
Menu example
'''
import sys
import random
import pygame
# Constants
FPS = 60
SCREEN_WIDTH = 1200
SCREEN_HEIGHT = 800
BTN_PADDING = 10 # How much padding are we going to put around a button?
BTN_MARGIN = 10 # How much space do we want around button text?
# Colors
WHITE = [255, 255, 255]
GREY = [175, 175, 175]
BLACK = [0, 0, 5]
YELLOW = [255, 229, 153]
DARKER_YELLOW = [255, 217, 102]
pygame.init() # As always, initialize pygame
screen = pygame.display.set_mode([SCREEN_WIDTH, SCREEN_HEIGHT])
clock = pygame.time.Clock()
menu_font = pygame.font.SysFont('impact', 32) # Here's our button font
print("Loaded the font!")
screen_mode = 'title' # Modes: title, menu
menu_btn_color = YELLOW
menu_btn_hover_color = DARKER_YELLOW
################################################################################
# Title screen components
################################################################################
title_screen_bg_color = BLACK
# === Title text ===
title_font = pygame.font.SysFont('impact', 128)
title_surface = title_font.render('THE MENU GAME', True, WHITE)
title_rect = title_surface.get_rect()
title_rect.x = (SCREEN_WIDTH / 2) - (title_rect.width / 2) # Put rect in middle of screen (perfectly in middle x)
title_rect.y = (SCREEN_HEIGHT / 2) - (title_rect.height) # Put rect in middle of the screen (sitting on top of horizontal midline)
# === Open menu button ===
menu_btn_txt_surface = menu_font.render('open menu', True, BLACK)
# setup open menu button background
menu_btn_bg_rect = menu_btn_txt_surface.get_rect()
menu_btn_bg_rect.width += 2 * BTN_MARGIN # Add some margins to the button
menu_btn_bg_rect.height += 2 * BTN_MARGIN # Add margin to the button
menu_btn_bg_rect.x = title_rect.midbottom[0] - (menu_btn_bg_rect.width / 2)
menu_btn_bg_rect.y = title_rect.midbottom[1] + BTN_PADDING
# setup text rectangle (used to determine where we'll draw text)
menu_btn_txt_rect = menu_btn_txt_surface.get_rect()
menu_btn_txt_rect.x = title_rect.midbottom[0] - (menu_btn_txt_rect.width / 2)
menu_btn_txt_rect.y = title_rect.midbottom[1] + BTN_PADDING + BTN_MARGIN
################################################################################
# Menu screen components
################################################################################
menu_screen_bg_color = GREY
menu_screen_buttons = ['resume', 'random', 'quit'] # Available buttons
cur_menu_btn_id = 0 # What button are we currently on?
btn_color = YELLOW
# === Resume button ===
# Render resume btn text onto surface
resume_btn_txt_surface = menu_font.render('Resume', True, BLACK)
# Setup resume button background
resume_btn_bg_rect = resume_btn_txt_surface.get_rect()
resume_btn_bg_rect.width += 2 * BTN_MARGIN
resume_btn_bg_rect.height += 2 * BTN_MARGIN
resume_btn_bg_rect.x = (SCREEN_WIDTH / 2) - (0.5 * resume_btn_bg_rect.width)
resume_btn_bg_rect.y = 50
# Setup the resume button text
resume_btn_txt_rect = resume_btn_txt_surface.get_rect()
resume_btn_txt_rect.x = resume_btn_bg_rect.x + BTN_MARGIN
resume_btn_txt_rect.y = resume_btn_bg_rect.y + BTN_MARGIN
# === Random button ===
# Render random btn text onto surface
random_btn_txt_surface = menu_font.render('???', True, BLACK)
# Setup random button background
random_btn_bg_rect = random_btn_txt_surface.get_rect()
random_btn_bg_rect.width += 2 * BTN_MARGIN
random_btn_bg_rect.height += 2 * BTN_MARGIN
random_btn_bg_rect.x = (SCREEN_WIDTH / 2) - (0.5 * random_btn_bg_rect.width)
random_btn_bg_rect.y = resume_btn_bg_rect.y + (resume_btn_bg_rect.height + BTN_PADDING)
# Setup the random button text
random_btn_txt_rect = random_btn_txt_surface.get_rect()
random_btn_txt_rect.x = random_btn_bg_rect.x + BTN_MARGIN
random_btn_txt_rect.y = random_btn_bg_rect.y + BTN_MARGIN
# === Quit button ===
# Render quit btn text onto surface
quit_btn_txt_surface = menu_font.render('Quit', True, BLACK)
# Setup quit button background
quit_btn_bg_rect = quit_btn_txt_surface.get_rect()
quit_btn_bg_rect.width += 2 * BTN_MARGIN
quit_btn_bg_rect.height += 2 * BTN_MARGIN
quit_btn_bg_rect.x = (SCREEN_WIDTH / 2) - (0.5 * quit_btn_bg_rect.width)
quit_btn_bg_rect.y = random_btn_bg_rect.y + (resume_btn_bg_rect.height + BTN_PADDING)
# Setup the quit button text
quit_btn_txt_rect = quit_btn_txt_surface.get_rect()
quit_btn_txt_rect.x = quit_btn_bg_rect.x + BTN_MARGIN
quit_btn_txt_rect.y = quit_btn_bg_rect.y + BTN_MARGIN
################################################################################
# Game loop
while True:
# We need to show different things depending on whether or not we're in 'title'
# or 'menu' mode
if screen_mode == 'title':
# ==== TITLE SCREEN MODE ====
screen.fill(title_screen_bg_color)
# Draw the title screen
# Render the title text in the middle of the screen
screen.blit(title_surface, title_rect)
# Draw the menu button, first: the text
pygame.draw.rect(screen, menu_btn_color, menu_btn_bg_rect)
screen.blit(menu_btn_txt_surface, menu_btn_txt_rect)
elif screen_mode == 'menu':
# === MENU SCREEN MODE ===
screen.fill(menu_screen_bg_color)
# Draw button rectangles!
# - If button is active, color background with hover color and an outline
# - otherwise, draw with normal color and no outline
if menu_screen_buttons[cur_menu_btn_id] == 'resume':
pygame.draw.rect(screen, menu_btn_hover_color, resume_btn_bg_rect)
pygame.draw.rect(screen, BLACK, resume_btn_bg_rect, 5)
else:
pygame.draw.rect(screen, menu_btn_color, resume_btn_bg_rect)
if menu_screen_buttons[cur_menu_btn_id] == 'random':
pygame.draw.rect(screen, menu_btn_hover_color, random_btn_bg_rect)
pygame.draw.rect(screen, BLACK, random_btn_bg_rect, 5)
else:
pygame.draw.rect(screen, menu_btn_color, random_btn_bg_rect)
if menu_screen_buttons[cur_menu_btn_id] == 'quit':
pygame.draw.rect(screen, menu_btn_hover_color, quit_btn_bg_rect)
pygame.draw.rect(screen, BLACK, quit_btn_bg_rect, 5)
else:
pygame.draw.rect(screen, menu_btn_color, quit_btn_bg_rect)
# Layer button text over button backgrounds
screen.blit(resume_btn_txt_surface, resume_btn_txt_rect)
screen.blit(random_btn_txt_surface, random_btn_txt_rect)
screen.blit(quit_btn_txt_surface, quit_btn_txt_rect)
else:
# ==== ???? MODE ====
print("AAAH UNRECOGNIZED SCREEN MODE! Exiting")
pygame.quit()
sys.exit()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
# ===== TITLE MODE EVENTS =====
if screen_mode == 'title' and event.type == pygame.MOUSEBUTTONDOWN:
mouse_pos = pygame.mouse.get_pos()
if menu_btn_bg_rect.collidepoint(mouse_pos):
screen_mode = 'menu'
cur_menu_btn_id = 0
# ===== MENU MODE EVENTS =====
if screen_mode == 'menu' and event.type == pygame.KEYDOWN:
if event.key == pygame.K_DOWN:
# player presses down arrow
cur_menu_btn_id = (cur_menu_btn_id + 1) % len(menu_screen_buttons)
if event.key == pygame.K_UP:
# player presses up arrow
cur_menu_btn_id = (cur_menu_btn_id - 1) % len(menu_screen_buttons)
if event.key == pygame.K_RETURN:
# Player presses return (selects current option)
if menu_screen_buttons[cur_menu_btn_id] == 'resume':
# if on resume button, go back to title screen
screen_mode = 'title'
elif menu_screen_buttons[cur_menu_btn_id] == 'random':
# if on random ('???') button, randomize background color
menu_screen_bg_color = [random.randint(0, 255),
random.randint(0, 255),
random.randint(0, 255)]
elif menu_screen_buttons[cur_menu_btn_id] == 'quit':
# if on quit button, quit the game
pygame.quit()
sys.exit()
clock.tick(FPS)
pygame.display.update()
|>> download menu.py
CHALLENGE: Add another option to the menu screen that changes the title screen’s background