Pygame class menu - python

I thought I would give pygame a try and have ran into an issue that I hope someone can help me with. I would like to have a class def to create the quit button but when I do this I can't get the mouse collision. I am most likely doing it wrong, but would really appreciate the help.
It works in the state I have below but not when I put it in a def!
pygame.init()
class Window:
def __init__(self):
self.screen_width = 800
self.screen_height = 600
self.screen = (pygame.display.set_mode((self.screen_width, self.screen_height)))
self.FPS = 30
self.clock = pygame.time.Clock()
self.font = pygame.font.SysFont('Arial', 25)
self.menu_open = True
self.colors = {"red": (255, 0, 0),
"green": (0, 255, 0),
"blue": (0, 0, 255),
"white": (255, 255, 255),
"black": (0, 0, 0),
"brown": (153, 76, 0),
"grey": (100, 100, 100)}
def setup(self):
self.screen.fill(self.colors["black"])
pygame.display.set_caption("Menu Test!")
def text(self, message, text_color, x_pos, y_pos):
text = self.font.render(message, True, (self.colors[text_color]))
text_rect = text.get_rect(center=(x_pos, y_pos))
self.screen.blit(text, text_rect)
def exit(self):
self.screen.fill(self.colors["black"])
text = self.font.render("Thank you for playing. Goodbye!", True,
(self.colors["white"]))
text_rect = text.get_rect(center=(self.screen_width / 2,
self.screen_height / 2))
self.screen.blit(text, text_rect)
pygame.display.update()
sleep(3)
pygame.quit()
sys.exit()
def main():
window = Window()
window.setup()
This is the bit i would like in a class def
quit_button = pygame.draw.rect(window.screen, window.colors["white"],
(window.screen_width / 2 - 100,
window.screen_height / 1.5 - 25, 200, 50), 0)
window.text("QUIT", "red", window.screen_width / 2, window.screen_height / 1.5)
pygame.display.update()
while window.menu_open == 1:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
And this is where it get the posistion of the mouse click on the rect.
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
pos = pygame.mouse.get_pos()
if quit_button.collidepoint(pos):
window.exit()
else:
print("Error Line 48")
if __name__ == "__main__":
main()

Create a class (in this case I use a pygame.sprite.Sprite subclass) with an image and a rect attribute and blit the text onto the image.
import sys
import pygame as pg
pg.init()
class Window:
def __init__(self):
self.screen = pg.display.set_mode((800, 600))
self.rect = self.screen.get_rect()
self.FPS = 30
self.clock = pg.time.Clock()
self.font = pg.font.SysFont("Arial", 25)
self.menu_open = True
self.colors = {"red": (255, 0, 0),
"green": (0, 255, 0),
"blue": (0, 0, 255),
"white": (255, 255, 255),
"black": (0, 0, 0),
"brown": (153, 76, 0),
"grey": (100, 100, 100)}
def setup(self):
self.screen.fill(self.colors["black"])
pg.display.set_caption("Menu Test!")
def text(self, message, text_color, x_pos, y_pos):
text = self.font.render(message, True, (self.colors[text_color]))
text_rect = text.get_rect(center=(x_pos, y_pos))
self.screen.blit(text, text_rect)
def exit(self):
self.screen.fill(self.colors["black"])
text = self.font.render("Thank you for playing. Goodbye!", True,
(self.colors["white"]))
text_rect = text.get_rect(center=(self.rect.w/2, self.rect.h/2))
self.screen.blit(text, text_rect)
pg.display.update()
pg.time.wait(1000)
pg.quit()
sys.exit()
class Button(pg.sprite.Sprite):
def __init__(self, pos, text, window):
super().__init__() # Call __init__ of the parent class.
# Render the text.
self.text_surf = window.font.render(text, True, window.colors["black"])
self.image = pg.Surface((self.text_surf.get_width()+40,
self.text_surf.get_height()+20))
self.image.fill(window.colors["white"])
# Now blit the text onto the self.image.
self.image.blit(self.text_surf, (20, 10))
self.rect = self.image.get_rect(topleft=pos)
def main():
window = Window()
window.setup()
clock = pg.time.Clock()
# gui is a sprite group which will contain the button sprites.
gui = pg.sprite.Group()
# Instantiate some buttons.
quit_button = Button(
pos=(window.rect.w/2 - 100, window.rect.h/1.5 - 25),
text="QUIT",
window=window,
)
hello_button = Button(
pos=(window.rect.w/8, window.rect.h/2),
text="hello",
window=window,
)
# Add the buttons to the gui group.
gui.add(quit_button, hello_button)
while window.menu_open == True:
for event in pg.event.get():
if event.type == pg.QUIT:
window.exit()
if event.type == pg.MOUSEBUTTONDOWN and event.button == 1:
# Handle button events.
if quit_button.rect.collidepoint(event.pos):
window.exit()
elif hello_button.rect.collidepoint(event.pos):
print("hello")
gui.update() # Call update methods of contained sprites.
gui.draw(window.screen) # Draw all sprites.
pg.display.flip()
clock.tick(30)
if __name__ == "__main__":
main()
A bit more advanced solution would be to handle the events in the Button class and then call a callback function which you have to pass during the instantiation.
class Button(pg.sprite.Sprite):
def __init__(self, pos, text, window, callback):
super().__init__()
self.text_surf = window.font.render(text, True, window.colors["black"])
self.image = pg.Surface((self.text_surf.get_width()+40,
self.text_surf.get_height()+20))
self.image.fill(window.colors["white"])
self.image.blit(self.text_surf, (20, 10))
self.rect = self.image.get_rect(topleft=pos)
# The callback function will be called when
# the left mouse button gets pressed.
self.callback = callback
def handle_event(self, event):
if event.type == pg.MOUSEBUTTONDOWN and event.button == 1:
if self.rect.collidepoint(event.pos):
self.callback() # Call the callback function.
def main():
window = Window()
window.setup()
clock = pg.time.Clock()
gui = pg.sprite.Group()
quit_button = Button(
pos=(window.rect.w/2 - 100, window.rect.h/1.5 - 25),
text="QUIT",
window=window,
callback=window.exit, # Pass the callback function.
)
hello_button = Button(
pos=(window.rect.w/8, window.rect.h/2),
text="hello",
window=window,
callback=lambda: print("hello"), # Pass the callback function.
)
gui.add(quit_button, hello_button)
while window.menu_open == True:
for event in pg.event.get():
if event.type == pg.QUIT:
window.exit()
# Iterate over the gui group and pass the event to the buttons.
# If they collide with the mouse, call the callback func.
for button in gui:
button.handle_event(event)
gui.update()
gui.draw(window.screen)
pg.display.flip()
clock.tick(30)
if __name__ == "__main__":
main()

