Button's rectangle is in the wrong place [duplicate] - python

This question already has an answer here:
Why is my collision test always returning 'true' and why is the position of the rectangle of the image always wrong (0, 0)?
(1 answer)
Closed 2 years ago.
I am developing an idle clicker game for a school project. There's a button that you can click on to upgrade your clicking power, but the rectangle assigned to it is in the wrong place. The button's rectangle is not on the button itself. Here is some of the code.
import pygame
pygame.init()
pygame.font.init()
screen = pygame.display.set_mode((800, 600))
clickimage = pygame.image.load('block.png')
button = pygame.image.load('button.png')
boxclip = clickimage.get_rect()
buttonclip = button.get_rect()
myfont = pygame.font.SysFont('Comic Sans MS', 50)
coins = 0
cost = 15
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if pygame.mouse.get_pressed()[0] and boxclip.collidepoint(pygame.mouse.get_pos()):
coins = coins+1
if pygame.mouse.get_pressed()[0] and buttonclip.collidepoint(pygame.mouse.get_pos()):
coins = coins-cost
screen.fill((255, 255, 255))
screen.blit(clickimage, (50, 50))
screen.blit(button, (500, 50))
coindisplay = myfont.render(f'Coins: {coins}', False, (0, 0, 0))
costdisplay = myfont.render(f'Cost: {cost}', False, (0, 0, 0))
screen.blit(coindisplay, (50, 310))
screen.blit(costdisplay, (500, 200))
pygame.display.update()
I assigned the button's rectangle to the button but it's for some reason overlapping the clickimage. Why is this happening?

pygame.Surface.get_rect.get_rect() returns a rectangle with the size of the Surface object, that always starts at (0, 0) since a Surface object has no position. The Surface is placed at a position when it is blit to the display. The position of the rectangle can be specified by a keyword argument. For example, the top ledt of the rectangle can be specified with the keyword argument topleft. These keyword argument are applied to the attributes of the pygame.Rect before it is returned (see pygame.Rect for a full list of the keyword arguments).
You only need to change 2 lines of your code:
clickimage = pygame.image.load('block.png')
button = pygame.image.load('button.png')
boxclip = clickimage.get_rect(topleft = (50, 50)) # <---
buttonclip = button.get_rect(topleft = (500, 50)) # <---
# [...]
running = True
while running:
# [...]
screen.blit(clickimage, boxclip)
screen.blit(button, buttonclip)
# [...]

You are checking to see if the user clicked the block by checking the boxclip rect. You set the boxclip = clickimage.get_rect() before you blit the image to a place on the screen, so how could the rect know what position it is? It's position is likely set at 0, 0.
You can fix this by defining your own rectangle for the block.
boxclip = pygame.Rect(50, 50, clickimage.get_rect().width, clickimage.get_rect().height)
The first 2 arguments are the x and y positions and 50, 50 is where you blited it. Then you can check if the mouse collides with this rectangle. You also need to apply this to your button collisions as well.

Related

"Hello from the pygame community"

When I executed the following code, instead of an error it said "Hello from the pygame community. https://www.pygame.org/contribute.html". It would be easier for me if it just included the error so I could fix it myself without posting the code in stackoverflow (for privacy reasons).
Here's the code:
# import the pygame module, so you can use it
import pygame
# define a main function
def main():
# initialize the pygame module
pygame.init()
# load and set the logo
logo = pygame.image.load("logo32x32.png")
pygame.display.set_icon(logo)
pygame.display.set_caption("minimal program")
# create a surface on screen that has the size of 240 x 180
screen = pygame.display.set_mode((240, 180))
# define a variable to control the main loop
running = True
# main loop
while running:
# event handling, gets all event from the eventqueue
for event in pygame.event.get():
# only do something if the event is of type QUIT
if event.type == pygame.QUIT:
# change the value to False, to exit the main loop
running = False
# draw a green line from (0,0) to (100,100)
pygame.draw.line(screen, (0,255,0), (0,0), (100,100))
# draw a green line from (0,100) to (100,0)
pygame.draw.line(screen, (0,255,0), (0,100), (100,0))
# draw a rectangle outline
pygame.draw.rect(screen, (0,255,0), (60,60,100,50), 1)
# draw a filled in rectangle
pygame.draw.rect(screen, (0,255,0), (60,120,100,50))
# draw a circle
pygame.draw.circle(screen, (0,255,0), (120,60), 20, 0)
# draw a circle outline
pygame.draw.circle(screen, (0,255,0), (120,60), 20, 1)
# draw a polygon
pygame.draw.polygon(screen, (0,255,0), ((140,60),(160,80),(160,100),(140,120),(120,100),(120,80)), 0)
# draw a filled in poly
pygame.draw.polygon(screen, (0,255,0), ((140,60),(160,80),(160,100),(140,120),(120,100),(120,80)), 1)
# draw a text
font = pygame.font.Font(None, 36)
text = font.render("Hello There", 1, (10, 10, 10))
textpos = text.get_rect()
textpos.centerx = screen.get_rect().centerx
screen.blit(text, textpos)
# update the screen
pygame.display.flip()
# quit pygame properly to clean up resources
pygame.quit()
# end of the code
That isn't an error. That line is printed when you import pygame. None of your code is producing an error because none of it is being called. After you define the main function, make sure to call main().

Pygame draggable frame seems not to be working

I am working with Pygame currently, and I made a simple function to create window instances much like Windows 10 UI. the code I made doesn't give any errors or any unwanted outputs. It just seems not to be working properly, what I mean by "not working properly"; it just doesn't seem to be moving the frames that are meant to be dragged around by a master frame...
This is my code:
import pygame
from pyautogui import size
import datetime
pygame.init()
infoObject = pygame.display.Info()
surface = pygame.display.set_mode((900, 700))
run = True
clock = pygame.time.Clock()
def draw_text(text, font, text_col, x,y):
img = font.render(text, True, text_col)
rect = img.get_rect()
rect.center = (x,y)
surface.blit(img, rect)
return rect
class make_a_window():
def __init__(self,app,width=750,height=500):
self.app_name = str(app)
self.width = width
self.height = height
def run(self):
self.top_frame = pygame.draw.rect(surface, "#C0C0C0", pygame.Rect(0,0,int(self.width),40))#master frame
self.main_frame = pygame.draw.rect(surface, (255,255,255), pygame.Rect(0,40,int(self.width),int(self.height)))
self.red_frame_for_exit_btn_X = pygame.draw.rect(surface, (255,0,0), pygame.Rect(self.width-42,0,42,40))
self.exit_btn_X = draw_text("x", pygame.font.SysFont("calibri",25), "black", self.width-20, 15)
self.mouse_position = pygame.mouse.get_pos()
if pygame.mouse.get_pressed()[0] == 1:
if self.top_frame.collidepoint(self.mouse_position):
#moving the frames
self.top_frame.move(self.mouse_position[0],self.mouse_position[1])
self.main_frame.move(self.mouse_position[0]-40,self.mouse_position[1])
self.red_frame_for_exit_btn_X.move(self.mouse_position[0]-42,self.mouse_position[1])
self.exit_btn_X.move(self.mouse_position[0]-20,self.mouse_position[1])
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
run = False
app = make_a_window("hello")
app.run()
pygame.display.update()
clock.tick(60)
Sorry for my bad English. and thanks for the help, I really appreciate it 😃!
There is some logic error from line 32 to 41.
Firstly you should use the event queue by pygame.event.get() to track mouse activities(this is really important) and secondly why are you recording the mouse position before hand you are checking for its collision. Instead you should insert your
{self.mouse_position = pygame.mouse.get_pos()}
inside the collision checking if statement (rather that would not work smoothly until you use pygame.event.get())
One more thing that the function
pygame.Rect().move()
takes x and y offesets as its arguments not x and y coordinates.
So, mainly give focus on your event loop and the destination positions of your manual window. Maybe I would share the correct code later (don't wait for it.)
The method pygame.Rect.move doesn't move the rectangle itself, but it returns new rectangle that is moved. In compare, the method pygame.Rect.move_ip move the object in place.
However, these methods do not move anything that has been drawn on the screen. These methods simply move a rectangle representing an area of the screen. This rectangle can later be used to draw something on the screen at a new location.
Create the pygame.Rect objects in the class's constructor and use them to draw the objects. Use move_ip to move the rectangles:
class make_a_window():
def __init__(self,app,width=750,height=500):
self.app_name = str(app)
self.width = width
self.height = height
self.top_frame = pygame.Rect(0,0,int(self.width),40)
self.main_frame = pygame.Rect(0,40,int(self.width),int(self.height))
self.red_frame_for_exit_btn_X = pygame.Rect(self.width-42,0,42,40)
self.exit_btn_X = pygame.Rect(self.width-20, 15, 0, 0)
def run(self):
pygame.draw.rect(surface, "#C0C0C0", self.top_frame)
pygame.draw.rect(surface, (255,255,255), self.main_frame)
pygame.draw.rect(surface, (255,0,0), self.red_frame_for_exit_btn_X)
draw_text("x", pygame.font.SysFont("calibri",25), "black", self.exit_btn_X.topleft)
self.mouse_position = pygame.mouse.get_rel()
if pygame.mouse.get_pressed()[0] == 1:
if self.top_frame.collidepoint(self.mouse_position):
#moving the frames
move_rel = pygame.mouse.get_rel()
self.top_frame.move_ip(*move_rel)
self.main_frame.move_ip(*move_rel)
self.red_frame_for_exit_btn_X.move_ip(*move_rel)
self.exit_btn_X.move_ip(*move_rel)

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

Blit user text input to screen

I want to blit text that is input by the user to the screen. Each time the user presses Return, the typed text should be blitted to the screen. For text input I use this [text_input module] (https://github.com/Nearoo/pygame-text-input).
Here is the code I came up with so far:
import pygame_textinput
import pygame
pygame.init()
# Set some parameters
duration = 5.0
time = pygame.time.get_ticks()/1000
screen = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()
yoffset = 5
# Function that positions user input rects on screen
def renderInput(text, xoffset, yoffset):
font = pygame.font.SysFont("arial", 20)
renderText = font.render(text, False, (0, 0, 0))
rectText = renderText.get_rect()
rectText = rectText.move((0 + xoffset), (screen.get_height()/2 + yoffset))
return renderText, rectText
# Fills the screen once at the beginning
screen.fill((225, 225, 225))
while (pygame.time.get_ticks()/1000) < time + duration:
# creat new text input object on every trial
textinput = pygame_textinput.TextInput()
while True:
# Fills the surface after each keypress
screen.fill((225, 225, 225))
# Check events
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
exit()
# Feed with events every frame
# This evaluates to True once Return is pressed
if textinput.update(events):
userInput = textinput.get_text()
yoffset += 20
break
# Blit surface onto the screen
screen.blit(textinput.get_surface(), (10, 10))
# Update screen
pygame.display.update()
clock.tick(30)
# Blits user input to screen each time "Return" is pressed
# First get input text and the rectangle of the text
text, textrect = renderInput(userInput, 5, yoffset)
# Then blit it to the screen
screen.blit(text, textrect)
pygame.display.update()
My problem is, that the blitting only works if I do not fill the screen after each keypress within the while-loop that handles the input. If I do that, then the text input, however, is not cleared after each time the user presses Return.
So is there a way to have both, redraw after each keypress and have the text displayed below after each time Return is pressed by the user.
Thanks.
If I understand you correctly, the text in the input field should be cleared and it should be blit in the main area of the screen. I'd assign the text to the user_input variable if the user presses enter and then create a new pygame_textinput.TextInput() instance to clear the input field.
I've tried to simplify your code, because the two while loops are a bit confusing and I'm not sure what their purpose is. There should usually be only one while loop in a game.
import pygame
import pygame_textinput
pygame.init()
screen = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()
font = pygame.font.SysFont("arial", 20)
textinput = pygame_textinput.TextInput()
user_input = ''
done = False
while not done:
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
done = True
if textinput.update(events):
user_input = textinput.get_text()
textinput = pygame_textinput.TextInput()
# Draw everything.
screen.fill((225, 225, 225))
screen.blit(textinput.get_surface(), (10, 10))
user_input_surface = font.render(user_input, True, (30, 80, 100))
screen.blit(user_input_surface, (10, 50))
pygame.display.update()
clock.tick(30)
pygame.quit()
Edit: In this version I append the rendered text surfaces to a list and blit them with an offset.
import pygame
import pygame_textinput
pygame.init()
screen = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()
font = pygame.font.SysFont("arial", 20)
textinput = pygame_textinput.TextInput()
user_inputs = []
done = False
while not done:
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
done = True
if textinput.update(events):
user_inputs.append(
font.render(textinput.get_text(), True, (30, 80, 100)))
textinput = pygame_textinput.TextInput()
screen.fill((225, 225, 225))
screen.blit(textinput.get_surface(), (10, 10))
for y, text_surf in enumerate(user_inputs):
screen.blit(text_surf, (10, 50+30*y))
pygame.display.update()
clock.tick(30)
pygame.quit()
Edit2: To get a table, you can use modulo for the row offset and floor division for the column offset. The problem with this example is that the text surfaces can overlap if they are too wide.
for n, text_surf in enumerate(user_inputs):
# 5 rows. Offset = 30 pixels.
y_pos = 50 + (n%5) * 30
# After 5 rows add a new column. Offset = 100 pixels.
x_pos = 10 + n // 5 * 100
screen.blit(text_surf, (x_pos, y_pos))
I have edited my code containing your suggestions. Thanks a lot, this really seems to solve my problem. Here is the current version including a timer:
import pygame_textinput
import pygame
pygame.init()
# Set some parameters
duration = 5.0
time = pygame.time.get_ticks()/1000
xoffset = 5
yoffset = 5
screen = pygame.display.set_mode((400, 400))
font = pygame.font.SysFont("arial", 20)
clock = pygame.time.Clock()
# Creates textinput instance and an empty list to store inputs
textinput = pygame_textinput.TextInput()
userInputs = []
# Fills the screen once at the beginning
screen.fill((225, 225, 225))
while (pygame.time.get_ticks()/1000) < time + duration:
# Check events
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
exit()
# Feed with events every frame
# This evaluates to True once Return is pressed
if textinput.update(events):
userInputs.append(font.render(textinput.get_text(), True, (30, 80, 100)))
textinput = pygame_textinput.TextInput()
# Fill screen
screen.fill((225, 225, 225))
# Blit its surface onto the screen
screen.blit(textinput.get_surface(), (screen.get_rect().centerx, screen.get_rect().height/5))
for y, text_surf in enumerate(userInputs):
screen.blit(text_surf, (10, (screen.get_rect().height/4)+30*y))
# Update screen
pygame.display.update()
clock.tick(30)
I do not want to bother you to much, but now I have one more issue left that I am having trouble solving. Is it possible to render the text inputs in a second column once it exits the bottom border of the screen? So for example, if the user types a lot of words, that do not fit under each other, is it possible to move the next text input to the right and make it start next to the first input (create a second column so to speak). Thanks for your help so far, I really apreciatie it.

How to display text in pygame? [duplicate]

This question already has answers here:
pygame - How to display text with font & color?
(7 answers)
Closed 2 years ago.
I can't figure out to display text in pygame.
I know I can't use print like in regular Python IDLE but I don't know how.
import pygame, sys
from pygame.locals import *
BLACK = ( 0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = ( 255, 0, 0)
pygame.init()
size = (700, 500)
screen = pygame.display.set_mode(size)
DISPLAYSURF = pygame.display.set_mode((400, 300))
pygame.display.set_caption('P.Earth')
while 1: # main game loop
for event in pygame.event.get():
if event.type == QUIT:
pygame.display.update()
import time
direction = ''
print('Welcome to Earth')
pygame.draw.rect(screen, RED, [55,500,10,5], 0)
time.sleep(1)
This is only the beginning part of the whole program.
If there is a format that will allow me to show the text I type in the pygame window that'd be great. So instead of using print I would use something else. But I don't know what that something else is.
When I run my program in pygame it doesn't show anything.
I want the program to run in the pygame window instead of it just running in idle.
You can create a surface with text on it. For this take a look at this short example:
pygame.font.init() # you have to call this at the start,
# if you want to use this module.
my_font = pygame.font.SysFont('Comic Sans MS', 30)
This creates a new object on which you can call the render method.
text_surface = my_font.render('Some Text', False, (0, 0, 0))
This creates a new surface with text already drawn onto it.
At the end you can just blit the text surface onto your main screen.
screen.blit(text_surface, (0,0))
Bear in mind, that every time the text changes, you have to recreate the surface again, to see the new text.
There's also the pygame.freetype module which is more modern, works with more fonts and offers additional functionality.
Create a font object with pygame.freetype.SysFont() or pygame.freetype.Font if the font is inside of your game directory.
You can render the text either with the render method similarly to the old pygame.font.Font.render or directly onto the target surface with render_to.
import pygame
import pygame.freetype # Import the freetype module.
pygame.init()
screen = pygame.display.set_mode((800, 600))
GAME_FONT = pygame.freetype.Font("your_font.ttf", 24)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill((255,255,255))
# You can use `render` and then blit the text surface ...
text_surface, rect = GAME_FONT.render("Hello World!", (0, 0, 0))
screen.blit(text_surface, (40, 250))
# or just `render_to` the target surface.
GAME_FONT.render_to(screen, (40, 350), "Hello World!", (0, 0, 0))
pygame.display.flip()
pygame.quit()
When displaying I sometimes make a new file called Funk. This will have the font, size etc. This is the code for the class:
import pygame
def text_to_screen(screen, text, x, y, size = 50,
color = (200, 000, 000), font_type = 'data/fonts/orecrusherexpand.ttf'):
try:
text = str(text)
font = pygame.font.Font(font_type, size)
text = font.render(text, True, color)
screen.blit(text, (x, y))
except Exception, e:
print 'Font Error, saw it coming'
raise e
Then when that has been imported when I want to display text taht updates E.G score I do:
Funk.text_to_screen(screen, 'Text {0}'.format(score), xpos, ypos)
If it is just normal text that isn't being updated:
Funk.text_to_screen(screen, 'Text', xpos, ypos)
You may notice {0} on the first example. That is because when .format(whatever) is used that is what will be updated. If you have something like Score then target score you'd do {0} for score then {1} for target score then .format(score, targetscore)
This is slighly more OS independent way:
# do this init somewhere
import pygame
pygame.init()
screen = pygame.display.set_mode((640, 480))
font = pygame.font.Font(pygame.font.get_default_font(), 36)
# now print the text
text_surface = font.render('Hello world', antialias=True, color=(0, 0, 0))
screen.blit(text_surface, dest=(0,0))

Categories