Related
I'm attempting to create a pygame Tic-Tac-Toe game. My issue is in my 'mousemovement()' method. Whenever i use the 'pygame.mouse.getpos()' in my 'collidepoint()' method it gives me a type error and says that my tuples index is out of range. Anyone have any ideas?
import pygame
pygame.init()
pygame.font.init()
pygame.display.set_caption("Tic-Tac-Toe")
pygame.mouse.set_visible(True)
FPS = 60
clock = pygame.time.Clock()
WIDTH,HEIGHT = 500,600
BORDER_THICKNESS = 10
WIN = pygame.display.set_mode((WIDTH,HEIGHT))
#delcare the fonts of the characters
GAME_FONT = pygame.font.SysFont("comicsans",100)
#colors
WHITE = (255,255,255)
BLACK = (0,0,0)
GREEN = (0,255,0)
def display_window(squares,marks,turn):
WIN.fill(WHITE)
starting_pos = WIDTH // 3
for l in range(2): #draws the board
vert = pygame.Rect((starting_pos - BORDER_THICKNESS//2,0),(BORDER_THICKNESS,HEIGHT))
hrzn = pygame.Rect((0,starting_pos - BORDER_THICKNESS//2),(WIDTH,BORDER_THICKNESS))
starting_pos = starting_pos * 2 + BORDER_THICKNESS//2
pygame.draw.rect(WIN,BLACK,vert)
pygame.draw.rect(WIN,BLACK,hrzn)
#draws black background for text box
more_border = pygame.Rect((0,WIDTH),(WIDTH,HEIGHT - WIDTH))
pygame.draw.rect(WIN,BLACK,more_border)
#draws actual text box
text_box = pygame.Rect((0+BORDER_THICKNESS,WIDTH+BORDER_THICKNESS),(WIDTH-BORDER_THICKNESS*2,HEIGHT - WIDTH))
pygame.draw.rect(WIN,WHITE,text_box)
#display game squares
for s in squares:
pygame.draw.rect(WIN,GREEN,s)
#prints the marks of x's and o's
for m in marks:
pass #still working on printing the marks
pygame.display.update()
#use the mouse methods in order to retrive location in which
#mouse is at and see if it collides with a given square
def mouse_movement(squares,mouse_presses,marks,turn):
for s in squares:
if s.collidepoint(pygame.mouse.get_pos()) and mouse_presses[pygame.MOUSEBUTTONDOWN]:
if turn % 2 == 1:
marks[squares.index(s)] = 'X'
elif turn % 2 == 0:
marks[squares.index(s)] = 'O'
else:
raise TypeError("Neither condition is being met")
def main():
turn = 1
x, y = 0,0
squares = []
for c in range(3):
for r in range(3):
squares.append(pygame.Rect((x,y),(WIDTH//3-BORDER_THICKNESS//2,WIDTH//3-BORDER_THICKNESS//2)))
x += WIDTH//3 - BORDER_THICKNESS//2 + BORDER_THICKNESS
x = 0
y += WIDTH//3 - BORDER_THICKNESS//2 + BORDER_THICKNESS
marks = ['','','','','','','','','']
game_going = True
while game_going:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_going = False
pygame.quit()
print(pygame.mouse.get_pos())
mouse_presses = pygame.mouse.get_pressed()
marks = mouse_movement(squares,mouse_presses,marks,turn)
display_window(squares,marks,turn)
if __name__ == '__main__':
main()
I searched the pygame website to make sure that the 'pygame.mouse.get_pos()' method returns a tuple and it does. I'm not quite sure where to go from here.
You get the out of range exception because of mouse_presses[pygame.MOUSEBUTTONDOWN].
pygame.mouse.get_pressed() returns a list of Boolean values that represent the state (True or False) of all mouse buttons. The state of a button is True as long as a button is held down. When multiple buttons are pressed, multiple items in the list are True. The 1st, 2nd and 3rd elements in the list represent the left, middle and right mouse buttons.
If you want to test if the left mouse button is pressed it is:
if mouse_presses[0]:
If you want to test if any button is pressed it is:
if any(mouse_presses):
However, this is not how the MOUSEBUTTONDOWN event works. The MOUSEBUTTONDOWN event occurs once when you click the mouse button and the MOUSEBUTTONUP event occurs once when the mouse button is released. The pygame.event.Event() object has two attributes that provide information about the mouse event. pos is a tuple that stores the position that was clicked. button stores the button that was clicked. Each mouse button is associated a value. For instance the value of the attributes is 1, 2, 3, 4, 5 for the left mouse button, middle mouse button, right mouse button, mouse wheel up respectively mouse wheel down. When multiple keys are pressed, multiple mouse button events occur. Further explanations can be found in the documentation of the module pygame.event.
e.g.:
game_going = True
while game_going:
# [...]
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_going = False
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1: # left button
print(event.pos) # mouse position
# [...]
I dont know how I can use my button function to either overlay the background.jpg back over the buttons or wipe the current screen and put the background back in place after the scene has been cleared.
import pygame
pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()
BLACK = (0, 0, 0)
BACKGROUND = (200, 230, 234)
WHITE = (255, 255, 255)
HOVER_COLOUR = (50, 70, 90)
# Text Variables
FONT = pygame.font.SysFont ("Times New Norman", 60)
TEXT = FONT.render ("", True, WHITE)
background_images = pygame.image.load("background.jpg").convert()
screen.blit(background_images, [0,0])
screen.blit(TEXT, (150, 50))
# Text & Rectangles construction
text1 = FONT.render("PlAY", True, WHITE)
text2 = FONT.render("CONTROLS", True, WHITE)
text3 = FONT.render("DIFFICULTY", True, WHITE)
text4 = FONT.render("SCOREBOARD", True, WHITE)
rect1 = pygame.Rect(250,200,300,80)
rect2 = pygame.Rect(250,300,300,80)
rect3 = pygame.Rect(250,400,300,80)
rect4 = pygame.Rect(250,500,300,80)
# The button construction arry. Text and Rectangle
buttons = [
[text1, rect1, BACKGROUND, 1],
[text2, rect2, BACKGROUND, 2],
[text3, rect3, BACKGROUND, 3],
[text4, rect4, BACKGROUND, 4],
]
# Function for button printing (testing)
def on_button(buttons):
print(buttons[3])
def game_intro():
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
return
elif event.type == pygame.MOUSEMOTION:
for button in buttons:
# Uses collisionpoint to detect mouse position collisions
if button[1].collidepoint(event.pos):
# Set the button's colour to the hover colour.
button[2] = HOVER_COLOUR
else:
# resets the colour to normal.
button[2] = BACKGROUND
# Button Controls
elif event.type == pygame.MOUSEBUTTONDOWN:
for button in buttons:
# Uses collisionpoint to detect mouse position collisions
if button[1].collidepoint(event.pos):
on_button(button)
if button == buttons[0]:
screen.fill(0,0,0)
# Draws the buttons with their current colours (normal & collisions)
for text, rect, colour, button_id in buttons:
pygame.draw.rect(screen, colour, rect)
screen.blit(text, rect)
pygame.display.flip()
clock.tick(15)
#Run Game
game_intro()
pygame.quit()
As you can see the operation:
if button == buttons[0]:
screen.fill(0,0,0)
Is what im currently working with. The if statement works fine and iv tested its feedback with print operations but i cannot work it with Pygame functions.
The issue is caused by
screen.fill(0,0,0)
because the 2nd parameter to pygame.Surface.fill() is assumed to be a rectangle (e.g. pygame.Rect), which limits the fill to a specific area.
The 1st parameter to pygame.Surface.fill() has to be a RGB sequence, RGBA sequence or a color index.
So it has to be
screen.fill( (0,0,0) )
or
screen.fill(0)
The buttons are still they, because they are drawn continuously in every frame:
for text, rect, colour, button_id in buttons:
pygame.draw.rect(screen, colour, rect)
screen.blit(text, rect)
Add a global state variable (play) which is set when the play button is pressed. Change the state in the function on_button, use the global statement to change the value of the globale variable play. Draw the scene dependent on the state:
play = False
def on_button(buttons):
global play
play = buttons[3] == 1
print(buttons[3], play)
def game_intro():
# [...]
if play:
screen.fill(0)
# [...]
else:
for text, rect, colour, button_id in buttons:
pygame.draw.rect(screen, colour, rect)
screen.blit(text, rect)
pygame.display.flip()
clock.tick(15)
To directly answer the question:
if button[1].collidepoint(event.pos):
on_button(button)
if button == buttons[0]:
screen.fill(0,0,0)
Check your indentation. For each button, the code does the .collidepoint check and possibly calls on_button, and then it also checks which button is being examined - regardless of the .collidepoint result.
if button[1].collidepoint(event.pos):
on_button(button)
if button == buttons[0]:
screen.fill(0,0,0)
Now the screen.fill only happens if both conditions are true - i.e. the button being examined is buttons[0], and the event.pos (i.e., the place where the user clicked) is inside that button.
But to deal with the problem - you really should use something more sophisticated to represent your buttons. Basically, what we would like to happen is for the on_button code to make the decision of what is done when the button is clicked, according to which button it is. To make that work smoothly, the buttons info needs to include something that tells on_button what to do.
Python allows us to do a neat trick here: names of things are just names, even if the thing being named is a function - and that means, for example, that we can put those names in a list, and then pull them out and use them to call the function. For example, if we had a function that explains what the Play button should do:
def do_play():
# up to you ;)
And then set up the button to store that name, instead of a button ID:
play_button = [text1, rect1, BACKGROUND, do_play]
Now we can have on_button figure it out for us:
def on_button(button):
button[3]()
When the .collidepoint test passes for that button, it gets passed to on_button, which looks up the function name do_play and calls that function. Simple :)
(The next level of sophistication is to use a class to represent the Button information instead of a plain list.)
You might also find some useful ideas here:
How to make buttons in python/pygame?
https://www.pygame.org/tags/button
So I am writing code for a a Stealth Game in python, I am using the pygame module for it but this problem may not even concern it. I have stages for my menu where I am taking an OOP approach (I am new to OOP so please don't hate so much on my code!) where buttons are generated for a menu where there is a play and quit button. Then Easy, medium, hard and veteran buttons are loaded on top of the old buttons (where the previous buttons lose functionality) and then the 1, 2 , 3 and 4 buttons for levels are loaded again on top of the previous ones if that makes sense. However, the way I wrote my code, I need for the variable stage where stage 1 is the play and quit, 2 is the difficulty and 3 is the level number to be passed out the method and class it would need to be in. I don't know how to do this without declaring the variable with a global scope which defeats the purpose of OOP I think. So how do I? Because otherwise the code just takes input and goes through all the buttons to the end.
Here is my code, you would need images I guess of at least the text files.
#Stealth Assassin
import pygame #Imports the pygame module inclulding many in built functions that aids in game design
import time #Imports the time module for which I can implement delays into my program
pygame.init() #Runs pygame
clock = pygame.time.Clock() #Intialises the variable to control the game clock (FPS)
gameDisplay = pygame.display.set_mode((1920,1080),pygame.FULLSCREEN) #Variable which will set the resolution of the game window and put the window into fullscreen mode
pygame.display.set_caption("Stealth Assassin") #Sets the title of the pygame window for the game
class DisplayImage: #This class contains methods required to load images into the game
def __init__(self, filename, xpos, ypos): #Method used to allow the class to intialise attributes
self.filename = filename #Defines filename as the filename attribute passed through
self.xpos = xpos #Defines the x axis positioning of the image as the attribute passed through
self.ypos = ypos #Defines the y axis positioning of the image as the attribute passed through
def LoadImage(self): #This method will load images into the game displaying them
image = pygame.image.load(self.filename+".png") #Image is loaded into the program
gameDisplay.blit(image, (self.xpos,self.ypos)) #Image is displayed to coordinates which were attributes that were defined prior
stage = 1 #Sets the menu as stage 1 which is the play and quit buttons
class Button: #This class contains methods for buttons including display and functionality
def __init__(self, buttonname, buttonx, buttony, buttonwidth, buttonheight, textfile, textx, texty, stage): #Methods used to allow classes to intialise attributes
self.buttonname = buttonname #Defines the name of the button as the attribute passed through
self.buttonx = buttonx #Defines the x axis positioning of the button as the attribute passed through
self.buttony = buttony #Defines the y axis positioning of the button as the attribute passed through
self.buttonwidth = buttonwidth #Defines the width of the button as the attribute passed through
self.buttonheight = buttonheight #Defines the height of the button as the attribute passed through
self.textfile = textfile #Sets the name of the textfile to be called
self.textx = textx #Defines the x axis positioning of the text as the attribute passed through
self.texty = texty #Defines the y axis positioning of the text as the attribute passed through
self.stage = stage #Sets the stage of the menu which has 3 states of play/quit, difficulty and level choice
def createbutton(self): #Method which creates a button for the menu
buttonname = pygame.draw.rect(gameDisplay, (0,0,0), [self.buttonx, self.buttony, self.buttonwidth, self.buttonheight]) #Draws a rectangular button which is black and given the size and coordinates which were attributes
text = pygame.image.load(self.textfile+".png") #Loads the text file into the program
gameDisplay.blit(text, (self.textx,self.texty)) #Displays the text given coordinates
def quitbutton(self): #Method which quits the program if the quit button is clicked
if self.buttonx+self.buttonwidth > mouse[0] > self.buttonx and self.buttony+self.buttonheight > mouse[1] > self.buttony and pressed[0] == 1: #If the button is clicked (regarding its dimensions)
pygame.quit() #Exits pygame
quit() #Quits program
def buttonaction(self): #Method which takes action for the particular button
if self.buttonx+self.buttonwidth > mouse[0] > self.buttonx and self.buttony+self.buttonheight > mouse[1] > self.buttony and pressed[0] == 1: #If the button is clicked (regarding its dimensions)
if self.stage == 1: #If the play/quit buttons are active
EasyButton.createbutton() #Creates and displays the easy button through the button class and its method
MediumButton.createbutton() #Creates and displays the medium button through the button class and its method
HardButton.createbutton() #Creates and displays the hard button through the button class and its method
VeteranButton.createbutton() #Creates and displays the veteran button through the button class and its method
if self.stage == 2: #If the difficulty buttons are active
OneButton.createbutton() #Creates and displays the one button through the button class and its method
TwoButton.createbutton() #Creates and displays the two button through the button class and its method
ThreeButton.createbutton() #Creates and displays the three button through the button class and its method
FourButton.createbutton() #Creates and displays the four button through the button class and its method
if self.buttonname == 'easybutton':
difficulty = 'easy'
if self.buttonname == 'mediumbutton':
difficulty = 'medium'
if self.buttonname == 'hardbutton':
difficulty = 'hard'
if self.buttonname == 'veteranbutton':
difficulty = 'veteran'
print(difficulty)
time.sleep(0.5)
PlayButton = Button('playbutton',133,477,756,223,'PlayText',387,545,1) #Creates play button
QuitButton = Button('quitbutton',133,731,756,223,'QuitText',387,806,None) #Creates quit button
EasyButton = Button('easybutton',127,477,362,223,'EasyText',214,548,2) #Creates easy button
MediumButton = Button('mediumbutton',533,477,362,223,'MediumText',560,548,2) #Creates medium button
HardButton = Button('hardbutton',127,727,362,223,'HardText',214,806,2) #Creates hard button
VeteranButton = Button('veteranbutton',533,727,362,223,'VeteranText',537,806,2) #Creates veteran button
OneButton = Button('onebutton',127,477,362,223,'OneText',287,550,3) #Creates the level 1 button
TwoButton = Button('twobutton',533,477,362,223,'TwoText',693,550,3) #Creates the level 2 button
ThreeButton = Button('threebutton',127,727,362,223,'ThreeText',285,810,3) #Creates the level 3 button
FourButton = Button('fourbutton',533,727,362,223,'FourText',685,810,3) #Creates the level 4 button
PlayButton.createbutton() #Creates the play button through the button class and its method
QuitButton.createbutton() #Creates the play button through the button class and its method
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
mouse = pygame.mouse.get_pos() #Gets the x and y coordinates of the mouse cursor
pressed = pygame.mouse.get_pressed() #Checks if the mouse has been pressed
PlayButton.buttonaction() #Checks if the playbutton needs action
QuitButton.quitbutton() #Checks if the quitbutton needs action
EasyButton.buttonaction() #Checks if the easybutton needs action
MediumButton.buttonaction() #Checks if the mediumbutton needs action
HardButton.buttonaction() #Checks if the hardbutton needs action
VeteranButton.buttonaction() #Checks if the veteranbutton needs action
OneButton.buttonaction() #Checks if the onebutton needs action
TwoButton.buttonaction() #Checks if the twobutton needs action
ThreeButton.buttonaction() #Checks if the threebutton needs action
FourButton.buttonaction() #Checks if the fourbutton needs action
pressed = [0,0,0]
pygame.display.update()
clock.tick(5)
One of the principals of object oriented design is to keep all the aspects of what the object is modelling/implementing inside the object, but no more. What is the purpose of this "Button" - to be seen on-screen, to accept user input, to signal the state to the main program.
So in terms of this, your button class is mostly OK. However, it's including things inside the button definition that really are not the job of the button, for example:
def quitbutton(self):
#If the button is clicked (regarding its dimensions)
if self.buttonx+self.buttonwidth > mouse[0] > self.buttonx and self.buttony+self.buttonheight > mouse[1] > self.buttony and pressed[0] == 1:
pygame.quit() #Exits pygame
quit() #Quits program
A generic button class should not be responsible for quitting the program, it just needs to click and un-click. Similarly for the code that switches between the Play+Quit and the difficulty level button-sets - this is not the button's job.
I think a better button removes clutter to be simpler:
class Button:
def __init__(self, buttonname, buttonx, buttony, buttonwidth, buttonheight, textfile, textx, texty ):
self.buttonname = buttonname # Name of the button
self.buttonx = buttonx # X-axis position
self.buttony = buttony # Y-axis position
self.buttonwidth = buttonwidth # Width of the button
self.buttonheight= buttonheight # Height of the button
self.text_image = pygame.image.load( textfile+".png" ) # Button Label
self.textx = textx # X-axis positioning of the text
self.texty = texty # Y-axis positioning of the text
def drawButton( self, screen ):
""" Paint the button to the screen """
# Black rectangle the size of the button
pygame.draw.rect( screen, (0,0,0), [self.buttonx, self.buttony, self.buttonwidth, self.buttonheight])
# Overlay the button text onto the background
screen.blit( text_image, ( self.textx, self.texty ) )
def mouseIsOver( self, mouse_position ):
""" Returns true if the mouse is within this buttons area """
inside = self.buttonx+self.buttonwidth > mouse_position[0] > self.buttonx and self.buttony+self.buttonheight > mouse_position[1] > self.buttony
return inside
Then in your main program:
PlayButton = Button('playbutton',133,477,756,223,'rock1_64',387,545,1) #Creates play button
QuitButton = Button('quitbutton',133,731,756,223,'rock1_64',387,806,None) #Creates quit button
EasyButton = Button('easybutton',127,477,362,223,'rock1_64',214,548,2) #Creates easy button
MediumButton = Button('mediumbutton',533,477,362,223,'rock1_64',560,548,2)
...
all_buttons = [ PlayButton, QuitButton, EasyButton, MediumButton ]
# Main Loop
while True:
# Handle user-input
for event in pygame.event.get():
if event.type == pygame.QUIT:
break
if event.type == pygame.MOUSEBUTTONUP:
# The mouse button was clicked, was it inside a button?
click_location = pygame.mouse.get_pos()
for b in all_buttons:
if ( b.mouseIsOver( click_location ) ):
print( "Button [%s] pressed" % ( b.buttonname ) )
# Re-paint screen
gameDisplay.fill( BACKGROUND_COLOUR )
for b in all_buttons:
b.drawButton( gameDisplay )
pygame.display.flip()
clock.tick_busy_loop( 60 ) # Limit FPS
pygame.quit()
quit()
Or failing that, the Button class could post and event to the PyGame queue:
(EDIT: There was a bug in this, it needed to create the event to post, FIXED).
BUTTON_CLICK_EVENT = pygame.USEREVENT + 1
...
class Button ( ... ):
def checkClick( self, mouse_pos ):
""" If the mouse-click is inside our rectangle, post a message to the queue """
if ( self.buttonx+self.buttonwidth > mouse_position[0] > self.buttonx and self.buttony+self.buttonheight > mouse_position[1] > self.buttony ):
pygame.event.post( pygame.event.Event( BUTTON_CLICK_EVENT, { "button_name" : self.buttonname } ) )
...
# Main loop
if event.type == BUTTON_CLICK_EVENT:
print("Clicked "+ event.button_name )
Doing this allows the separation of the button logic from the rest of the program, and prevents needing global variables. Obviously you have a need to switch between banks of buttons, but this is not a problem for the Button object, it should go somewhere else. Perhaps in a ButtonGroup or suchlike.
The code below is supposed to create a green button that makes a score text appear. unfortunately the button does nothing, and the only way I've managed to get it to work is by putting the function call for makeText in the while loop instead of in the clickButton function, but if I do that it's no longer dynamic. Can someone explain why the text isn't showing up when I press the button and fix my code so it does show up?
import pygame
import sys
#game stuff
pygame.init()
screen = pygame.display.set_mode((640, 480),0,32)
clock = pygame.time.Clock()
#functions
def makeText(title,text,posx,posy):
font=pygame.font.Font(None,30)
scoretext=font.render(str(title)+ ": " +str(text), 1,(0,0,0))
screen.blit(scoretext, (posx, posy))
def clickButton(name,x,y,width,height):
if x + width > cur[0] > x and y + height > cur[1] > y:
if click == (1,0,0):
makeText("score",300,100,10)
#objects
button1 = pygame.Rect((0,0), (32,32))
while True:
screen.fill((255,255,255))
screen.fill((55,155,0), button1)
#update display
pygame.display.update()
clock.tick(60)
#event handling
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
elif event.type == pygame.MOUSEBUTTONDOWN:
cur = event.pos
click = pygame.mouse.get_pressed()
clickButton("button1",button1.left,button1.top,button1.width,button1.height)
The problem is that once you created the text, your main loop keeps going and calls screen.fill, overdrawing the text even before pygame.display.update() is called.
You could change it to:
...
def clickButton(name,x,y,width,height):
print x + width > cur[0] > x and y + height > cur[1] > y
if x + width > cur[0] > x and y + height > cur[1] > y:
if click == (1,0,0):
makeText("score",300,100,10)
#objects
button1 = pygame.Rect((0,0), (32,32))
while True:
screen.fill((255,255,255))
screen.fill((55,155,0), button1)
#event handling
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
elif event.type == pygame.MOUSEBUTTONDOWN:
cur = event.pos
click = pygame.mouse.get_pressed()
clickButton("button1",button1.left,button1.top,button1.width,button1.height)
...
so the text is created after filling the screen with the background color and before pygame.display.update() is called, but that does not solve the problem of the screen being filled again the next iteration of the while loop.
So the solution is to keep track of the fact that the button was pressed, a.k.a. keeping track of a state.
Here's an example of a different approach, using classes for the buttons and a dict for the global state (so you don't need global variables, which should you avoid most of the time, because it can get very confusing fast if your game starts becoming more complex).
Click the first button to show or hide the score, and click the second button to change the background color and earn 100 points.
See how easy it becomes to create new buttons; it's just adding a simple function.
import pygame
import sys
import random
pygame.init()
screen = pygame.display.set_mode((640, 480),0,32)
clock = pygame.time.Clock()
# create font only once
font = pygame.font.Font(None,30)
# it's always a good idea to cache all text surfaces, since calling 'Font.render' is
# an expensive function. You'll start to notice once your game becomes more complex
# and uses more text. Also, use python naming conventions
text_cache = {}
def make_text(title, text):
key = "{title}: {text}".format(title=title, text=text)
if not key in text_cache:
text = font.render(key, 1,(0,0,0))
text_cache[key] = text
return text
else:
return text_cache[key]
# we use the 'Sprite' class because that makes drawing easy
class Button(pygame.sprite.Sprite):
def __init__(self, rect, color, on_click):
pygame.sprite.Sprite.__init__(self)
self.rect = rect
self.image = pygame.Surface((rect.w, rect.h))
self.image.fill(color)
self.on_click = on_click
# this happens when the first button is pressed
def toggle_score_handler(state):
state['show_score'] = not state['show_score']
# this happens when the second button is pressed
def toggle_backcolor_handler(state):
state['backcolor'] = random.choice(pygame.color.THECOLORS.values())
state['score'] += 100
# here we create the buttons and keep them in a 'Group'
buttons = pygame.sprite.Group(Button(pygame.Rect(30, 30, 32, 32), (55, 155 ,0), toggle_score_handler),
Button(pygame.Rect(250, 250, 32, 32), (155, 0, 55), toggle_backcolor_handler))
# here's our game state. In a real
# game you probably have a custom class
state = {'show_score': False,
'score': 0,
'backcolor': pygame.color.Color('White')}
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
# you can check for the first mouse button with 'event.button == 1'
elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
# to check if the mouse is inside the button, you
# can simple use the 'Rect.collidepoint' function
for button in (b for b in buttons if b.rect.collidepoint(event.pos)):
button.on_click(state)
screen.fill(state['backcolor'])
# draw all buttons by simple calling 'Group.draw'
buttons.draw(screen)
if state['show_score']:
screen.blit(make_text("score", state['score']), (100, 30))
pygame.display.update()
clock.tick(60)
You are checking the value of "click" in the clickButton function, but I don't see click defined anywhere that clickButton would have access to it.
Perhaps you should pass click as an argument in the clickButton function, which would then possibly make the if condition true?
I have a problem I can't seem to solve. I have tried to find a way to add a function like quitting a program to a rectangle in Pygame. Here is the code I have so far. I would like to add an on click quit feature to the quit box in the corner.
def addRect(self):
self.rect = pygame.draw.rect(self.screen, (white), (300, 200, 300, 200), 2)
pygame.display.update()
def addText(self):
self.screen.blit(self.font.render('Quit', True, (84,84,84)), (550, 375))
pygame.display.update()
I have it working with the bits above and below and it does make a "Quit" Image at the bottom corner where I need it. However, I'm again stuck on the function!
I did something very similar to this, and the way that I handled it was I made a list in the main program that had all of the "inner windows" or whatever you want to call them. Whenever the main program received a signal from a window to close it, it deleted it from the list.
To make the signal, you will want to create a rect in the location where you want the button to be. Make a function for the "inner window" and have it test for that rect being clicked. If it is clicked, have the function return something like 'closed' or whatever you want. In the main program, say something like
for window in windows:
if window.update()=='closed':
windows.remove(window)
to remove any window which is closed.
EDIT:
After looking at your code a bit more in depth, it looks like how you're doing it won't work. To add a rect, you will need to have something in your main code to store whether or not the rect is there. To close the window, you will have to change that variable.
To check if the rect should be closed, make another rect that is where the text which should be closing the window is. When this text is clicked, have the function return something which should be interpreted by the main code to close the window.
A basic example is shown below.
The class:
def update(self):
#set up the test rect
text=self.font.render('Quit', True, (84,84,84))
textrect=text.get_rect()
textrect.topleft=(550, 375)
#see if the button is pressed
if textrect.collidepoint(pygame.mouse.get_pos()) and pygame.mouse.get_pressed()[0]:
return 'closed'
#render stuff
self.rect = pygame.draw.rect(self.screen, (white), (300, 200, 300, 200), 2)
self.screen.blit(text, (550, 375))
Note that I combined your two original classes into one, as I don't see a reason why you would ever want the rect but not the text or vise versa. This is a pretty simple change if you don't like it.
Also note that this will close the window if the mouse is pressed off the button, then dragged onto it. To avoid this, you will have to pass the list gotten from pygame.event.get() as an argument for the update function, and search through it for a MOUSEBUTTONDOWN event, but this would cause unnecessary complications that I tried to avoid.
The main code:
rectOn=False
while True:
if rectOn:
if rect.update()=='closed':
rectOn=False
To make the rect appear again after it has been closed, simply set rectOn to True.
A made a small example that you can work on. Instead of buttons returning something on click, they have a function assigned to the click.
import pygame,sys
from pygame.locals import *
screen_color = (0,0,0)
class Button:
def __init__(self,pos,action):
self.rect = pygame.Rect(pos)
self.action = action
def draw(self,screen):
pygame.draw.rect(screen, (255,255,255), self.rect)
def checkCollide(self,x,y):
return self.rect.collidepoint(x,y)
def do(self):
self.action()
def action():
global screen_color
screen_color = (255,255,0)
pygame.init()
screen = pygame.display.set_mode((640,360),0,32)
buttons = []
buttons.append(Button((10,10,50,50),action))
while True:
screen.fill(screen_color)
for button in buttons:
button.draw(screen)
pygame.display.flip()
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == MOUSEBUTTONDOWN:
x,y = pygame.mouse.get_pos()
for button in buttons:
if (button.checkCollide(x,y)):
button.do()