Related

Why does my button not work outside of the main loop

I have been following this tutorial about animated buttons in pygame. It worked perfectly until I created a button outside of the main loop in another function.
Here is my code:
import pygame
from pygame.locals import *
import sys
import random
# Constants
SCREEN = pygame.display.set_mode((1280, 720), 0, 32)
# Colours
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GREY = (100, 100, 100)
LIGHT_BLUE = (66, 233, 245)
# Button Class
class Button:
def __init__(self, text, width, height, pos, elevation):
# Core attributes
self.pressed = False
self.elevation = elevation
self.dynamicElevation = elevation
self.originalYPos = pos[1]
# Top Rectangle
self.topRectangle = pygame.Rect(pos, (width, height))
self.topColor = '#457B9D'
# Bottom Rectangle
self.bottomRectangle = pygame.Rect(pos, (width, elevation))
self.bottomColor = '#1D3557'
# Text
self.textSurface = gui_font.render(text, True, '#FFFFFF')
self.textRectangle = self.textSurface.get_rect(center = self.topRectangle.center)
def draw(self):
# Elevation Logic
self.topRectangle.y = self.originalYPos - self.dynamicElevation
self.textRectangle.center = self.topRectangle.center
self.bottomRectangle.midtop = self.topRectangle.midtop
self.bottomRectangle.height = self.topRectangle.height + self.dynamicElevation
bottom =pygame.draw.rect(SCREEN, self.bottomColor, self.bottomRectangle, border_radius = 12)
top = pygame.draw.rect(SCREEN, self.topColor, self.topRectangle, border_radius = 12)
pygame.draw.rect(SCREEN, '#000000', top, 1, border_radius = 12)
pygame.draw.rect(SCREEN, '#000000', bottom, 1, border_radius = 12)
SCREEN.blit(self.textSurface, self.textRectangle)
self.check_click()
def check_click(self):
mousePosition = pygame.mouse.get_pos()
if self.topRectangle.collidepoint(mousePosition):
self.topColor = '#F1FAEE'
if pygame.mouse.get_pressed()[0]:
self.dynamicElevation = 0
self.pressed = True
else:
self.dynamicElevation = self.elevation
if self.pressed == True:
print("Click")
self.pressed = False
else:
self.topColor = '#457B9D'
class GameState():
def __init__(self):
self.state = "welcome"
def welcomeScreen(self):
SCREEN.fill(WHITE)
for event in pygame.event.get():
if event.type == QUIT:
exit()
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
exit()
if event.key == K_F1:
self.state = "mainGame"
pygame.display.update()
def mainGame(self):
SCREEN.fill(GREY)
buttonBack = Button("Back to Main Screen", 250, 30, (1000, 650), 8)
for event in pygame.event.get():
if event.type == QUIT:
exit()
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
exit()
if event.key == K_F2:
self.state = "welcome"
buttonBack.draw()
pygame.display.update()
def stateManager(self):
if self.state == "welcome":
self.welcomeScreen()
if self.state == "mainGame":
self.mainGame()
pygame.init()
clock = pygame.time.Clock()
gameState = GameState()
pygame.display.set_caption("Button Test")
gui_font = pygame.font.Font(None, 30)
while True:
gameState.stateManager()
clock.tick(60)
I have tried to play around with putting the button in a different screen or at different stages of the loop. Is there a logic error I cannot see or lies my mistake somewhere else?
You are actually creating the button inside the main loop since you create it each time mainGame is called. mainGame is called by stateManager if the state is "mainGame", and that's called at each frame in your while True loop. So as you are recreating your button at each frame I think your problems might come from there.
I suggest you create your button in the parent's class constructor instead:
class GameState():
def __init__(self):
self.state = "welcome"
# Create the button here to make the object persistent.
self.buttonBack = Button("Back to Main Screen", 250, 30, (1000, 650), 8)
# ...
def mainGame(self):
SCREEN.fill(GREY)
for event in pygame.event.get():
if event.type == QUIT:
exit()
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
exit()
if event.key == K_F2:
self.state = "welcome"
self.buttonBack.draw() # <- use your button with self.buttonBack
pygame.display.update()
# ...

Pygame buttons not reacting to clicks

I've been trying to make a button work for a pygame multiple choice game, where after the random question is pressed it will bring you to a question page with 2 answers, one right, one wrong. However they are not reacting to anything when pressed, my code can be seen below;
import pygame
import random
pygame.init()
win = pygame.display.set_mode((1200, 600))
pygame.display.set_caption("History Game")
#Background Image Loading
bg = pygame.image.load('bg.jpg')
bgAus = pygame.image.load('bg_Aus.jpg')
bgEN = pygame.image.load('bg_EN.jpg')
bgIR = pygame.image.load('bg_IR.jpg')
WHITE = (255, 255, 255)
ACTIVE_COLOR = pygame.Color('blue')
INACTIVE_COLOR = pygame.Color('red')
FONT = pygame.font.Font(None, 50)
def draw_buttonStart(buttonStart, win):
pygame.draw.rect(win, buttonStart['color'], buttonStart['rect'])
win.blit(buttonStart['text'], buttonStart['text rect'])
def draw_buttonAus1(buttonAus1, win):
pygame.draw.rect(win, buttonAus1['color'], buttonAus1['rect'])
win.blit(buttonAus1['text'], buttonAus1['text rect'])
def draw_buttonAus2(buttonAus2, win):
pygame.draw.rect(win, buttonAus2['color'], buttonAus2['rect'])
win.blit(buttonAus2['text'], buttonAus2['text rect'])
def draw_buttonIR1(buttonIR1, win):
pygame.draw.rect(win, buttonIR1['color'], buttonIR1['rect'])
win.blit(buttonIR1['text'], buttonIR1['text rect'])
def draw_buttonIR2(buttonIR2, win):
pygame.draw.rect(win, buttonIR2['color'], buttonIR2['rect'])
win.blit(buttonIR2['text'], buttonIR2['text rect'])
def draw_buttonEN1(buttonEN1, win):
pygame.draw.rect(win, buttonEN1['color'], buttonEN1['rect'])
win.blit(buttonEN1['text'], buttonEN1['text rect'])
def draw_buttonEN2(buttonEN2, win):
pygame.draw.rect(win, buttonEN2['color'], buttonEN2['rect'])
win.blit(buttonEN2['text'], buttonEN2['text rect'])
def create_button(x, y, w, h, text, callback):
text_surf = FONT.render(text, True, WHITE)
button_rect = pygame.Rect(x, y, w, h)
text_rect = text_surf.get_rect(center=button_rect.center)
button = {
'rect': button_rect,
'text': text_surf,
'text rect': text_rect,
'color': INACTIVE_COLOR,
'callback': callback,
}
return button
points = 0
def correct_answer():
global points
points += 100
print(points)
def wrong_answer():
global points
points -= 50
print(points)
moveOn = 0
def move_on():
global moveOn
moveOn = 1
win.blit(bg, (0, -200))
#Main Loop
over = False
while not over:
score = FONT.render('Score: ' + str(points), 1, (255,0,0))
win.blit(score, (390, 10))
pygame.display.flip()
buttonStart = create_button(50, 50, 250, 80, 'Random', move_on)
button_listStart = [buttonStart]
draw_buttonStart(buttonStart, win)
#Quits game if X is clicked
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
elif event.type == pygame.MOUSEBUTTONDOWN:
for button in button_listStart:
if button['rect'].collidepoint(event.pos):
button['callback']()
print (moveOn)
randomQ = random.randint(1,3)
if int(randomQ) == 1:
print("1")
buttonAus1 = create_button(275, 400, 250, 80, '1606', correct_answer)
buttonAus2 = create_button(675, 400, 250, 80, '1723', wrong_answer)
button_listAus = [buttonAus1, buttonAus2]
win.blit(bgAus, (-400, 0))
draw_buttonAus1(buttonAus1, win)
draw_buttonAus2(buttonAus2, win)
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONDOWN:
for button in button_listAus:
if button['rect'].collidepoint(event.pos):
button['callback']()
print (points)
#Hover over button changes colour
elif event.type == pygame.MOUSEMOTION:
for button in button_listAus:
if button['rect'].collidepoint(event.pos):
button['color'] = ACTIVE_COLOR
else:
button['color'] = INACTIVE_COLOR
elif int(randomQ) == 2:
print ("2")
buttonEN1 = create_button(675, 400, 250, 80, '1715', correct_answer)
buttonEN2 = create_button(275, 400, 250, 80, '1789', wrong_answer)
button_listEN = [buttonEN1, buttonEN2]
win.blit(bgEN, (0, -150))
draw_buttonEN1(buttonEN1, win)
draw_buttonEN2(buttonEN2, win)
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONDOWN:
for button in button_listEN:
if button['rect'].collidepoint(event.pos):
button['callback']()
#Hover over button changes colour
elif event.type == pygame.MOUSEMOTION:
for button in button_listEN:
if button['rect'].collidepoint(event.pos):
button['color'] = ACTIVE_COLOR
else:
button['color'] = INACTIVE_COLOR
else:
print ("3")
buttonIR1 = create_button(275, 400, 250, 80, '1760', correct_answer)
buttonIR2 = create_button(675, 400, 250, 80, '1812', wrong_answer)
button_listIR = [buttonIR1, buttonIR2]
win.blit(bgIR, (-375, -20))
draw_buttonIR1(buttonIR1, win)
draw_buttonIR2(buttonIR2, win)
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONDOWN:
for button in button_listIR:
if button['rect'].collidepoint(event.pos):
button['callback']()
#Hover over button changes colour
elif event.type == pygame.MOUSEMOTION:
for button in button_listIR:
if button['rect'].collidepoint(event.pos):
button['color'] = ACTIVE_COLOR
else:
button['color'] = INACTIVE_COLOR
I was wandering if it might have something to do with the for loop as before I combined the for loop for the main loop and the quit feature, the quit feature didn't work at all so any help is appreciated, Thank you.
try binding your buttons to their respective mouse actions.

Text Size Change Persistence

I'll start with simply copy and pasting my huge chunk of code:
# Libraries #
import pygame
from pygame.locals import *
import ResolutionMenu
# Classes #
class GuiSettings:
def __init__(self):
self.TextSize = 20
self.button_color = (35, 65, 145)
self.button_color_hover = (70, 105, 150)
class Button():
def __init__(self, x, y, width, height, outline, settings, text = "", action = None):
self.x = x
self.y = y
self.width = width
self.height = height
self.text = text
self.settings = settings
self.action = action
self.outline = outline
def draw(self, screen, outline = None):
if self.outline:
pygame.draw.rect(screen, self.outline, (self.x - 2, self.y - 2, self.width + 4, self.height + 4), 0)
color = self.settings.button_color if not self.isOver(pygame.mouse.get_pos()) else self.settings.button_color_hover
pygame.draw.rect(screen, color, (self.x, self.y, self.width, self.height), 0)
if self.text != "":
font = pygame.font.SysFont('segoeuisemibold', self.settings.TextSize)
text = font.render(self.text, 1, (0, 0, 0))
screen.blit(text, (self.x + (self.width / 2 - text.get_width() / 2), self.y + (self.height / 2 - text.get_height() / 2)))
def update(self, events):
for event in events:
if event.type == pygame.MOUSEBUTTONDOWN and self.isOver(pygame.mouse.get_pos()) and self.action:
self.action()
def isOver(self, pos):
if pos[0] > self.x and pos[0] < self.x + self.width:
if pos[1] > self.y and pos[1] < self.y + self.height:
return True
return False
# Variables #
settings = GuiSettings()
Click = False
Clock = pygame.time.Clock()
H, W = ResolutionMenu.resolution
pygame.init()
pygame.display.set_caption("Main Menu")
font = pygame.font.SysFont(None, settings.TextSize)
screen = pygame.display.set_mode((ResolutionMenu.resolution))
# Functions #
def draw_text_center(text, font, colour, surface):
textobj = font.render(text, 1, colour)
textrect = textobj.get_rect()
textrect.center = ((H / 2), (W / 10))
surface.blit(textobj, textrect)
def draw_text(text, font, colour, surface, XandY):
textobj = font.render(text, 1, colour)
textrect = textobj.get_rect()
textrect.topleft = (XandY)
surface.blit(textobj, textrect)
def MainMenu():
while True:
if ResolutionMenu.resolution == (800, 600):
screen.fill ((0, 0, 0))
draw_text_center("Main Menu Navigation", font, (255, 255, 255), screen)
mx, my = pygame.mouse.get_pos()
button1 = pygame.Rect((W/10), (H/5), (W/3), (H/15))
button2 = pygame.Rect((W/10), (H/2), (W/3), (H/15))
if button1.collidepoint((mx, my)):
if Click:
game()
if button2.collidepoint((mx, my)):
if Click:
OptionsMenu()
pygame.draw.rect(screen, (255, 0, 0), button1)
pygame.draw.rect(screen, (255, 0, 0,), button2)
Click = False
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
pygame.quit()
if event.type == MOUSEBUTTONDOWN:
if event.button == 1:
Click = True
pygame.display.update()
Clock.tick(60)
def game():
running = True
while running:
screen.fill((0,0,0))
draw_text('Test 1', font, (255, 255, 255), screen, (20, 20))
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
running = False
pygame.display.update()
Clock.tick(60)
def OptionsMenu():
screen = pygame.display.set_mode((800, 600))
settings = GuiSettings()
buttons = [
Button(100, 150, 200, 50, (0, 0, 0), settings, "Set Text Size: 14", lambda: settings.__setattr__('TextSize', 14)),
Button(500, 150, 200, 50, (0, 0, 0), settings, "Set Text Size: 16", lambda: settings.__setattr__('TextSize', 16)),
Button(100, 300, 200, 50, (0, 0, 0), settings, "Set Text Size: 18", lambda: settings.__setattr__('TextSize', 18)),
Button(500, 300, 200, 50, (0, 0, 0), settings, "Set Text Size: 20", lambda: settings.__setattr__('TextSize', 20))
]
running = True
while running:
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
pygame.quit()
return
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
running = False
for button in buttons:
button.update(events)
screen.fill((100, 100, 100))
for button in buttons:
button.draw(screen)
pygame.display.flip()
MainMenu()
So as you can see, recently I've been trying to make a main menu navigation panel for a game, and all is going well so far. I have stumbled into a problem, however.
The problem is thus: I have created a class of which holds the settings for text size, the button colour and the button hover colour (the colour of the button will change when the mouse pos is inside of the button's area).
class GuiSettings:
def __init__(self):
self.TextSize = 20
self.button_color = (35, 65, 145)
self.button_color_hover = (70, 105, 150)
This works completely fine when I'm inside of the OptionMenu() function, but when breaking that function, by pressing esc and returning to the MainMenu() function, the text size of the "Main Menu Navigation" text (Code below) doesn't change, despite using the 'settings.TextSize' as the size variable. I'm not sure why this is seeing as on the options menu uses a lambda function to change the value of the text size.
buttons = [
Button(100, 150, 200, 50, (0, 0, 0), settings, "Set Text Size: 14", lambda: settings.__setattr__('TextSize', 14)),
Button(500, 150, 200, 50, (0, 0, 0), settings, "Set Text Size: 16", lambda: settings.__setattr__('TextSize', 16)),
Button(100, 300, 200, 50, (0, 0, 0), settings, "Set Text Size: 18", lambda: settings.__setattr__('TextSize', 18)),
Button(500, 300, 200, 50, (0, 0, 0), settings, "Set Text Size: 20", lambda: settings.__setattr__('TextSize', 20))
]
Sorry that this has been such a long question, thank you for reading and I hope someone better at this than me can give me some help.
You create the font object before the main application loop with the font size settings.TextSize. This font object doesn't magically change when you change the settings.TextSize afterwards. The font size is used to rasterize the glyphs in the font. You need to create a new font object with a new size after the function OptionsMenu is left.
Furthermore you need to use the global statement, since font and settings are variables in global namespace.
def MainMenu():
global font # font is a variable in global namespace
while True:
if ResolutionMenu.resolution == (800, 600):
# [...]
if button2.collidepoint((mx, my)):
if Click:
OptionsMenu()
# create new font with new size
font = pygame.font.SysFont(None, settings.TextSize)
# [...]
def OptionsMenu():
global settings # settings is a variable in global namespace
screen = pygame.display.set_mode((800, 600))
# settings = GuiSettings() # <--- DELETE use the existing object instead
# [...]

Detecting mouseover and making a sound when the mouse is over it?

So im setting up a main menu for the program I'm making and I was planning to have a beeping sound for whenever a mouse hovers above a button.
The thing is, I have the button labels on my image already, so i don't really know how i could incorporate this.
import math, random, sys
import enum
import pygame, time
from pygame.locals import*
from sys import exit
from pygame import mixer
#initialising python
pygame.init()
#pygame.mixer.init()
pygame.mixer.pre_init(44100,16,2,4096)
mixer.init()
#define display
W, H = 1600,900
HW, HH = (W/2), (H/2)
AREA = W * H
#bsound effects
buttonsound1 = pygame.mixer.Sound("ButtonSound1.wav")
#initialising display
CLOCK = pygame.time.Clock()
DS = pygame.display.set_mode((W, H))
pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
FPS = 54
progress = 0
background = pygame.Surface(DS.get_size())
smallfont = pygame.font.SysFont("century gothic",25)
#background image
bg = pygame.image.load("Daytime.jpg").convert()
loadingimg = pygame.image.load("LoadingScreen.png").convert()
pause = pygame.image.load("Pause screen.png").convert()
gameover = pygame.image.load("Game Over.png").convert()
mainmenu = pygame.image.load("Main_Menu4.png").convert()
#mainmenu = pygame.transform.smoothscale(mainmenu, (W,H))
loadingimg = pygame.transform.smoothscale(loadingimg, (W,H))
#define some colours
BLACK = (0,0,0,255)
WHITE = (255,255,255,255)
green = (0,140,0)
grey = (180,180,180)
walkLeft = [pygame.image.load('Moving1.png'), pygame.image.load('Moving2.png'), pygame.image.load('Moving3.png'), pygame.image.load('Moving4.png'), pygame.image.load('Moving5.png'), pygame.image.load('Moving6.png'), pygame.image.load('Moving7.png'), pygame.image.load('Moving8.png'), pygame.image.load('Moving9.png')]
walkRight = []
for i in walkLeft:
walkRight.append(pygame.transform.flip(i, True, False))
char = pygame.image.load('Moving1.png').convert_alpha()
char2 = pygame.image.load('Moving1.png').convert_alpha()
char2 = pygame.transform.flip(char2, True, False)
x = 0
y = 500
height = 40
width = 87
vel = 5
isJump = False
jumpCount = 10
left = False
right = False
walkCount = 0
run = True
# FUNCTIONS
def event_handler():
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
pygame.quit()
exit()
# === CLASSES === (CamelCase names)
class Button():
def __init__(self, text, x=0, y=0, width=100, height=50, command=None):
self.text = text
self.command = command
self.image_normal = pygame.Surface((width, height))
self.image_normal.fill(green)
self.image_hovered = pygame.Surface((width, height))
#buttonsound1.play()
self.image = self.image_normal
self.rect = self.image.get_rect()
font = pygame.font.Font('freesansbold.ttf', 15)
text_image = font.render(text, True, WHITE)
text_rect = text_image.get_rect(center = self.rect.center)
self.image_normal.blit(text_image, text_rect)
self.image_hovered.blit(text_image, text_rect)
# you can't use it before `blit`
self.rect.topleft = (x, y)
self.hovered = False
#self.clicked = False
def update(self):
if self.hovered:
buttonsound1.play()
else:
self.image = self.image_normal
def draw(self, surface):
surface.blit(self.image, self.rect)
def handle_event(self, event):
if event.type == pygame.MOUSEMOTION:
self.hovered = self.rect.collidepoint(event.pos)
buttonsound1.play()
elif event.type == pygame.MOUSEBUTTONDOWN:
if self.hovered:
buttonsound1.play()
print('Clicked:', self.text)
if self.command:
self.command()
class GameState( enum.Enum ):
Loading = 0
Menu = 1
Settings = 2
Playing = 3
GameOver = 4
#set the game state initially.
game_state = GameState.Loading
#LOADING
def text_objects(text, color, size):
if size == "small":
textSurface = smallfont.render(text, True, color)
return textSurface, textSurface.get_rect()
def loading(progress):
if progress < 100:
text = smallfont.render("Loading: " + str(int(progress)) + "%", True, WHITE)
else:
text = smallfont.render("Loading: " + str(100) + "%", True, WHITE)
DS.blit(text, [50, 660])
def message_to_screen(msh, color, y_displace = 0, size = "small"):
textSurf, textRect = text_objects(msg, color, size)
textRect.center = HW, HH + y_displace
DS.blit(textSurf, textRect)
while (progress/4) < 100:
event_handler()
DS.blit(loadingimg, (0,0))
time_count = (random.randint(1,1))
increase = random.randint(1,20)
progress += increase
pygame.draw.rect(DS, green, [50, 700, 402, 29])
pygame.draw.rect(DS, grey, [50, 701, 401, 27])
if (progress/4) > 100:
pygame.draw.rect(DS, green, [50, 700, 401, 28])
else:
pygame.draw.rect(DS, green, [50, 700, progress, 28])
loading(progress/4)
pygame.display.flip()
time.sleep(time_count)
#changing to menu
game_state = GameState.Menu
Menumusic = pygame.mixer.music.load("MainMenu.mp3")
Menumusic = pygame.mixer.music.play(-1, 0.0)
def main_menu():
DS.blit(mainmenu, (0, 0))
pygame.display.update()
btn1 = Button('Hello', 812.5, 250, 100, 50)
btn2 = Button('World', 825, 325, 100, 50)
btn3 = Button('Hello', 825, 450, 100, 50)
btn4 = Button('World', 825, 575, 100, 50)
btn5 = Button('World', 825, 675, 100, 50)
btn6 = Button('Hello', 825, 790, 100, 50)
while run:
event_handler()
btn1.update()
btn2.update()
# --- draws ---
btn1.draw(DS)
btn2.draw(DS)
btn3.draw(DS)
btn4.draw(DS)
btn5.draw(DS)
btn6.draw(DS)
pygame.display.update()
main_menu()
This is my whole code and I'm not sure what to do with adding buttons.
My image: https://gyazo.com/ca251495b348ab8cd27f7328c84518e8
It doesn't matter where you have button label - you need only its positon and size (x,y,width,height) or its pygame.Rect to compare with mouse position.
Rect has even function collidepoint to check collision with point and it can be mouse position.
if button_rect.collidepoint(mouse_position):
print("Mouse over button")
EDIT:
It is my code from GitHub with simple example with hovering button using class Button which changes color when mouse is over button (hover). It uses Rect.coolidepoint in Button.handle_event(). Maybe it can help you.
import pygame
# === CONSTANTS === (UPPER_CASE names)
BLACK = ( 0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = ( 0, 255, 0)
BLUE = ( 0, 0, 255)
SCREEN_WIDTH = 600
SCREEN_HEIGHT = 400
# === CLASSES === (CamelCase names)
class Button():
def __init__(self, text, x=0, y=0, width=100, height=50, command=None):
self.text = text
self.command = command
self.image_normal = pygame.Surface((width, height))
self.image_normal.fill(GREEN)
self.image_hovered = pygame.Surface((width, height))
self.image_hovered.fill(RED)
self.image = self.image_normal
self.rect = self.image.get_rect()
font = pygame.font.Font('freesansbold.ttf', 15)
text_image = font.render(text, True, WHITE)
text_rect = text_image.get_rect(center = self.rect.center)
self.image_normal.blit(text_image, text_rect)
self.image_hovered.blit(text_image, text_rect)
# you can't use it before `blit`
self.rect.topleft = (x, y)
self.hovered = False
#self.clicked = False
def update(self):
if self.hovered:
self.image = self.image_hovered
else:
self.image = self.image_normal
def draw(self, surface):
surface.blit(self.image, self.rect)
def handle_event(self, event):
if event.type == pygame.MOUSEMOTION:
self.hovered = self.rect.collidepoint(event.pos)
elif event.type == pygame.MOUSEBUTTONDOWN:
if self.hovered:
print('Clicked:', self.text)
if self.command:
self.command()
# === FUNCTIONS === (lower_case names)
# empty
# === MAIN === (lower_case names)
def main():
# --- init ---
pygame.init()
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
screen_rect = screen.get_rect()
clock = pygame.time.Clock()
is_running = False
btn1 = Button('Hello', 200, 50, 100, 50)
btn2 = Button('World', 200, 150, 100, 50)
# --- mainloop --- (don't change it)
is_running = True
while is_running:
# --- events ---
for event in pygame.event.get():
# --- global events ---
if event.type == pygame.QUIT:
is_running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
is_running = False
# --- objects events ---
btn1.handle_event(event)
btn2.handle_event(event)
# --- updates ---
btn1.update()
btn2.update()
# --- draws ---
screen.fill(BLACK)
btn1.draw(screen)
btn2.draw(screen)
pygame.display.update()
# --- FPS ---
clock.tick(25)
# --- the end ---
pygame.quit()
#----------------------------------------------------------------------
if __name__ == '__main__':
main()
EDIT: play sound only when mouse hovers over button
if event.type == pygame.MOUSEMOTION:
previous_value = self.hovered # remeber previus value
self.hovered = self.rect.collidepoint(event.pos) # get new value
# check both values
if previous_value is False and self.hovered is True:
buttonsound1.play()
# similar play sound when mouse unhovers button
#if previous_value is True and self.hovered is False:
# unhover_sound1.play()

Quit python program with a button and a callback method

I feel like I'm making a dumb error, but I can't seem to figure it out.
Here is the code:
class menu:
hover = False
def __init__(self, text, pos):
self.text = text
self.pos = pos
self.set_rect()
self.draw()
def draw(self):
self.set_render()
screen.blit(self.render, self.rect)
def set_render(self):
self.render = subFont.render(self.text, True, self.get_color())
def get_color(self):
if self.hover:
return (BLACK)
else:
return (GREEN)
def set_rect(self):
self.set_render()
self.rect = self.render.get_rect()
self.rect.topleft = self.pos
select = [menu("Computer Virus", (100, 200)),
menu("Computer Crime", (100, 300)),
menu("QUIT", (100, 400))]
running = True
while running:
for evnt in event.get():
if evnt.type == QUIT:
running = False
screen.fill(WHITE)
title()
for menu in select:
if menu.rect.collidepoint(mouse.get_pos()):
menu.hover = True
else:
menu.hover = False
menu.draw()
pointer()
display.update()
but the program would just crash.
How can I make it so that I can quit the screen when clicked on quit?
To quit your game, you can define a function or method in which you set the running variable to False and then pass it to one of the button instances as the callback function.
Here's a more object-oriented example with an App class that has a self.running attribute and a quit_game method. If the user clicks on the button, the quit_game method gets called, self.running is set to False and the main while loop will stop.
import pygame
pygame.init()
# Global constants.
WHITE = pygame.Color('white')
GREEN = pygame.Color('green')
BLACK = pygame.Color('black')
FONT = pygame.font.Font(None, 40)
class Button:
def __init__(self, text, pos, callback):
self.text = text
self.callback = callback
self.image = FONT.render(self.text, True, GREEN)
self.rect = self.image.get_rect(topleft=pos)
def draw(self, screen):
screen.blit(self.image, self.rect)
def handle_event(self, event):
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
if self.rect.collidepoint(event.pos):
self.callback()
elif event.type == pygame.MOUSEMOTION:
if self.rect.collidepoint(event.pos):
self.image = FONT.render(self.text, True, BLACK)
else:
self.image = FONT.render(self.text, True, GREEN)
class App:
def __init__(self):
self.screen = pygame.display.set_mode((640, 480))
self.clock = pygame.time.Clock()
self.select = [
Button("Computer Virus", (100, 200), lambda: print("Computer Virus")),
Button("Computer Crime", (100, 300), lambda: print("Computer Crime")),
Button("QUIT", (100, 400), self.quit_game),
]
self.running = True
def quit_game(self):
self.running = False
def main_loop(self):
while self.running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
for button in self.select:
button.handle_event(event)
self.screen.fill(WHITE)
for button in self.select:
button.draw(self.screen)
pygame.display.update()
self.clock.tick(60)
app = App()
app.main_loop()
pygame.quit()
If you don't want to use the App class, you can also write a function which changes the global running variable. However, note that global variables are usually frowned upon because they make code harder to read and more error prone (in this case it's not that dramatic).
def quit_game():
global running
running = False
# Pass it as the `callback` argument.
Button("QUIT", (100, 400), quit_game)

Categories