I am working on my first python project. I have some buttons, 'Yes' and 'No' after the initial question of "do you want to play?". When I press no it closes the game. When I click yes, I'd like it for it to jump to the next function where it displays the text "so you've decided to play", and although I can see it jump to the next text for a little bit, it quickly switches back to the previous question and buttons of "Do you wish to play?" with the Yes and No buttons. How can I fix this? I've tried to order pygame.display.update() in different ways but so far no luck. Any help would be appreciated, thanks guys. Here is my code:
import pygame
pygame.init()
win=pygame.display.set_mode((800,700))
win.fill((255,255,255))
our_game_display=pygame.Surface((800,700))
font_name = pygame.font.get_default_font()
class button():
def __init__(self, color, x, y, width, height, text=''):
self.color = color
self.x = x
self.y = y
self.width = width
self.height = height
self.text = text
def draw(self, win, outline=None):
# Call this method to draw the button on the screen
if outline:
pygame.draw.rect(win, outline, (self.x - 2, self.y - 2, self.width + 4, self.height + 4), 0)
pygame.draw.rect(win, self.color, (self.x, self.y, self.width, self.height), 0)
if self.text != '':
font = pygame.font.SysFont('comicsans', 60)
text = font.render(self.text, 1, (255, 255, 255))
win.blit(text, (
self.x + (self.width / 2 - text.get_width() / 2), self.y + (self.height / 2 - text.get_height() / 2)))
def isOver(self, pos):
# Pos is the mouse position or a tuple of (x,y) coordinates
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
def yes_no_choice():
#win.fill((0,0,0))
YesButton.draw(win,(255,255,255))
NoButton.draw(win,(255,255,255))
def draw_text(text, size, x, y):
pygame.font.init()
font = pygame.font.Font(font_name, size)
text_surface = font.render(text, True, (255,255,255))
text_rect = text_surface.get_rect()
text_rect.center = (x, y)
our_game_display.blit(text_surface, text_rect)
def beginning_question():
our_game_display.fill((0, 0, 0))
draw_text('The story of this game depends on your choices. Do you wish to play?', 20, 800 / 2, 700 / 2 - 100)
win.blit(our_game_display, (0, 0))
yes_no_choice()
pygame.display.update()
def begin_game():
our_game_display.fill((0,0,0))
draw_text("So you've decided to play...very well.", 20, 800 / 2, 700 / 2 - 100)
win.blit(our_game_display,(0,0))
pygame.display.update()
#game loop
running = True
YesButton=button((0,0,0),100,500,250,100,'Yes')
NoButton=button((0,0,0),450,500,250,100,'No')
while running:
beginning_question()
for event in pygame.event.get():
pos=pygame.mouse.get_pos()
if event.type==pygame.QUIT:
running = False
pygame.quit()
quit()
if event.type==pygame.MOUSEBUTTONDOWN:
if YesButton.isOver(pos):
begin_game()
if NoButton.isOver(pos):
running=False
pygame.quit()
quit()
if event.type==pygame.MOUSEMOTION:
if YesButton.isOver(pos):
YesButton.color=(0,0,139)
elif NoButton.isOver(pos):
NoButton.color=(0,0,139)
else:
YesButton.color=(0,0,0)
NoButton.color=(0,0,0)
The reason why it only flashes for a second and then switches back is because the drawing for the "So you've decided to play" only happens in frames where you click the yes button. It should happen continuously after you hit the yest button. Here is one potential solution:
Have a boolean variable game_begun to keep track of whether the game has begun or not (initially false):
YesButton = button((0, 0, 0), 100, 500, 250, 100, 'Yes')
NoButton = button((0, 0, 0), 450, 500, 250, 100, 'No')
game_begun = False # New variable
After the user clicks the yes button, set that variable to true:
if event.type == pygame.MOUSEBUTTONDOWN:
if YesButton.isOver(pos):
game_begun = True
if NoButton.isOver(pos):
running = False
pygame.quit()
quit()
Finally, in the game loop (not the event loop), have a condition to check if the game has begun, and if so, then call the begin_game function. If not, then draw the beginning question:
if game_begun:
begin_game()
else:
beginning_question()
This might not be the absolute best solution, but it should suffice for your first python project. As you add more states to your game, you should use a string variable to keep track of you game state instead of a boolean. Happy coding!
Related
I'm making a simple text based RPG game in pygame. I'm using a button class (not mine, but fairly simple to use and integrate) to add buttons with choices in them. However, it seems a button on the second slide, the 'Sit and Wait' choice, is overlapping with the 'No' button on the first slide. Since the No button closes the game, pressing on Sit and Wait seems to close the game also. I've tried ordering the if-else statements in different ways, but every other button seems fine. Any tips? Thank you. Here is my code, sorry I know it's a lot but I'm a beginner and not great at condensing yet:
import pygame
pygame.init()
win=pygame.display.set_mode((800,700))
win.fill((255,255,255))
our_game_display=pygame.Surface((800,700))
font_name = pygame.font.get_default_font()
class button():
def __init__(self, color, x, y, width, height, text=''):
self.color = color
self.x = x
self.y = y
self.width = width
self.height = height
self.text = text
def draw(self, win, outline=None):
# Call this method to draw the button on the screen
if outline:
pygame.draw.rect(win, outline, (self.x - 2, self.y - 2, self.width + 4, self.height + 4), 0)
pygame.draw.rect(win, self.color, (self.x, self.y, self.width, self.height), 0)
if self.text != '':
font = pygame.font.SysFont('comicsans', 30)
text = font.render(self.text, 1, (255, 255, 255))
win.blit(text, (self.x + (self.width / 2 - text.get_width() / 2), self.y + (self.height / 2 - text.get_height() / 2)))
def isOver(self, pos):
# Pos is the mouse position or a tuple of (x,y) coordinates
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
def draw_text(text, size, x, y):
pygame.font.init()
font = pygame.font.Font(font_name, size)
text_surface = font.render(text, True, (255,255,255))
text_rect = text_surface.get_rect()
text_rect.center = (x, y)
our_game_display.blit(text_surface, text_rect)
def yell_choice():
our_game_display.fill((0, 0, 0))
draw_text('You yell to see if anyone can hear you.', 20, 800 / 2, 700 / 2 - 100)
win.blit(our_game_display, (0, 0))
pygame.display.update()
def sit_choice():
our_game_display.fill((0,0,0))
draw_text('So you decided to sit and wait', 20, 800 / 2, 700 / 2 - 100)
win.blit(our_game_display,(0,0))
pygame.display.update()
def search_choice():
our_game_display.fill((0,0,0))
draw_text('So you decided to search', 20, 800 / 2, 700 / 2 - 100)
win.blit(our_game_display,(0,0))
pygame.display.update()
def beginning_question():
our_game_display.fill((0, 0, 0))
draw_text('The story of this game depends on your choices. Do you wish to play?', 20, 800 / 2, 700 / 2 - 100)
win.blit(our_game_display, (0, 0))
YesButton.draw(win, (255, 255, 255))
NoButton.draw(win, (255, 255, 255))
pygame.display.update()
def begin_game():
our_game_display.fill((0,0,0))
draw_text("You wake up, or...at least you think you do. Even though your eyes are open,", 20, 800 / 2, 700 / 2 - 300)
draw_text("they still can't detect anything in the complete darkness that surrounds you.", 20, 800 / 2, 700 / 2 - 270)
draw_text("What do you do?", 20, 800 / 2, 700 / 2 - 210)
win.blit(our_game_display,(0,0))
SitWaitButton.draw(win,(255,255,255))
SearchButton.draw(win,(255,255,255))
YellHelpButton.draw(win,(255,255,255))
pygame.display.update()
#game loop
running = True
#color,x,y,width,height
YesButton=button((0,0,0),100,500,250,100,'Yes')
NoButton=button((0,0,0),450,500,250,100,'No')
SitWaitButton=button((0,0,0),500,500,200,50,'Sit and wait..')
SearchButton=button((0,0,0),250,600,600,50,'Get up and try to search your surroundings.')
YellHelpButton=button((0,0,0),250,400,600,50,'Yell to see if anyone is there.')
game_begun='Input' #beginning choice variable
search_option='Input'#search variable
sit_option='Input' #sitting variable
yell_option = 'Input'
while running:
if search_option=='Go':
search_choice()
elif sit_option=='Go':
sit_choice()
elif yell_option=='Go':
yell_choice()
elif game_begun=='Go':
begin_game()
else:
beginning_question()
for event in pygame.event.get():
pos=pygame.mouse.get_pos()
if event.type==pygame.QUIT:
running = False
pygame.quit()
quit()
#yes and no buttons for beginning question
if event.type==pygame.MOUSEBUTTONDOWN:
if SitWaitButton.isOver(pos):
sit_option = 'Go'
print("this button is working")
if SearchButton.isOver(pos):
search_option = 'Go'
print("this button is working")
if YellHelpButton.isOver(pos):
yell_option='Go'
print("this button is working")
if YesButton.isOver(pos):
game_begun='Go'
if NoButton.isOver(pos):
running=False
pygame.quit()
quit()
if event.type==pygame.MOUSEMOTION:
if YellHelpButton.isOver(pos):
YellHelpButton.color = (0, 0, 139)
elif SitWaitButton.isOver(pos):
SitWaitButton.color = (0, 0, 139)
elif SearchButton.isOver(pos):
SearchButton.color = (0, 0, 139)
elif YesButton.isOver(pos):
YesButton.color=(0,0,139)
elif NoButton.isOver(pos):
NoButton.color=(0,0,139)
else:
YesButton.color=(0,0,0)
NoButton.color=(0,0,0)
YellHelpButton.color = (0, 0, 0)
SitWaitButton.color = (0, 0, 0)
SearchButton.color = (0, 0, 0)
You can use an if-elif instruction like below, so that if the user clicks on the mouse while cursor is over the sit and wait button then you don't check if it's also over the quit button (and thus you don't quit the game):
if SitWaitButton.isOver(pos):
sit_option = 'Go'
print("this button is working")
elif NoButton.isOver(pos):
pygame.quit()
quit()
A cleaner solution would however be to remember which buttons are actually currently displayed. For example, you could have a list of visible buttons and check if the cursor is over a button only if this one is in the list.
On a side note, your running variable is useless: your main loop is while running but as soon as running is set to False you just quit the game immediately. So the condition of your loop never evaluates to False.
I know this question has been asked before but I'v tried different approaches and I can't seem to be able to fix it.
I know I have to reset my important global variables in order to correctly restart the game.
The reset of those variables is done when lives == 0 -> I reset the global variables, main menu becomes True and Game False which should put me back in the main menu.
However when I run my game, as soon as the lives reach 0 the code finishes and exits -> Process finished with exit code 0.
The issue must be in my while game loop but I'm unsure what I'm doing wrong.
I'v also tried wrapping my game logic in a function, this seems to mess with my game since I only get the frozen state screen, no enemy spawning and not able to move.
Any ideas what might cause this issue?
Full code:
import pygame
import pygame_gui
import random
# Initialize game window here
pygame.init()
# Variables for the game window
window = pygame.display.set_mode((800, 800))
manager = pygame_gui.UIManager((800, 800))
pygame.display.set_caption('Sea Invaders')
clock = pygame.time.Clock() # Adds the clock for spawn timers
score_font = pygame.font.SysFont('Calibri', 36)
multiplier_font = pygame.font.SysFont('Calibri', 18)
# Sprites, background, UI and music
background = pygame.image.load('Background.jpg')
whale_sprite = pygame.image.load('Whale.png')
speedboat_sprite = pygame.image.load('Speedboat.png')
beam_sprite = pygame.image.load('Beam.png')
life_sprite = pygame.image.load('Life.png')
pirate_boss_sprite_right = pygame.image.load('Pirateboss_right.png')
game_title = pygame_gui.elements.UILabel(relative_rect=pygame.Rect((200, 100), (400, 100)), text='SEA INVADERS',
manager=manager)
description_box = pygame_gui.elements.UILabel(relative_rect=pygame.Rect((150, 250), (500, 100)),
text='Protect the ocean as long as possible by destroying ships',
manager=manager)
start_button = pygame_gui.elements.UIButton(relative_rect=pygame.Rect((350, 400), (100, 50)), text='Start',
manager=manager)
quit_button = pygame_gui.elements.UIButton(relative_rect=pygame.Rect((350, 450), (100, 50)), text='Quit',
manager=manager)
# Classes
class Player():
def __init__(self, x, y, width, height):
# Variables for the Whale go here
self.x = x
self.y = y
self.width = width
self.height = height
self.mov_speed = 5
self.hitbox = (self.x, self.y, self.width, self.height)
def draw(self, window):
# This function draws the whale using the sprite and the defined location in the class parameters
window.blit(whale_sprite, (self.x, self.y))
self.hitbox = (self.x, self.y, self.width, self.height)
# Whale hitbox check
# pygame.draw.rect(window, (255, 0, 0), self.hitbox, 2)
class Enemy(pygame.sprite.Sprite):
def __init__(self, width, height, mov_speed):
# Variables for the various enemies
pygame.sprite.Sprite.__init__(self)
self.width = width
self.height = height
self.mov_speed = mov_speed
self.image = speedboat_sprite
self.rect = self.image.get_rect()
self.rect.x = random.randint(64, 734)
self.rect.y = random.randrange(-150, -100)
self.hitbox = (self.rect.x + 22, self.rect.y + 15, 19, 45)
self.alive = True
def update(self):
# This function lets the enemy go forward until the end of the screen
max_distance = 800 - self.height
if self.rect.y < max_distance:
self.rect.y += self.mov_speed
def hit(self):
# This function deletes the sprite from the sprite group when hit by an attack from a player
self.kill()
self.alive = False
class Boss():
def __init__(self, x, y, width, height, mov_speed, hitpoints):
# Variables for the boss objects
self.x = x
self.y = y
self.width = width
self.height = height
self.mov_speed = mov_speed
self.hitpoints = hitpoints
self.hitbox = (self.x + 30, self.y + 50, 225, 160) # Dimensions of the hitbox to make it close to the model
self.alive = False
self.killed = False
self.end_reached = False
def draw(self, window):
if self.alive and not self.killed:
window.blit(pirate_boss_sprite_right, (self.x, self.y))
self.hitbox = (self.x + 30, self.y + 50, 225, 160)
pygame.draw.rect(window, (255, 0, 0),
(self.x + ((self.width / 2) - (self.hitpoints / 2)), self.y + 220, 50, 10))
pygame.draw.rect(window, (0, 255, 0),
(self.x + ((self.width / 2) - (self.hitpoints / 2)), self.y + 220, self.hitpoints, 10))
# Pirate Boss hitbox check
# pygame.draw.rect(window, (255, 0, 0), self.hitbox, 2)
def move(self):
# This function moves the boss forward. As soon as the boss reaches the end it spawns lower on the screen.
# If the end is reached the model is deleted and the end_reached parameter is set tot True.
max_distance = 800 - self.height
if self.x < (800 + self.width):
self.x += self.mov_speed
else:
self.y += 100
self.x = -51
if self.y >= max_distance:
self.alive = False
self.end_reached = True
def hit(self):
# As soon as the Whale hitpoints reaches 0, the model is overwritten on screen.
if self.hitpoints <= 0:
self.killed = True
return True
class Projectile():
def __init__(self, x, y, width, height, speed, damage, image):
# Variables for the player attacks (ranged)
self.x = x
self.y = y
self.width = width
self.height = height
self.speed = speed
self.damage = damage
self.image = image
self.rect = self.image.get_rect()
def draw(self, window):
window.blit(self.image, (self.x, self.y))
def checkCollision(self, enemy):
# This function checks collision with enemies that belong to a Sprite Group.
self.rect.x = int(self.x)
self.rect.y = int(self.y)
# noinspection PyTypeChecker
return any(pygame.sprite.spritecollide(self, enemy, True))
class Hud():
# class to place the HUDS on the screen (lives, score)
def __init__(self, x, y, sprite):
self.x = x
self.y = y
self.sprite = sprite
def draw(self, window):
window.blit(self.sprite, (self.x, self.y))
# Methods
def redrawUI():
window.blit(background, (0, 0)) # Loads in the background
whale.draw(window) # Draws the Whale in the game
window.blit(speedboat_sprite, (100, 100))
window.blit(speedboat_sprite, (620, 100))
manager.draw_ui(window)
if lives == 0:
text = f'Game over! Your score: {score}.'
game_over = score_font.render(text, True, (0, 0, 0))
window.blit(game_over, (200, 200))
pygame.display.update()
def redrawGameWindow():
window.blit(background, (0, 0)) # Loads in the background
whale.draw(window) # Draws the Whale in the game
scoreboard = score_font.render(str(score), True, (255, 255, 255))
multiplier_message = multiplier_font.render(f'multiplier: {multiplier}', True, (255, 255, 255))
window.blit(scoreboard, (700, 760))
window.blit(multiplier_message, (85, 766))
x_life = 0
# This for loop places the lives next to eachother on the HUD
for life in range(lives):
life = Hud(x_life, 760, life_sprite)
x_life += 25
life.draw(window)
for b in bosses:
b.alive = True
b.draw(window)
b.move()
speedboats.draw(window)
speedboats.update()
for fired_beam in beams:
fired_beam.draw(window)
pygame.display.update() # This updates the above changes to the game window
# Game logic here
main_menu = True
game = False
FPS = 100
whale = Player(334, 650, 128, 128) # Spawns the Player at the start of the game in the middle of the screen
speedboat_locations = (125, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 800)
speedboats = pygame.sprite.Group()
bosses = []
beams = []
cooldown = 0
lives = 3
score = 0
speedboat_hits = 0
multiplier = 1
start = pygame.time.get_ticks()
while main_menu:
time_delta = clock.tick(60) / 1000.0
for event in pygame.event.get(): # this for-loop makes sure the game exits when clicking x
if event.type == pygame.QUIT:
main_menu = False
if event.type == pygame.USEREVENT:
if event.user_type == pygame_gui.UI_BUTTON_PRESSED:
if event.ui_element == start_button:
game = True
main_menu = False
if event.ui_element == quit_button:
main_menu = False
manager.process_events(event)
manager.update(time_delta)
redrawUI()
while game:
for event in pygame.event.get(): # this for-loop makes sure the game exits when clicking x
if event.type == pygame.QUIT:
game = False
# Life check
for speedboat in speedboats:
if speedboat.rect.y >= 734:
speedboat.kill()
lives -= 1
if not speedboat.alive:
speedboats.remove(speedboat)
for pirate_boss in bosses:
if pirate_boss.end_reached and not pirate_boss.killed:
lives = 0
if lives == 0:
whale = Player(334, 650, 128, 128) # Spawns the Player at the start of the game in the middle of the screen
speedboat_locations = (125, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 800)
speedboats = pygame.sprite.Group()
bosses = []
beams = []
cooldown = 0
lives = 3
score = 0
speedboat_hits = 0
multiplier = 1
main_menu = True
game = False
# Basic cooldown for the projectiles of the player
if cooldown > 0:
cooldown += 1
if cooldown > 50:
cooldown = 0
# Collision of attacks
for beam in beams:
if beam.checkCollision(speedboats):
speedboat_hits += 1
score += (25 * multiplier)
beams.pop(beams.index(beam))
for pirate_boss in bosses:
if pirate_boss.alive and not pirate_boss.killed:
if beam.y - beam.width < pirate_boss.hitbox[1] + pirate_boss.hitbox[3] and beam.y + beam.width > \
pirate_boss.hitbox[1]:
if beam.x + beam.height > pirate_boss.hitbox[0] and beam.x - beam.height < pirate_boss.hitbox[0] + \
pirate_boss.hitbox[2]:
pirate_boss.hitpoints -= beam.damage
beams.pop(beams.index(beam))
alive_check = pirate_boss.hit()
if alive_check:
multiplier += 1
bosses.remove(pirate_boss)
if beam.y > 0:
beam.y -= beam.speed # This makes sure the bullet moves forward as long as it is not of the screen
beam.rect.center = (beam.x, beam.y)
else:
beams.pop(beams.index(beam)) # If the bullet goes of the screen it gets removed from the list
# --- PLAYER CONTROLS ---
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and whale.x > whale.mov_speed:
# Makes sure the whale can move left
# and prevents the whale from exiting the screen
whale.x = whale.x - whale.mov_speed
if keys[pygame.K_RIGHT] and whale.x < 800 - whale.mov_speed - whale.width:
# Makes sure the whale can move right
# and prevents the whale from exiting the screen
whale.x = whale.x + whale.mov_speed
if keys[pygame.K_SPACE] and cooldown == 0:
# This block of code takes care of the beam-projectile the player can shoot
if len(beams) < 3:
beams.append(
Projectile(round(whale.x + (whale.width // 2) - (32 // 2)), round(whale.y - (32 // 2)), 32, 32, 2,
10, beam_sprite))
# The beam gets spawned at the whale X/Y Coordinate. To make the beam appear in the middle and at the
# nose we add half the sprites width - half the width of the projectile to the for the x coordinate
# and we use the y coordinate - half the length of the projectile to make the attack spawn at the top
cooldown = 1
# --- ENEMY SPAWNING ---
now = pygame.time.get_ticks()
spawn_time = 3000
speedboat = Enemy(64, 64, 1)
boss = Boss(1, 1, 256, 256, 1, 50)
# Game loop to increase difficulty
if score > 500:
spawn_time = 2000
if score > 1000:
spawn_time = 1500
if score > 5000:
speedboat = Enemy(64, 64, 2)
spawn_time = 2000
if score > 10000:
speedboat = Enemy(64, 64, 2)
spawn_time = 1500
if now - start > spawn_time:
start = now
speedboats.add(speedboat)
if speedboat_hits >= 10:
bosses.append(boss)
speedboat_hits = 0
redrawGameWindow()
clock.tick(FPS)
You are setting game = False, but using that in the while loop. As soon as game is set to False the while loop exits and the game ends. If you want to do it this way, use another variable in the while loop:
running = True
while running:
if game:
game_loop()
elif menu:
menu_loop()
Then use game and menu as switches to decide which thing to show.
This question already has answers here:
Pygame mouse clicking detection
(4 answers)
How do I implement option buttons and change the button color in PyGame?
(2 answers)
How can I add an image or icon to a button rectangle in Pygame?
(1 answer)
Closed 1 year ago.
I was trying to make a button, the code works but I was trying to figure out how you can make the rectangle smaller and move it to the top left corner, I was also wondering how you can make multiple buttons. I would recommend if you could run the code in pycharm, and put the in the code to change the position.
Button Code:
import pygame
pygame.init()
win = pygame.display.set_mode((500, 500))
win.fill((255, 255, 255))
#START
class button():
def __init__(self, color, x, y, width, height, text=''):
self.color = color
self.x = x
self.y = y
self.width = width
self.height = height
self.text = text
def draw(self, win, outline=None):
# Call this method to draw the button on the screen
if outline:
pygame.draw.rect(win, outline, (self.x - 2, self.y - 2, self.width + 4, self.height + 4), 0)
pygame.draw.rect(win, self.color, (self.x, self.y, self.width, self.height), 0)
if self.text != '':
font = pygame.font.SysFont('comicsans', 60)
text = font.render(self.text, 1, (0, 0, 0))
win.blit(text, (
self.x + (self.width / 2 - text.get_width() / 2), self.y + (self.height / 2 - text.get_height() / 2)))
def isOver(self, pos):
# Pos is the mouse position or a tuple of (x,y) coordinates
if pos[1] > self.x and pos[1] < self.x + self.width:
if pos[1] > self.y and pos[1] < self.y + self.height:
return True
def redrawWindow ():
win.fill((255, 255, 255))
greenButton.draw(win, (0, 0, 0))
run = True
greenButton = button((0, 255, 0), 150, 225, 250, 100, 'Start')
while run:
redrawWindow()
pygame.display.update()
for event in pygame.event.get():
pos = pygame.mouse.get_pos()
if event.type == pygame.QUIT:
run = False
pygame.quit()
quit()
if event.type == pygame.MOUSEBUTTONDOWN:
if greenButton.isOver(pos):
print('clicked')
if event.type == pygame.MOUSEMOTION:
if greenButton.isOver(pos):
greenButton.color = (0,100,0)
else:
greenButton.color = (0, 255, 0)
I'm using Python 3.7.4 and Pygame 1.9.6. This is kind of my first time with an object oriented in Python; and same with creating buttons. I'm just testing this, because I want to create a button for my Space Invaders game. I'm just unsure if this is an easy way to do it or what. And when I want to create another button. Do I just do what I did with the 'play_again_button'.
I came up with:
import pygame
class button():
def __init__(self, color, x, y, width, height, text=''):
self.color = color
self.x = x
self.y = y
self.width = width
self.height = height
self.text = text
def draw(self, screen, outline=None):
# Call this method to draw the button on the screen
if outline:
pygame.draw.rect(screen, outline, (self.x - 2, self.y - 2, self.width + 4, self.height + 4), 0)
pygame.draw.rect(screen, self.color, (self.x, self.y, self.width, self.height), 0)
if self.text != '':
font = pygame.font.SysFont('freesansbold.ttf', 60)
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 isOver(self, pos):
# Pos is the mouse position or a tuple of (x,y) coordinates
if self.x < pos[0] < self.x + self.width:
if self.y < pos[1] < self.y + self.height:
return True
return False
pygame.init()
screen = pygame.display.set_mode((800, 600))
running = True
play_again_button = button((20, 20, 20), (340, 340), 20, 30, 20, 'Play again')
while running:
pygame.display.update()
play_again_button.draw(screen)
screen.fill((255, 255, 255))
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.MOUSEBUTTONDOWN:
if play_again_button.isOver(1):
print("Clicked button")
if event.type == pygame.MOUSEMOTION:
if play_again_button.isOver(0):
play_again_button.color = (255, 0, 0)
else:
play_again_button.color = (0, 255, 0)
Anyways let me know if I should do a button like this or not. I always appreciate the feed back.
Making a button as an object is a good idea because it makes the code easier to understand, as it wraps up all the button properties into a single data structure, with member functions to act on it.
There's a couple of bugs in your code but it's mostly OK.
When you create the button, you're passing too many parameters, and some are tuples rather than individual integers:
play_again_button = button((20, 20, 20), (340, 340), 20, 30, 20, 'Play again') # BROKEN
play_again_button = button((20, 20, 20), 340, 340, 20, 30, 'Play again') # FIXED
Where the code does the mouse-position check, it's passing 1 or 0, and not the mouse event.pos, this is an easy fix:
if play_again_button.isOver( 1 ):
if play_again_button.isOver( event.pos ): # FIXED
Similarly for the mouse click-check.
And Finally the screen-painting is being done in a reverse order, so that the buttons is drawn before the screen is erased, so you never see the button.
Below is code that works. I moved the offset border code inside the button class.
According to Python Style Guide PEP8, class-names Should Be Capitalised too.
Ref:
import pygame
class Button():
def __init__(self, color, x, y, width, height, text='', outline=(0,0,0)):
self.color = color
self.x = x
self.y = y
self.width = width
self.height = height
self.text = text
self.outline = outline
def draw(self, screen):
# Call this method to draw the button on the screen
if self.outline:
pygame.draw.rect(screen, self.outline, (self.x - 2, self.y - 2, self.width + 4, self.height + 4), 0 )
pygame.draw.rect(screen, self.color, (self.x, self.y, self.width, self.height), 0)
if self.text != '':
font = pygame.font.SysFont('freesansbold.ttf', 60)
text = font.render(self.text, 1, (0, 0, 0))
screen.blit(text, (
self.x + int(self.width / 2 - text.get_width() / 2), self.y + int(self.height / 2 - text.get_height() / 2)))
def setColor( self, new_colour ):
self.color = new_color
def isOver(self, pos):
# Pos is the mouse position or a tuple of (x,y) coordinates
if self.x < pos[0] < self.x + self.width:
if self.y < pos[1] < self.y + self.height:
return True
return False
pygame.init()
screen = pygame.display.set_mode((800, 600))
running = True
DARK_GREY= ( 20, 20, 20 )
play_again_button = Button( DARK_GREY, 340, 340, 20, 30, 'Play again')
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.MOUSEBUTTONDOWN:
if play_again_button.isOver( event.pos ):
print("Clicked button")
if event.type == pygame.MOUSEMOTION:
if play_again_button.isOver( event.pos ):
play_again_button.setColor( (255, 0, 0) )
else:
play_again_button.setColor( (0, 255, 0) )
screen.fill((255, 255, 255))
play_again_button.draw(screen)
pygame.display.update()
This question already has an answer here:
How to detect when a rectangular object, image or sprite is clicked
(1 answer)
Closed 2 years ago.
I am new to programming , PyGame and OOP especially. I can't figure out how to make a button execute a specific command in PyGame. I tried making a class for a button, and if you look at this code, I am able to execute the hover method/function as an example, but I am struggling to make the game close when I press the exit button. I can't seem to understand how to pass an argument that would make the main_menu be false for when exit is executed and main_game be true when play is executed.
from ColorsAndCoordinates import *
pygame.init()
screen = pygame.display.set_mode((1000, 700))
font = pygame.font.Font("freesansbold.ttf", 42)
class Button:
main_menu = True
def __init__(self, color, x, y, width, height, text):
self.color = color
self.x = x
self.y = y
self.width = width
self.height = height
self.text = text
def display(self, color):
self.color = color
pygame.draw.rect(screen, self.color, (self.x, self.y, self.width, self.height))
text = font.render(self.text, True, red)
screen.blit(text, (self.x, self.y))
def hover(self, color):
mouse = pygame.mouse.get_pos()
if self.x + self.width > mouse[0] > self.x and self.y + self.height > mouse[1] > self.y:
Button.display(self, color)
def clicked(self):
mouse = pygame.mouse.get_pos()
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
if self.x + self.width > mouse[0] > self.x and self.y + self.height > mouse[1] > self.y:
pass
play_button = Button(blue, 200, 300, 95, 46, "Play")
exit_button = Button(blue, 700, 300, 95, 46, "Exit")
tutorial_button = Button(blue, 410, 550, 165, 46, "Tutorial")
main_menu = True
main_game = False
while main_menu:
screen.fill(black)
play_button.display(blue)
exit_button.display(blue)
tutorial_button.display(blue)
play_button.hover(black)
exit_button.hover(black)
tutorial_button.hover(black)
for event in pygame.event.get():
if event.type == pygame.QUIT:
main_menu = False
exit_button.clicked()
pygame.display.update()
There are different solutions for this, for instance polymorphism, an action or an event.
An obvious and simple solution, is to add an action argument to the the method clicked. The argument is an action function, which is invoked when the button is clicked:
class Button:
# [...]
def clicked(self, action = None):
mouse = pygame.mouse.get_pos()
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
if self.x + self.width > mouse[0] > self.x and self.y + self.height > mouse[1] > self.y:
if action:
action()
Create a function, which changes the states. Consider to use the global statement for the variables in global name space main_menu and main_game:
def action_exit():
global main_menu, main_game
main_menu = False
main_game = True
Pass the action to the exit_button.clicked:
while main_menu:
# [...]
exit_button.clicked(action_exit)
Furthermore changed Button.display(self, color) to self.display(color) in the method display().
Complete example:
import pygame
from pygame.locals import *
pygame.init()
screen = pygame.display.set_mode((1000, 700))
font = pygame.font.Font("freesansbold.ttf", 42)
red = (255, 0, 0)
green = (0, 255, 0)
blue = (0, 0, 255)
black = (0, 0, 0)
class Button:
main_menu = True
def __init__(self, color, x, y, width, height, text):
self.color = color
self.x = x
self.y = y
self.width = width
self.height = height
self.text = text
def display(self, color):
self.color = color
pygame.draw.rect(screen, self.color, (self.x, self.y, self.width, self.height))
text = font.render(self.text, True, red)
screen.blit(text, (self.x, self.y))
def hover(self, color):
mouse = pygame.mouse.get_pos()
if self.x + self.width > mouse[0] > self.x and self.y + self.height > mouse[1] > self.y:
self.display(color)
def clicked(self, action = None):
mouse = pygame.mouse.get_pos()
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
if self.x + self.width > mouse[0] > self.x and self.y + self.height > mouse[1] > self.y:
if action:
action()
def action_exit():
global main_menu, main_game
main_menu = False
main_game = True
play_button = Button(blue, 200, 300, 95, 46, "Play")
exit_button = Button(blue, 700, 300, 95, 46, "Exit")
tutorial_button = Button(blue, 410, 550, 165, 46, "Tutorial")
main_menu = True
main_game = False
while main_menu:
screen.fill(black)
play_button.display(blue)
exit_button.display(blue)
tutorial_button.display(blue)
play_button.hover(black)
exit_button.hover(black)
tutorial_button.hover(black)
for event in pygame.event.get():
if event.type == pygame.QUIT:
main_menu = False
exit_button.clicked(action_exit)
pygame.display.update()
make main_menu and main_game variable global inside your clicked function
def clicked(self):
global main_menu, main_game
mouse = pygame.mouse.get_pos()
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
if self.x + self.width > mouse[0] > self.x and self.y + self.height > mouse[1] > self.y:
main_menu = False
main_game = True
Here's an example that I wrote for another question. I changed it to have a button quit the game:
import pygame
pygame.init()
display_width = 1200
display_height = 600
# use python style variable names (lowercase)
screen = pygame.display.set_mode((display_width, display_height))
pygame.display.set_caption('Log In')
clock = pygame.time.Clock()
# load the font only once instead of every frame
font = pygame.font.SysFont('comicsans', 20)
# class name should be singular
class Button(pygame.sprite.Sprite):
# 1) no need to have 4 parameters for position and size, use pygame.Rect instead
# 2) let the Button itself handle which color it is
# 3) give a callback function to the button so it can handle the click itself
def __init__(self, color, color_hover, rect, callback, text='', outline=None):
super().__init__()
self.text = text
# a temporary Rect to store the size of the button
tmp_rect = pygame.Rect(0, 0, *rect.size)
# create two Surfaces here, one the normal state, and one for the hovering state
# we create the Surfaces here once, so we can simple blit them and dont have
# to render the text and outline again every frame
self.org = self._create_image(color, outline, text, tmp_rect)
self.hov = self._create_image(color_hover, outline, text, tmp_rect)
# in Sprites, the image attribute holds the Surface to be displayed...
self.image = self.org
# ...and the rect holds the Rect that defines it position
self.rect = rect
self.callback = callback
def _create_image(self, color, outline, text, rect):
# function to create the actual surface
# see how we can make use of Rect's virtual attributes like 'size'
img = pygame.Surface(rect.size)
if outline:
# here we can make good use of Rect's functions again
# first, fill the Surface in the outline color
# then fill a rectangular area in the actual color
# 'inflate' is used to 'shrink' the rect
img.fill(outline)
img.fill(color, rect.inflate(-4, -4))
else:
img.fill(color)
# render the text once here instead of every frame
if text != '':
text_surf = font.render(text, 1, pygame.Color('black'))
# again, see how easy it is to center stuff using Rect's attributes like 'center'
text_rect = text_surf.get_rect(center=rect.center)
img.blit(text_surf, text_rect)
return img
def update(self, events):
# here we handle all the logic of the Button
pos = pygame.mouse.get_pos()
hit = self.rect.collidepoint(pos)
# if the mouse in inside the Rect (again, see how the Rect class
# does all the calculation for use), use the 'hov' image instead of 'org'
self.image = self.hov if hit else self.org
for event in events:
# the Button checks for events itself.
# if this Button is clicked, it runs the callback function
if event.type == pygame.MOUSEBUTTONDOWN and hit:
self.callback(self)
run = True
# we store all Sprites in a Group, so we can easily
# call the 'update' and 'draw' functions of the Buttons
# in the main loop
sprites = pygame.sprite.Group()
sprites.add(Button(pygame.Color('green'),
pygame.Color('red'),
pygame.Rect(150, 200, 90, 100),
lambda b: print(f"Button '{b.text}' was clicked"),
'Press',
pygame.Color('black')))
def close(*args):
global run
run = False
sprites.add(Button(pygame.Color('dodgerblue'),
pygame.Color('lightgreen'),
pygame.Rect(300, 200, 90, 100),
close,
'Close'))
while run:
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
pygame.quit()
quit()
# update all sprites
# it now doesn't matter if we have one or 200 Buttons
sprites.update(events)
# clear the screen
screen.fill(pygame.Color('white'))
# draw all sprites/Buttons
sprites.draw(screen)
pygame.display.update()
# limit framerate to 60 FPS
clock.tick(60)
We simply pass the Button-class a callback function that gets called if the button is clicked. For closing the window, we call a little function that captures the run variable and sets it to False.