How to assign a pygame_gui Button to a specific surface - python

I'm currently working on a project based on pygame. It is the first time I'm attempting a project like this. I choose to try the pygmae_gui library in order to (who wold have guessed) implement a GUI to the project.
I successfully added some buttons to the application without any problems. I wrote a part where a new surface pops up when a certain button is pressed (which works without issues).
I want to add buttons on this new surface, but i don't know how.
My code looks something like this:
def _set_up_surfaces():
screen = pygame.display.set_mode((self.width, self.height))
background = pygame.Surface((self.width, self.height))
new_layer = pygame.Surface((440, 360))
def _set_up_click_button():
start_button_rect = pygame.Rect(0, 0, 150, 30)
bye_button_layout_rect = pygame.Rect(0, 0, 150, 30)
bye_button_layout_rect.bottomright = (-30, -20)
close_button_rect = pygame.Rect(320, 320, 150, 30)
start_button = pygame_gui.elements.UIButton(relative_rect=start_button_rect, text="START",manager=gui_manager)
new_layer_close_button = pygame_gui.elements.UIButton(relative_rect=close_button_rect, text='close', manager=self.gui_manager, container=new_layer)
active = True
# set up start display:
new_layer_visible = False
set_up_surfaces()
set_up_click_button()
# main game loop
while active:
for event in pygame.event.get():
if event.type == pygame.QUIT:
active = False
if event.type == pygame_gui.UI_BUTTON_PRESSED:
if event.ui_element == self.start_button:
self.machine_visible = True
if event.ui_element == self.bye_button:
print('Good Bye!')
active = False
self.gui_manager.process_events(event)
if new_layer_visible:
new_layer_visible.blit(imported_drawing, (0, 0))
self.background.blit(new_layer, (140, 60))
gui_manager.update(self.time_delta)
screen.blit(self.background, (0, 0))
gui_manager.draw_ui(self.screen)
pygame.display.update()
I know that the code does not run as I wrote it here since I only included the part I thought is relevant.
I tried to set the container argument to the surface as I saw as a suggestion online but I got an Error as response:
ValueError: container parameter must be of type IContainerLikeInterface.
The objective is to put the "close-Button" on the new_layer surface. Is this possible using pygame_gui, should I use a different library?

You cannot add a Pygame GUI button to a pygame.Surface object because a pygame.Surface consists only of a grid of pixels and cannot contain objects. Also, the Pygame GUI is built on top of Pygame, not the other way around, so a Pygame object cannot handle a Pygame GUI object and no Pygame object implements the interface IContainerLikeInterface. You have to draw the Surface manually in the background and put the GUI on top of it.

Related

Python: why the surface.blit function doesn't display all inputs?

I am trying to write a little digits game using pygame. The idea of the game is to guess the four-digit number randomly chosen by computer. But I am stuck at the very beginning I started by creating all the essential elements: colours, fonts, surfaces, etc. I used blit to 'simulate' computer choice and to show the user's guess. And interestingly enough, not all the inputs are displayed. E.g. '1234' and '9999' is displayed. However, '5738' and '7365' are not. Looking forward to hearing opinions of the experienced users.
import random
import pygame
pygame.init()
width = 900
height = 500
black = (0,0,0)
pastel_blue = (200,205,230)
win = pygame.display.set_mode((width,height))
pygame.display.set_caption("Bulls and Cows")
digit_font = pygame.font.SysFont('comicsans', 30)
a = (random.randint(1000, 10000))
print(a)
def display():
win.fill((pastel_blue))
number = digit_font.render("_ _ _ _", 1, black)
win.blit(number, (width//2-number.get_width()//2, height//4))
pygame.display.update()
display()
def guess_number():
global c
c = input("Guess the number: ")
guess_number()
def guess_display():
text = digit_font.render(c, 1, black)
print(text.get_width()//2)
win.blit(text, [width//2-text.get_width()/2, 300]) #this seems to be the part that doesn't work correctly
pygame.display.update()
pygame.time.delay(2000)
guess_display()
You have to handle the events in the application loop. See pygame.event.get() respectively pygame.event.pump():
For each frame of your game, you will need to make some sort of call to the event queue. This ensures your program can internally interact with the rest of the operating system.
def guess_display():
text = digit_font.render(c, 1, black)
print(text.get_width()//2)
win.blit(text, [width//2-text.get_width()/2, 300]) #this seems to be the part that doesn't work correctly
pygame.display.update()
pygame.event.pump() # <---
pygame.time.delay(2000)
However, the usual way is to use an application loop. Also see Why is my PyGame application not running at all?:
def guess_display():
text = digit_font.render(c, 1, black)
print(text.get_width()//2)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
win.blit(text, [width//2-text.get_width()/2, 300])
pygame.display.update()
Also see:
Why is my display not responding while waiting for input?
Why does pygame.display.update() not work if an input is directly followed after it?
How to create a text input box with pygame?

Making a Semi-Transparent Pause Background screen in Pygame

I was wondering if anyone out there would be able to help me with my problem. Currently I want to display a pause screen everytime a certain key is pressed, which it does however the resulting screen is always either fully transparent or non-transparent and was wondering if there was any way that I would be able to adjust the following code in order to make that dream a reality.
Here's where the Pause Screen is called:
if event.key == pygame.K_p:
notPaused = False
#print("Obtained")
pause = Pause(self.pauseScreen)
while notPaused == False:
#print("Received")
notPaused = pause.processEvents()
print(str(notPaused))
pause.displayFrame(self.pauseScreen)
clock = pygame.time.Clock()
clock.tick(60)
And here's how the pause screen displays itself:
screen.fill(constants.BLACK)
font = pygame.font.SysFont("serif", 25)
for counter in range(1,5):
text = font.render(self.options[counter-1], True, constants.WHITE)
center_x = 150
center_y = (counter * 120) - (text.get_height() // 2) + (self.pointer.image.get_height() // 2)
screen.blit(text, [center_x, center_y])
self.active_sprite_list.draw(screen)
pygame.display.flip()
And for anyone wondering I sometimes try to sub out BLACK for ABLACK using the RGBA values of: (0,0,0,125)
Finally this is where the screen is initialized for the pause screen:
self.size = [constants.SCREEN_WIDTH, constants.SCREEN_HEIGHT]
self.pauseScreen = pygame.Surface(self.size,pygame.SRCALPHA,32)
Any and all help is appreciated.
You should build up your pause screen as a separate surface. This command: screen.fill(constants.BLACK) all but ensures you will never get 50% transparency (since everything that was on your screen before has been painted over with black).
Build a new surface for your pause screen, fill it black, add your text; then use something like my_pause_surface.set_alpha(128) to make that whole surface 50% transparent, then blit it on top of the rest of your game.
Here is some useful information in another question: Draw a transparent rectangle in pygame

How do I clear my game screen and move to a new scene when "play" is clicked

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

How to stop drawing a specific rectangle pygame

My program is a 'Piano Hero' game in pygame which works in the same way as guitar hero except that it is for a computer keyboard and its based on playing the piano rather than the guitar. I am using a design similar to Synthesia for my interface where rectangles come down to a 'hitline' and you have to press the key at the right time.
My problem is that although the rectangles are drawing and working as intended at first, they do not seem to update so that the top ever stops. In other words, every note in the song is infinitely long.
I feel like this is probably where the error is although I am not 100% sure.
def Draw(self,hitLine):
if self.coords[2][1]<hitLine:
self.coords[0][1]+=2
self.coords[1][1]+=2
self.coords[2][1]+=2
self.coords[3][1]+=2
elif self.coords[2][1]>=hitLine and self.coords[0][1]<hitLine:
self.coords[0][1]+=2
self.coords[1][1]+=2
else:
self.drawing = False
pygame.draw.polygon(screen,BLUE,self.coords,0)
pygame.display.update()
This line is inside a while loop which just updates all of the rectangles in the song one at a time.
for z in notes:
if z.drawing:
z.Draw(hitLine)
I found you're question quite fun to work on and is very interesting!
Some items to consider.
It doesn't seem that there is any reason to use a "pygame polygon" for your Note objects which are clearly rectangles. In my code below I used "pygame Rect" objects.
You're main loop doesn't clear the screen every frame.
In your main loop you need to clear the screen every frame. In my code I used Rect objects. The Note stops drawing itself when it's top hits the hitLine.
import pygame
pygame.init()
gameScreen = pygame.display.set_mode((1100, 692))
hitLine = 500
class Note:
def __init__(self, rect):
self.rect = rect
self.drawing = True
def draw(self):
if self.rect.y < hitLine:
self.rect.y += 2
else:
self.drawing = False;
pygame.draw.rect(gameScreen, (0, 0, 255), self.rect, 0)
fNote = Note(pygame.Rect(500, -550, 80, 550))
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
gameScreen.fill((0, 0, 0))
if fNote.drawing:
fNote.draw()
pygame.display.update()

Pygame, Adding quit function to a rectangle

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()

Categories