Last active
August 23, 2022 04:35
-
-
Save programmingpixels/27b7f8f59ec53b401183c68f4be1634b to your computer and use it in GitHub Desktop.
A gist for the blog post https://programmingpixels.com/handling-a-title-screen-game-flow-and-buttons-in-pygame.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import pygame | |
import pygame.freetype | |
from pygame.sprite import Sprite | |
from pygame.rect import Rect | |
BLUE = (106, 159, 181) | |
WHITE = (255, 255, 255) | |
def create_surface_with_text(text, font_size, text_rgb, bg_rgb): | |
""" Returns surface with text written on """ | |
font = pygame.freetype.SysFont("Courier", font_size, bold=True) | |
surface, _ = font.render(text=text, fgcolor=text_rgb, bgcolor=bg_rgb) | |
return surface.convert_alpha() | |
class UIElement(Sprite): | |
""" An user interface element that can be added to a surface """ | |
def __init__(self, center_position, text, font_size, bg_rgb, text_rgb): | |
""" | |
Args: | |
center_position - tuple (x, y) | |
text - string of text to write | |
font_size - int | |
bg_rgb (background colour) - tuple (r, g, b) | |
text_rgb (text colour) - tuple (r, g, b) | |
""" | |
self.mouse_over = False # indicates if the mouse over the element | |
# create the default image | |
default_image = create_surface_with_text( | |
text=text, font_size=font_size, text_rgb=text_rgb, bg_rgb=bg_rgb | |
) | |
# create the image that shows when mouse is over the element | |
highlighted_image = create_surface_with_text( | |
text=text, font_size=font_size * 1.2, text_rgb=text_rgb, bg_rgb=bg_rgb | |
) | |
# add both images and their rects to lists | |
self.images = [default_image, highlighted_image] | |
self.rects = [ | |
default_image.get_rect(center=center_position), | |
highlighted_image.get_rect(center=center_position), | |
] | |
# calls the init method of the parent sprite class | |
super().__init__() | |
# properties that vary the image and its rect when the mouse is over the element | |
@property | |
def image(self): | |
return self.images[1] if self.mouse_over else self.images[0] | |
@property | |
def rect(self): | |
return self.rects[1] if self.mouse_over else self.rects[0] | |
def update(self, mouse_pos): | |
if self.rect.collidepoint(mouse_pos): | |
self.mouse_over = True | |
else: | |
self.mouse_over = False | |
def draw(self, surface): | |
""" Draws element onto a surface """ | |
surface.blit(self.image, self.rect) | |
def main(): | |
pygame.init() | |
screen = pygame.display.set_mode((800, 600)) | |
# create a ui element | |
uielement = UIElement( | |
center_position=(400, 400), | |
font_size=30, | |
bg_rgb=BLUE, | |
text_rgb=WHITE, | |
text="Hello World", | |
) | |
# main loop | |
while True: | |
for event in pygame.event.get(): | |
pass | |
screen.fill(BLUE) | |
uielement.update(pygame.mouse.get_pos()) | |
uielement.draw(screen) | |
pygame.display.flip() | |
# call main when the script is run | |
if __name__ == "__main__": | |
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import pygame | |
import pygame.freetype | |
from pygame.sprite import Sprite | |
from pygame.rect import Rect | |
BLUE = (106, 159, 181) | |
WHITE = (255, 255, 255) | |
def create_surface_with_text(text, font_size, text_rgb): | |
""" Returns surface with text written on """ | |
font = pygame.freetype.SysFont("Courier", font_size, bold=True) | |
surface, _ = font.render(text=text, fgcolor=text_rgb) | |
return surface.convert_alpha() | |
class UIElement(Sprite): | |
""" An user interface element that can be added to a surface """ | |
def __init__(self, center_position, text, font_size, text_rgb): | |
""" | |
Args: | |
center_position - tuple (x, y) | |
text - string of text to write | |
font_size - int | |
text_rgb (text colour) - tuple (r, g, b) | |
""" | |
self.mouse_over = False # indicates if the mouse over the element | |
# create the default image | |
default_image = create_surface_with_text( | |
text=text, font_size=font_size, text_rgb=text_rgb | |
) | |
# create the image that shows when mouse is over the element | |
highlighted_image = create_surface_with_text( | |
text=text, font_size=font_size * 1.2, text_rgb=text_rgb | |
) | |
# add both images and their rects to lists | |
self.images = [default_image, highlighted_image] | |
self.rects = [ | |
default_image.get_rect(center=center_position), | |
highlighted_image.get_rect(center=center_position), | |
] | |
# calls the init method of the parent sprite class | |
super().__init__() | |
# properties that vary the image and its rect when the mouse is over the element | |
@property | |
def image(self): | |
return self.images[1] if self.mouse_over else self.images[0] | |
@property | |
def rect(self): | |
return self.rects[1] if self.mouse_over else self.rects[0] | |
def update(self, mouse_pos): | |
if self.rect.collidepoint(mouse_pos): | |
self.mouse_over = True | |
else: | |
self.mouse_over = False | |
def draw(self, surface): | |
""" Draws element onto a surface """ | |
surface.blit(self.image, self.rect) | |
def main(): | |
pygame.init() | |
screen = pygame.display.set_mode((800, 600)) | |
# create a ui element | |
uielement = UIElement( | |
center_position=(400, 400), | |
font_size=30, | |
text_rgb=WHITE, | |
text="Hello World", | |
) | |
# main loop | |
while True: | |
for event in pygame.event.get(): | |
pass | |
screen.fill(BLUE) | |
uielement.update(pygame.mouse.get_pos()) | |
uielement.draw(screen) | |
pygame.display.flip() | |
# call main when the script is run | |
if __name__ == "__main__": | |
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import pygame | |
import pygame.freetype | |
from pygame.sprite import Sprite | |
from pygame.rect import Rect | |
from enum import Enum | |
BLUE = (106, 159, 181) | |
WHITE = (255, 255, 255) | |
def create_surface_with_text(text, font_size, text_rgb, bg_rgb): | |
""" Returns surface with text written on """ | |
font = pygame.freetype.SysFont("Courier", font_size, bold=True) | |
surface, _ = font.render(text=text, fgcolor=text_rgb, bgcolor=bg_rgb) | |
return surface.convert_alpha() | |
class UIElement(Sprite): | |
""" An user interface element that can be added to a surface """ | |
def __init__(self, center_position, text, font_size, bg_rgb, text_rgb, action=None): | |
""" | |
Args: | |
center_position - tuple (x, y) | |
text - string of text to write | |
font_size - int | |
bg_rgb (background colour) - tuple (r, g, b) | |
text_rgb (text colour) - tuple (r, g, b) | |
action - the gamestate change associated with this button | |
""" | |
self.mouse_over = False | |
default_image = create_surface_with_text( | |
text=text, font_size=font_size, text_rgb=text_rgb, bg_rgb=bg_rgb | |
) | |
highlighted_image = create_surface_with_text( | |
text=text, font_size=font_size * 1.2, text_rgb=text_rgb, bg_rgb=bg_rgb | |
) | |
self.images = [default_image, highlighted_image] | |
self.rects = [ | |
default_image.get_rect(center=center_position), | |
highlighted_image.get_rect(center=center_position), | |
] | |
# assign button action | |
self.action = action | |
super().__init__() | |
@property | |
def image(self): | |
return self.images[1] if self.mouse_over else self.images[0] | |
@property | |
def rect(self): | |
return self.rects[1] if self.mouse_over else self.rects[0] | |
def update(self, mouse_pos, mouse_up): | |
""" Updates the mouse_over variable and returns the button's | |
action value when clicked. | |
""" | |
if self.rect.collidepoint(mouse_pos): | |
self.mouse_over = True | |
if mouse_up: | |
return self.action | |
else: | |
self.mouse_over = False | |
def draw(self, surface): | |
""" Draws element onto a surface """ | |
surface.blit(self.image, self.rect) | |
def main(): | |
pygame.init() | |
screen = pygame.display.set_mode((800, 600)) | |
quit_btn = UIElement( | |
center_position=(400, 500), | |
font_size=30, | |
bg_rgb=BLUE, | |
text_rgb=WHITE, | |
text="Quit", | |
action=GameState.QUIT, | |
) | |
# main loop | |
while True: | |
mouse_up = False | |
for event in pygame.event.get(): | |
if event.type == pygame.MOUSEBUTTONUP and event.button == 1: | |
mouse_up = True | |
screen.fill(BLUE) | |
ui_action = quit_btn.update(pygame.mouse.get_pos(), mouse_up) | |
if ui_action is not None: | |
return | |
quit_btn.draw(screen) | |
pygame.display.flip() | |
class GameState(Enum): | |
QUIT = -1 | |
if __name__ == "__main__": | |
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import pygame | |
import pygame.freetype | |
from pygame.sprite import Sprite | |
from pygame.rect import Rect | |
from enum import Enum | |
BLUE = (106, 159, 181) | |
WHITE = (255, 255, 255) | |
def create_surface_with_text(text, font_size, text_rgb, bg_rgb): | |
""" Returns surface with text written on """ | |
font = pygame.freetype.SysFont("Courier", font_size, bold=True) | |
surface, _ = font.render(text=text, fgcolor=text_rgb, bgcolor=bg_rgb) | |
return surface.convert_alpha() | |
class UIElement(Sprite): | |
""" An user interface element that can be added to a surface """ | |
def __init__(self, center_position, text, font_size, bg_rgb, text_rgb, action=None): | |
""" | |
Args: | |
center_position - tuple (x, y) | |
text - string of text to write | |
font_size - int | |
bg_rgb (background colour) - tuple (r, g, b) | |
text_rgb (text colour) - tuple (r, g, b) | |
action - the gamestate change associated with this button | |
""" | |
self.mouse_over = False | |
default_image = create_surface_with_text( | |
text=text, font_size=font_size, text_rgb=text_rgb, bg_rgb=bg_rgb | |
) | |
highlighted_image = create_surface_with_text( | |
text=text, font_size=font_size * 1.2, text_rgb=text_rgb, bg_rgb=bg_rgb | |
) | |
self.images = [default_image, highlighted_image] | |
self.rects = [ | |
default_image.get_rect(center=center_position), | |
highlighted_image.get_rect(center=center_position), | |
] | |
self.action = action | |
super().__init__() | |
@property | |
def image(self): | |
return self.images[1] if self.mouse_over else self.images[0] | |
@property | |
def rect(self): | |
return self.rects[1] if self.mouse_over else self.rects[0] | |
def update(self, mouse_pos, mouse_up): | |
""" Updates the mouse_over variable and returns the button's | |
action value when clicked. | |
""" | |
if self.rect.collidepoint(mouse_pos): | |
self.mouse_over = True | |
if mouse_up: | |
return self.action | |
else: | |
self.mouse_over = False | |
def draw(self, surface): | |
""" Draws element onto a surface """ | |
surface.blit(self.image, self.rect) | |
def main(): | |
pygame.init() | |
screen = pygame.display.set_mode((800, 600)) | |
game_state = GameState.TITLE | |
while True: | |
if game_state == GameState.TITLE: | |
game_state = title_screen(screen) | |
if game_state == GameState.NEWGAME: | |
game_state = play_level(screen) | |
if game_state == GameState.QUIT: | |
pygame.quit() | |
return | |
def title_screen(screen): | |
start_btn = UIElement( | |
center_position=(400, 400), | |
font_size=30, | |
bg_rgb=BLUE, | |
text_rgb=WHITE, | |
text="Start", | |
action=GameState.NEWGAME, | |
) | |
quit_btn = UIElement( | |
center_position=(400, 500), | |
font_size=30, | |
bg_rgb=BLUE, | |
text_rgb=WHITE, | |
text="Quit", | |
action=GameState.QUIT, | |
) | |
buttons = [start_btn, quit_btn] | |
while True: | |
mouse_up = False | |
for event in pygame.event.get(): | |
if event.type == pygame.MOUSEBUTTONUP and event.button == 1: | |
mouse_up = True | |
screen.fill(BLUE) | |
for button in buttons: | |
ui_action = button.update(pygame.mouse.get_pos(), mouse_up) | |
if ui_action is not None: | |
return ui_action | |
button.draw(screen) | |
pygame.display.flip() | |
def play_level(screen): | |
return_btn = UIElement( | |
center_position=(140, 570), | |
font_size=20, | |
bg_rgb=BLUE, | |
text_rgb=WHITE, | |
text="Return to main menu", | |
action=GameState.TITLE, | |
) | |
while True: | |
mouse_up = False | |
for event in pygame.event.get(): | |
if event.type == pygame.MOUSEBUTTONUP and event.button == 1: | |
mouse_up = True | |
screen.fill(BLUE) | |
ui_action = return_btn.update(pygame.mouse.get_pos(), mouse_up) | |
if ui_action is not None: | |
return ui_action | |
return_btn.draw(screen) | |
pygame.display.flip() | |
class GameState(Enum): | |
QUIT = -1 | |
TITLE = 0 | |
NEWGAME = 1 | |
if __name__ == "__main__": | |
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import pygame | |
import pygame.freetype | |
from pygame.sprite import Sprite | |
from pygame.rect import Rect | |
from enum import Enum | |
from pygame.sprite import RenderUpdates | |
BLUE = (106, 159, 181) | |
WHITE = (255, 255, 255) | |
def create_surface_with_text(text, font_size, text_rgb, bg_rgb): | |
""" Returns surface with text written on """ | |
font = pygame.freetype.SysFont("Courier", font_size, bold=True) | |
surface, _ = font.render(text=text, fgcolor=text_rgb, bgcolor=bg_rgb) | |
return surface.convert_alpha() | |
class UIElement(Sprite): | |
""" An user interface element that can be added to a surface """ | |
def __init__(self, center_position, text, font_size, bg_rgb, text_rgb, action=None): | |
""" | |
Args: | |
center_position - tuple (x, y) | |
text - string of text to write | |
font_size - int | |
bg_rgb (background colour) - tuple (r, g, b) | |
text_rgb (text colour) - tuple (r, g, b) | |
action - the gamestate change associated with this button | |
""" | |
self.mouse_over = False | |
default_image = create_surface_with_text( | |
text=text, font_size=font_size, text_rgb=text_rgb, bg_rgb=bg_rgb | |
) | |
highlighted_image = create_surface_with_text( | |
text=text, font_size=font_size * 1.2, text_rgb=text_rgb, bg_rgb=bg_rgb | |
) | |
self.images = [default_image, highlighted_image] | |
self.rects = [ | |
default_image.get_rect(center=center_position), | |
highlighted_image.get_rect(center=center_position), | |
] | |
self.action = action | |
super().__init__() | |
@property | |
def image(self): | |
return self.images[1] if self.mouse_over else self.images[0] | |
@property | |
def rect(self): | |
return self.rects[1] if self.mouse_over else self.rects[0] | |
def update(self, mouse_pos, mouse_up): | |
""" Updates the mouse_over variable and returns the button's | |
action value when clicked. | |
""" | |
if self.rect.collidepoint(mouse_pos): | |
self.mouse_over = True | |
if mouse_up: | |
return self.action | |
else: | |
self.mouse_over = False | |
def draw(self, surface): | |
""" Draws element onto a surface """ | |
surface.blit(self.image, self.rect) | |
class Player: | |
""" Stores information about a player """ | |
def __init__(self, score=0, lives=3, current_level=1): | |
self.score = score | |
self.lives = lives | |
self.current_level = current_level | |
def main(): | |
pygame.init() | |
screen = pygame.display.set_mode((800, 600)) | |
game_state = GameState.TITLE | |
while True: | |
if game_state == GameState.TITLE: | |
game_state = title_screen(screen) | |
if game_state == GameState.NEWGAME: | |
player = Player() | |
game_state = play_level(screen, player) | |
if game_state == GameState.NEXT_LEVEL: | |
player.current_level += 1 | |
game_state = play_level(screen, player) | |
if game_state == GameState.QUIT: | |
pygame.quit() | |
return | |
def title_screen(screen): | |
start_btn = UIElement( | |
center_position=(400, 400), | |
font_size=30, | |
bg_rgb=BLUE, | |
text_rgb=WHITE, | |
text="Start", | |
action=GameState.NEWGAME, | |
) | |
quit_btn = UIElement( | |
center_position=(400, 500), | |
font_size=30, | |
bg_rgb=BLUE, | |
text_rgb=WHITE, | |
text="Quit", | |
action=GameState.QUIT, | |
) | |
buttons = RenderUpdates(start_btn, quit_btn) | |
return game_loop(screen, buttons) | |
def play_level(screen, player): | |
return_btn = UIElement( | |
center_position=(140, 570), | |
font_size=20, | |
bg_rgb=BLUE, | |
text_rgb=WHITE, | |
text="Return to main menu", | |
action=GameState.TITLE, | |
) | |
nextlevel_btn = UIElement( | |
center_position=(400, 400), | |
font_size=30, | |
bg_rgb=BLUE, | |
text_rgb=WHITE, | |
text=f"Next level ({player.current_level + 1})", | |
action=GameState.NEXT_LEVEL, | |
) | |
buttons = RenderUpdates(return_btn, nextlevel_btn) | |
return game_loop(screen, buttons) | |
def game_loop(screen, buttons): | |
""" Handles game loop until an action is return by a button in the | |
buttons sprite renderer. | |
""" | |
while True: | |
mouse_up = False | |
for event in pygame.event.get(): | |
if event.type == pygame.MOUSEBUTTONUP and event.button == 1: | |
mouse_up = True | |
screen.fill(BLUE) | |
for button in buttons: | |
ui_action = button.update(pygame.mouse.get_pos(), mouse_up) | |
if ui_action is not None: | |
return ui_action | |
buttons.draw(screen) | |
pygame.display.flip() | |
class GameState(Enum): | |
QUIT = -1 | |
TITLE = 0 | |
NEWGAME = 1 | |
NEXT_LEVEL = 2 | |
if __name__ == "__main__": | |
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import pygame | |
import pygame.freetype | |
from pygame.sprite import Sprite | |
from pygame.rect import Rect | |
from enum import Enum | |
from pygame.sprite import RenderUpdates | |
BLUE = (106, 159, 181) | |
WHITE = (255, 255, 255) | |
def create_surface_with_text(text, font_size, text_rgb, bg_rgb): | |
""" Returns surface with text written on """ | |
font = pygame.freetype.SysFont("Courier", font_size, bold=True) | |
surface, _ = font.render(text=text, fgcolor=text_rgb, bgcolor=bg_rgb) | |
return surface.convert_alpha() | |
class UIElement(Sprite): | |
""" An user interface element that can be added to a surface """ | |
def __init__(self, center_position, text, font_size, bg_rgb, text_rgb, action=None): | |
""" | |
Args: | |
center_position - tuple (x, y) | |
text - string of text to write | |
font_size - int | |
bg_rgb (background colour) - tuple (r, g, b) | |
text_rgb (text colour) - tuple (r, g, b) | |
action - the gamestate change associated with this button | |
""" | |
self.mouse_over = False | |
default_image = create_surface_with_text( | |
text=text, font_size=font_size, text_rgb=text_rgb, bg_rgb=bg_rgb | |
) | |
highlighted_image = create_surface_with_text( | |
text=text, font_size=font_size * 1.2, text_rgb=text_rgb, bg_rgb=bg_rgb | |
) | |
self.images = [default_image, highlighted_image] | |
self.rects = [ | |
default_image.get_rect(center=center_position), | |
highlighted_image.get_rect(center=center_position), | |
] | |
self.action = action | |
super().__init__() | |
@property | |
def image(self): | |
return self.images[1] if self.mouse_over else self.images[0] | |
@property | |
def rect(self): | |
return self.rects[1] if self.mouse_over else self.rects[0] | |
def update(self, mouse_pos, mouse_up): | |
""" Updates the mouse_over variable and returns the button's | |
action value when clicked. | |
""" | |
if self.rect.collidepoint(mouse_pos): | |
self.mouse_over = True | |
if mouse_up: | |
return self.action | |
else: | |
self.mouse_over = False | |
def draw(self, surface): | |
""" Draws element onto a surface """ | |
surface.blit(self.image, self.rect) | |
class Player: | |
""" Stores information about a player """ | |
def __init__(self, score=0, lives=3, current_level=1): | |
self.score = score | |
self.lives = lives | |
self.current_level = current_level | |
def main(): | |
pygame.init() | |
screen = pygame.display.set_mode((800, 600)) | |
game_state = GameState.TITLE | |
while True: | |
if game_state == GameState.TITLE: | |
game_state = title_screen(screen) | |
if game_state == GameState.NEWGAME: | |
player = Player() | |
game_state = play_level(screen, player) | |
if game_state == GameState.NEXT_LEVEL: | |
player.current_level += 1 | |
game_state = play_level(screen, player) | |
if game_state == GameState.QUIT: | |
pygame.quit() | |
return | |
def title_screen(screen): | |
start_btn = UIElement( | |
center_position=(400, 400), | |
font_size=30, | |
bg_rgb=BLUE, | |
text_rgb=WHITE, | |
text="Start", | |
action=GameState.NEWGAME, | |
) | |
quit_btn = UIElement( | |
center_position=(400, 500), | |
font_size=30, | |
bg_rgb=BLUE, | |
text_rgb=WHITE, | |
text="Quit", | |
action=GameState.QUIT, | |
) | |
buttons = RenderUpdates(start_btn, quit_btn) | |
return game_loop(screen, buttons) | |
def play_level(screen, player): | |
return_btn = UIElement( | |
center_position=(140, 570), | |
font_size=20, | |
bg_rgb=BLUE, | |
text_rgb=WHITE, | |
text="Return to main menu", | |
action=GameState.TITLE, | |
) | |
nextlevel_btn = UIElement( | |
center_position=(400, 400), | |
font_size=30, | |
bg_rgb=BLUE, | |
text_rgb=WHITE, | |
text=f"Next level ({player.current_level + 1})", | |
action=GameState.NEXT_LEVEL, | |
) | |
buttons = RenderUpdates(return_btn, nextlevel_btn) | |
return game_loop(screen, buttons) | |
def game_loop(screen, buttons): | |
""" Handles game loop until an action is return by a button in the | |
buttons sprite renderer. | |
""" | |
while True: | |
mouse_up = False | |
for event in pygame.event.get(): | |
if event.type == pygame.QUIT: | |
pygame.quit() | |
if event.type == pygame.MOUSEBUTTONUP and event.button == 1: | |
mouse_up = True | |
screen.fill(BLUE) | |
for button in buttons: | |
ui_action = button.update(pygame.mouse.get_pos(), mouse_up) | |
if ui_action is not None: | |
return ui_action | |
buttons.draw(screen) | |
pygame.display.flip() | |
class GameState(Enum): | |
QUIT = -1 | |
TITLE = 0 | |
NEWGAME = 1 | |
NEXT_LEVEL = 2 | |
if __name__ == "__main__": | |
main() |
In step4_additional.py when clicking the top right X, it quits, but several error messages are shown. To make it cleaner, I did the following:
Right after line 164 with pygame.quit()
add the following line:
sys.exit()
then, add the corresponding import at the top.
import sys
@programmingpixels Thanks for the video! Feel free to incorporate the above change.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The file: step1_without_bg_rgb.py
Is an example of how you can complete step1 without specifying any object's background colours. If you're just interested in following the tutorials then the primary files you might like to compare your code with are: