text not showing up dynamically, pygame - python

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?

Related

Pygame: pygame.mouse.get_pos() tuple out of range?

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
# [...]

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

Making pygame wait for user click/input before proceeding (python quiz game)

So I am trying to make a game on pygame which would display a vocabulary as a question and three answer choices. And if the user presses the correct answer, their score will go up by one, and the game will move on to the next vocabulary question.
I store my questions in an 2D array called questions[], in each element of the array it holds the questions and answers for each question as [question, correct answer choice, answer choice, answer choice]. So the correct answer is always at index position [i][1]. I do randomize the order in which the answer choices are shown later.
Now my problem is that my game goes through the questions without waiting for user input. The point was that it would wait for the user to click. When the user clicks, then it checks where the user click. The position of the user's mouse will determine which "answer box" the user pressed. Let's pretend the user pressed the first box. The game then compares whether the text stored in that box is correct (i.e the text is the same as questions[i][1]). It displays each question for a split second, and then moves on to the next question and the next question.
But it does not wait for the user to click first. Not even, it doesn't even display the questions long enough for the user to read the question. Is there a way I can perhaps structure the loop or add certain conditions so that the program would display each question until the user chooses an answer, and then add the score and move on to the next question?
Here is the code:
import pygame
from random import randint
from pygame import *
pygame.init()
pygame.font.match_font('Courier New.ttf')
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
GREEN = (71, 212, 15)
BLUE = (42, 250, 246)
PINK = (255,102, 196)
YELLOW = (255, 255, 0)
i = 0
size = (700, 500)
screen = pygame.display.set_mode(size)
pygame.display.set_caption("Spanish Space Quiz")
done = False
questions = [["Hola", "Hello", "Goodbye", "Cow"],["Amigo", "Friend", "Cat", "Dog"],["Si", "Yes", "No", "Maybe"]]
answerboxes = [[30,300,190,150,BLUE,WHITE,7],[255,300,190,150,YELLOW,WHITE,7],[480,300,190,150,PINK,WHITE,7]]
score = 0
choices = []
def textObject (text, font):
textWord = font.render(text, True, WHITE)
return textWord, textWord.get_rect()
def answerbutton(drawbox):
mouse = pygame.mouse.get_pos()
if drawbox[0]+drawbox[2] > mouse[0] > drawbox[0] and drawbox[1]+drawbox[3] > mouse[1] > drawbox[1]:
pygame.draw.rect(screen, drawbox[5],(drawbox[0],drawbox[1],drawbox[2],drawbox[3]),drawbox[6])
else:
pygame.draw.rect(screen, drawbox[4],(drawbox[0],drawbox[1],drawbox[2],drawbox[3]),drawbox[6])
answerTextFont = pygame.font.SysFont("Courier New",60)
textWord, textBox = textObject(drawbox[7], answerTextFont) #the text & the "Text box"
textBox.center = ( (drawbox[0]+(drawbox[2]/2)), (drawbox[1]+(drawbox[3]/2)) )
screen.blit(textWord, textBox)
def questionbutton(message,x,y,w,h,color):
mouse = pygame.mouse.get_pos()
pygame.draw.rect(screen,color,(x,y,w,h))
answerTextFont = pygame.font.SysFont("Courier New",60)
textWord, textBox = textObject(message, answerTextFont) #the text & the "Text box"
textBox.center = ( (x+(w/2)), (y+(h/2)) )
screen.blit(textWord, textBox)
while not done:
screen.blit (backgroundImage, [0,0])
font = pygame.font.SysFont('Courier', 30, True, False)
text = font.render("SPACE VOCBULARY QUIZ",True,WHITE)
screen.blit(text, [30, 30])
font = pygame.font.SysFont('Courier', 30, False, False)
text = font.render("SCORE: ", True, WHITE)
screen.blit(text, [500, 30])
for event in pygame.event.get():
if i == (len(questions)): #if user clicks close then done becomes true and game quits
done = True
event.type == pygame.QUIT
for c in range (len(questions)):
mouse = pygame.mouse.get_pos()
click = pygame.mouse.get_pressed()
questionbutton((questions[c][0]),30,150,640,100,GREEN)
for n in range(3):
choices.append(questions[c][n+1])
for r in range(3):
randomPointer = randint(0, (len(choices)-1))
answerboxes[r].append(choices[randomPointer])
choices.remove(choices[randomPointer])
answerbutton(answerboxes[r][0:8])
if click[0] == 1:
for a in range(3):
if answerboxes[a][0]+answerboxes[a][2] > mouse[0] > answerboxes[a][0] and answerboxes[a][1]+answerboxes[a][3] > mouse[1] > answerboxes[a][1]:
if answerboxes[a][7] == questions[i][1]:
score = score + 1
print (score)
for g in range (3):
answerboxes[g].pop()
i = i+1
pygame.display.update()
pygame.quit()
You can put an infinite loop in the loop for questions, with the break condition of exiting the infinite loop when mouse is clicked on the answer box.
Example:
for c in len(range(questions)):
clicked_on_answer = False
while True:
# your code
if click[0] == 1:
for a in range(3):
if answerboxes[a][0]+answerboxes[a][2] > mouse[0] > answerboxes[a][0] and answerboxes[a][1]+answerboxes[a][3] > mouse[1] > answerboxes[a][1]:
clicked_on_answer = True
if answerboxes[a][7] == questions[i][1]:
score = score + 1
print (score)
if clicked_on_answer:
break
Yes, you need to restructure the program and better separate the drawing from the event handling and game logic. It should only proceed to the next question if a mouse button is pressed, so check in the event loop if event.type == pygame.MOUSEBUTTONDOWN: (only one MOUSEBUTTONDOWN event is produced per click), then see if a rect was clicked, increment the score and finally render the next question and choices. Blit the question and choice texts outside of the event loop.
clock = pygame.time.Clock() # A clock to limit the frame rate.
# Define the fonts outside of the main loop.
font = pygame.font.SysFont('Courier', 30, False, False)
# Render question and choice text surfaces.
# Create the rects for the choices and set their positions.
while not done:
# Handle events.
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
elif event.type == pygame.MOUSEBUTTONDOWN:
# Check if event.pos collides with the correct rect.
for index, rect in enumerate(rects):
if rect.collidepoint(event.pos) and choices[index] == correct_answer:
score += 1
# Get next question and choices, render them and update the rects.
# Draw everything.
# Blit the question and choice text surfaces at their rects.
pygame.display.update()
clock.tick(30) # Limit frame rate to 30 fps.

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.

Can't click on image again, what's wrong with my pygame code?

Okay, I'am trying to create a Tom and Jerry game with the pygame library.
The game focuses on catching mice by clicking on them as they appear in their holes. The problem
is that sometimes a cat appears instead of a mouse and should the player erroneously click on the
cat (s)he looses all earned points, but the game continues.
The mouse is an image of a mouse and the cat is an image of an cat.
If you click on the mouse, you get mouse, otherwise the cat gets the points.
The code is a mess, that's because I don't know what I'am doing and just set an another event loop because then it works, because it runs after I create the mouse. It works to click on the mouse but then you click somewhere else and after that it's like you did not clicked on the mouse.
The mouse is created in a loop and is supposed to wait for 5 seconds and if you click on the mouse within these seconds then an appropriate message prints out in the console ,,Jerry clicked!" else "1 click". If you don't click on the mouse within 5 seconds a image covers the mouse so she disappears.
Now, what I'am trying to do right now is to print the message 1 click when the player does not click on anything but print 1 click jerry clicked when the player clicks on the mouse. I have a image of the mousehole and then I put the mouse on the mousehole, that is, on an another image.
This code works with one image at least:
pygame.init()
width=350;
height=400
screen = pygame.display.set_mode( (width, height ) )
pygame.display.set_caption('clicked on image')
redSquare = pygame.image.load("images/red-square.png").convert()
x = 20; # x coordnate of image
y = 30; # y coordinate of image
screen.blit(redSquare , ( x,y)) # paint to screen
pygame.display.flip() # paint screen one time
running = True
while (running):
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.MOUSEBUTTONDOWN:
# Set the x, y postions of the mouse click
x, y = event.pos
if redSquare.get_rect().collidepoint(x, y):
print('clicked on image')
#loop over, quite pygame
pygame.quit()
My problem is that, when I click on the mouse and then I don't click on the mouse I can't click on the mouse again at another position.
So what's wrong? What I'am doing wrong here?
Here is my code:
import pygame
from pygame import *
from random import *
init()
run = True
screen = (800,800)
screen = display.set_mode(screen)
xpos = 0
ypos = 0
mouseorcatxpos = 5
mouseorcatypos = 0
mousehole = image.load("mousehole.png").convert()
cat = image.load("tom.png")
jerry = image.load("jerry.png")
def makeholes():
global ypos
global xpos
for holey in range(1,9):
for holex in range(1,9):
screen.blit(mousehole,(xpos,ypos))
display.flip()
xpos += 100
ypos += 100
xpos = 0
def mouseorcat():
global xpos
mouseorcatxpos = 5
ypos = 0
for mousecaty in range(1,9):
pygame.event.pump()
for mousecatx in range(1,9):
randommouse = randint(1, 3)
randomcat = randint(1, 10)
if(randommouse == 2):
screen.blit(jerry, (mouseorcatxpos, ypos))
display.flip()
for event in pygame.event.get():
if (event.type == MOUSEBUTTONDOWN):
if jerry.get_rect().collidepoint(xpos, ypos) == False:
print("l clicked!")
x, y = event.pos
if jerry.get_rect().collidepoint(xpos, y):
print("JERRY CLICKED!!")
x, y = event.pos
print(x, y)
time.wait(5000)
#screen.blit(mousehole, (mouseorcatxpos - 5, ypos))
display.flip()
elif(randomcat == 2):
screen.blit(cat, (mouseorcatxpos, ypos))
display.flip()
time.wait(1500)
screen.blit(mousehole, (mouseorcatxpos-5, ypos))
display.flip()
mouseorcatxpos += 100
mouseorcatxpos = 0
ypos += 100
makeholes()
while run == True:
for event in pygame.event.get():
mouseorcat()
if event.type == QUIT:
run = False
I rewrote your game to show you how I would do it.
To keep track of the time and to limit the framerate I used a pygame.time.Clock and a timer variable. The clock returns the time in milliseconds since clock.tick was called the last time, which is used to increase the timer variable. The cat just replaces the mouse after two seconds and the mouse is set to a new position. I use pygame.Rects to store the positions, but you could also use lists or tuples.
import sys
import random
import pygame
pygame.init()
size = (800, 800)
screen = pygame.display.set_mode(size)
# Images replaced by pygame.Surface. Do that too
# in the future before you post your code.
mousehole = pygame.Surface((40, 40)).convert()
mousehole.fill(pygame.Color(30, 30, 30))
cat = pygame.Surface((40, 40)).convert()
cat.fill(pygame.Color(110, 110, 130))
jerry = pygame.Surface((40, 40)).convert()
jerry.fill(pygame.Color(190, 130, 0))
# Create the background image and blit the holes.
background = pygame.Surface(size).convert()
for holey in range(8):
for holex in range(8):
background.blit(mousehole, (holex*100, holey*100))
def new_position():
"""Return a random position between 0-700 in steps of 100."""
return (random.randrange(0, 701, 100), random.randrange(0, 701, 100))
def main():
fps = 30
clock = pygame.time.Clock()
jerry_rect = jerry.get_rect() # Stores jerry's position and size.
jerry_rect.topleft = new_position() # New random position.
# The cat is outside of the screen first.
cat_rect = cat.get_rect(topleft=(-100, -100))
points = 0
timer = 0
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.MOUSEBUTTONDOWN:
if jerry_rect.collidepoint(event.pos):
points += 1
print('Jerry caught! Points:', points)
timer = 0
jerry_rect.topleft = new_position()
else:
print('Missed. Points:', points)
# Run logic.
timer += clock.tick(fps) / 1000 # timer + seconds since last tick.
if timer > 2: # Cat catches mouse after 2 seconds.
cat_rect.topleft = jerry_rect.topleft
jerry_rect.topleft = new_position()
timer = 0
points = 0
print('Tom caught Jerry.')
# Draw.
# Clear the screen by blitting the bg.
screen.blit(background, (0, 0))
screen.blit(jerry, jerry_rect)
screen.blit(cat, cat_rect)
pygame.display.flip()
if __name__ == '__main__':
main()
pygame.quit()
sys.exit()
Side notes:
Don't use star imports (from module import *), because that can make code harder to read. If you want you can use from pygame.locals import *, if it's the only star import.
Don't use global variables, because they can make code harder to read, understand and maintain. Pass variables to functions as arguments and then return the result.
Update: Some notes about your program:
The first big problem is that your game has two event loops and the important one is deeply nested inside of two other for loops and a if. The event loop should be directly under the main while loop (one indentation level (when you have more experience you can put it into a function or class method)).
The two for loops seem to have the purpose to let the code run until randommouse or randomcat are 2. To run code until a condition is met is the purpose of a while loop. But in this case you should better just pick a random number and write the if/elif conditions so that they always apply. For example, you want a 2/3 chance for mouse and 1/3 for a cat,
random_number = random.randint(1, 3)
if random_number < 3:
print("2/3 probability. It's a mouse")
else:
print("1/3 probability. It's a cat")
Or use random.choice with a list:
>>> random.choice(['mouse', 'mouse', 'cat'])
'mouse'
time.wait(5000) shouldn't be used because the game just hangs in this time. You can't even close the window. Limit the framerate and get the time since the last tick with a pygame.time.Clock.
pygame.event.pump() is not needed.
If you call get_rect() without an argument, the rect is positioned at (0, 0).
if jerry.get_rect().collidepoint(xpos, y):
That's the reason why clicking on jerry only works in the top row, and because you use the global xpos here. Since xpos is 0, the whole top row counts as Jerry.
You can pass coordinates to get_rect like so (you can also use center or other args instead of topleft):
jerry_rect = jerry.get_rect(topleft=(50, 100))
I'm sorry but I don't think I can simply fix your code. I've tried it several times, but I always end up re-writing it completely.
I begin by extracting the event loop out of the two nested for loops, then remove these loops, create rects for the mouse and cat, fix the collision detection, add a timer and so on. Take a close look at my example and try to rewrite your game in a similar way, and keep asking questions if you don't understand something.

Categories