How to remove sprites out of group, outside of the class: Pygame - python

#Importing Modules
import pygame as pg
import sys
import random
#All pygame stuff under here
pg.init()
#Font definitions
backFont = pg.font.SysFont("monospace",40)
titleFont = pg.font.SysFont("garamond", 100)
cipherFont = pg.font.SysFont("garamond", 50)
buttonFont = pg.font.SysFont("garamond", 25)
bigFont = pg.font.SysFont("garamond",100)
Font = pg.font.SysFont(None,32)
inputFont = pg.font.SysFont('consola', 35)
errorFont = pg.font.SysFont('tahoma',20)
diagramFont = pg.font.SysFont('courier new',25)
#Colour definitions
BackGray = pg.Color('gray60')
screenGray = pg.Color('gray80')
buttonGray2 = pg.Color('gray50')
textColour = pg.Color('navy')
#Screen size set
screen = pg.display.set_mode((400, 400))
clock = pg.time.Clock()
class Button(pg.sprite.Sprite):
def __init__(self, text, x, y, width, height, colour, enabled):
super().__init__()
self.image = pg.Surface((width, height))
self.image.fill(colour)
self.rect = self.image.get_rect()
txt = buttonFont.render(text, True, textColour)
txtRect = txt.get_rect(center = self.rect.center)
self.image.blit(txt, txtRect)
self.rect.topleft = x, y
self.enabled = enabled
def isPressed(self, event):
if self.enabled == True:
if event.type == pg.MOUSEBUTTONDOWN:
if self.rect.collidepoint(event.pos):
return True
return False
def Function():
background = pg.Surface(screen.get_size())
background.fill(screenGray)
Button1 = Button('Encrypt',100,100,125,50,buttonGray2,True)
Button2 = Button('Decrypt',100,200,125,50,buttonGray2,True)
buttonsGroup = pg.sprite.Group(Button1,Button2)
ACTIONPRINT = False
Active1 = False
while True:
for event in pg.event.get():
if event.type == pg.QUIT:
pg.quit()
sys.exit()
elif Button1.isPressed(event):
print("1")
Active1 = True
elif Button2.isPressed(event):
print("2")
if Active1 == True:
ACTIONPRINT = True
buttonsGroup = pg.sprite.Sprite.remove(Button2)
screen.blit(background,(0,0))
buttonsGroup.draw(screen)
pg.display.flip()
clock.tick(60)
Function()
Above is the code for the class of Buttons, and a simple function that runs two buttons. What I'd like to do is remove one of the buttons, when one is pressed, which are set as sprites. When the button is removed from the group, I believe it should disappear from the screen, after one is pressed.
The above code at the minute returns an error saying that there is an AttributeError: 'NoneType' object has no attribute 'draw'. However, in another program, when it did work, it said that in the Sprite.remove method, the parameters must be a sequence and not a button - what does this mean?
I have looked online, and all the removing sprites examples are inside a class. Does that mean that the only way for this to work is by changing the class?
Or can sprites still be removed from outside the class, and if so how is it done?
Any other methods are welcome to
Thanks in advance!

sprite.Group.remove doesn't return anything, it removes the sprite from the group that calls it, so instead of:
buttonsGroup = pg.sprite.Sprite.remove(Button2)
Try:
buttonsGroup.remove(Button2)

Related

How can I stop pygame looping though a dictionary uncontrollably?

I'm trying to make my first GUI quiz game. But for some reason when I run the code to loop through the questions, it does it too fast and doesn't wait for the user to choose one on the choices before moving onto the next question.
The number of iterations are supposed to be variable as the user should be able to choose the number of questions, so I can't make a function for each questions and instead am trying to loop though a dictionary and displaying it on the screen. But ran onto more problems. Is there any way for the program to wait until the user chooses one of the choices before moving onto the next question.
import pygame
from pygame.locals import *
from gui import Button, FlagButton
from os import path
from q_gen import generate_question
import sys
pygame.init()
WIDTH, HEIGHT = 1000, 600
win = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("World game")
font_path = path.join("fonts", "PartyConfetti.ttf")
small_font = pygame.font.Font(font_path, 50)
centre = ((win.get_width())//2, (win.get_height())//2)
WHITE = (255, 255, 255)
BLACK = ( 0, 0, 0)
PASTEL_BLUE = (189, 242, 255)
button_img = pygame.image.load(path.join("assets", "button.png"))
button_img = pygame.transform.scale(button_img, (300, 100))
arrow = pygame.image.load(path.join("assets", "arrow.png"))
arrow = pygame.transform.scale(arrow, (50, 50))
up = pygame.transform.rotate(arrow, 90)
down = pygame.transform.rotate(arrow, 270)
def game():
questions = generate_question("capital", 5, 1)
pressed = False
running = True
while running:
for q in questions:
win.fill(PASTEL_BLUE)
mouse_pos = pygame.mouse.get_pos()
title = small_font.render(f"{q}. {questions[q][0]}", True, "black")
title_rect = title.get_rect(center=(centre[0], centre[1]-200))
win.blit(title, title_rect)
choice1 = Button(win, button_img, (300, 500), "CHOICE")
choice1.changeColor(mouse_pos)
choice1.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.MOUSEBUTTONDOWN:
if choice1.checkForInput(mouse_pos):
pressed = True
break
pygame.display.update()
if pressed:
continue
pygame.quit()
sys.exit(0)
The question generator and class for the button, if needed, is below.
from info import info # Large dictionary containing info of countries
from random import choice, randint
modes = ("capital", "flag", "currency")
def generate_question(mode, q_no, difficulty):
questions = {}
new_mode = mode
for i in range(1, q_no+1):
reverse = choice((True, False))
country = choice(list(info.keys()))
choices = []
if mode == "mixed":
new_mode = choice(modes)
if new_mode != "flag":
query = info[country][new_mode]
if reverse:
question = f"Which country is {query} the {new_mode} of?"
answer = country
while len(choices)<=(difficulty-1)*2:
rand_choice = choice(list(info.keys()))
if rand_choice != answer and rand_choice not in choices:
choices.append(rand_choice)
else:
question = f"What is the {new_mode} of {country}?"
answer = query
while len(choices)<=(difficulty-1)*2:
rand_country = choice(list(info.keys()))
rand_choice = info[rand_country][new_mode]
if rand_choice != answer and rand_choice not in choices:
choices.append(rand_choice)
choices.insert(randint(0, len(choices)), answer)
questions[i] = (question, country, new_mode, answer, choices)
else:
question = f"Which one is the flag of {country}?"
answer = f"{country}.png"
return questions
import os
import pygame
pygame.init()
class Button():
def __init__(self, screen, image, pos, text_input, font=os.path.join("fonts", "PartyConfetti.ttf"), text_size=50):
self.screen = screen
self.image = image
self.x_pos = pos[0]
self.y_pos = pos[1]
self.font = pygame.font.Font(font, text_size)
self.rect = self.image.get_rect(center=(self.x_pos, self.y_pos))
self.text_input = text_input
self.text = self.font.render(self.text_input, True, "black")
self.text_rect = self.text.get_rect(center=(self.x_pos, self.y_pos))
def update(self):
self.screen.blit(self.image, self.rect)
self.screen.blit(self.text, self.text_rect)
def checkForInput(self, position):
if position[0] in range(self.rect.left, self.rect.right) and position[1] in range(self.rect.top, self.rect.bottom):
return True
return False
def changeColor(self, position):
if position[0] in range(self.rect.left, self.rect.right) and position[1] in range(self.rect.top, self.rect.bottom):
self.text = self.font.render(self.text_input, True, "white")
else:
self.text = self.font.render(self.text_input, True, "black")
class FlagButton(Button):
def __init__(self, screen, image, pos, text_input, flag_image, font=os.path.join("fonts", "PartyConfetti.ttf"), text_size=50):
super().__init__(screen, image, pos, text_input, font, text_size)
self.flag_image = flag_image
self.flag_image_rect = self.flag_image.get_rect(center=(self.x_pos, self.y_pos))
def update(self):
self.screen.blit(self.image, self.rect)
self.screen.blit(self.flag_image, self.flag_image_rect)
The problem is here
if pressed:
continue
continue does not do what you think it does, it only skips the rest of the current iteration and continues with the loop.
Instead, you could use a while loop to keep looping over the click check until a button is pressed.
import pygame
from pygame.locals import *
from gui import Button, FlagButton
from os import path
from q_gen import generate_question
import sys
pygame.init()
WIDTH, HEIGHT = 1000, 600
win = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("World game")
font_path = path.join("fonts", "PartyConfetti.ttf")
small_font = pygame.font.Font(font_path, 50)
centre = ((win.get_width())//2, (win.get_height())//2)
WHITE = (255, 255, 255)
BLACK = ( 0, 0, 0)
PASTEL_BLUE = (189, 242, 255)
button_img = pygame.image.load(path.join("assets", "button.png"))
button_img = pygame.transform.scale(button_img, (300, 100))
arrow = pygame.image.load(path.join("assets", "arrow.png"))
arrow = pygame.transform.scale(arrow, (50, 50))
up = pygame.transform.rotate(arrow, 90)
down = pygame.transform.rotate(arrow, 270)
def game():
questions = generate_question("capital", 5, 1)
pressed = False
running = True
while running:
for q in questions:
win.fill(PASTEL_BLUE)
title = small_font.render(f"{q}. {questions[q][0]}", True, "black")
title_rect = title.get_rect(center=(centre[0], centre[1]-200))
win.blit(title, title_rect)
choice1 = Button(win, button_img, (300, 500), "CHOICE")
while not pressed and running:
mouse_pos = pygame.mouse.get_pos()
choice1.changeColor(mouse_pos)
choice1.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.MOUSEBUTTONDOWN:
if choice1.checkForInput(mouse_pos):
pressed = True
pygame.display.update()
if not running:
break
pygame.quit()
sys.exit(0)
There are probably still some problems with this code but hopefully this helps fix your problem.

Why do I get "missing 1 required positional argument: 'self'"?

When I use in the program querauto_group.add(QuerWagen.create_querauto()) then I get the report
missing 1 required positional argument: 'self'. When I use querauto_group.add(player.create_querauto()) then the program works.
import pygame, sys
import random
class Player(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
def create_querauto(self):
ii=random.randrange(0,8)
img = pygame.image.load(f"Bilder/Gegenstaende/geg{ii}.png")
img = pygame.transform.scale(img,(100,25))
return QuerWagen(200,300,img)
class QuerWagen(pygame.sprite.Sprite):
def __init__(self, pos_x,pos_y,img):
super().__init__()
self.image=img
self.rect = self.image.get_rect(center = (pos_x,pos_y))
def update(self):
self.rect.x+=5
if self.rect.x > screen_width + 200:
self.kill()
def create_querauto(self):
ii=random.randrange(0,8)
img = pygame.image.load(f"Bilder/Gegenstaende/geg{ii}.png")
img = pygame.transform.scale(img,(100,25))
return QuerWagen(200,300,img)
pygame.init()
clock = pygame.time.Clock()
screen_width,screen_hight = (1200,800)
screen = pygame.display.set_mode((screen_width,screen_hight))
player = Player()
querauto_group = pygame.sprite.Group()
WECHSELBILD = pygame.USEREVENT + 1
pygame.time.set_timer(WECHSELBILD,1800)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type ==WECHSELBILD:
#querauto_group.add(QuerWagen.create_querauto())
querauto_group.add(player.create_querauto())
screen.fill((30,30,30))
querauto_group.draw(screen)
querauto_group.update()
pygame.display.flip()
clock.tick(30)
pygame.quit()
quit()
On both classes create_querauto is an instance method, meaning that the class needs to be instantiated in order to call the method.
That's why it is working with the player instance.
i.e.
class Player:
def create_querauto(self):
ii=random.randrange(0,8)
img = pygame.image.load(f"Bilder/Gegenstaende/geg{ii}.png")
img = pygame.transform.scale(img,(100,25))
return QuerWagen(200,300,img)
player_instance = Player()
player_instance.create_querauto() # This will work
Player.create_querauto() # This will throw the error you see above
If you would like to method to work without instantiating the class first you can define it as a static method, using the #staticmethod decorator
This would look like:
class QuerWagen(pygame.sprite.Sprite):
#staticmethod
def create_querauto():
ii=random.randrange(0,8)
img = pygame.image.load(f"Bilder/Gegenstaende/geg{ii}.png")
img = pygame.transform.scale(img,(100,25))
return QuerWagen(200,300,img)
Now you can call that function on the class directly like you were trying to do.
querwagen = QuerWagen.create_querauto() # works fine

Whenever i try run my python code with pygame the window opens and then stops responding after a second or any input

heres all the code i have currently, it is just a start of a pacman game i am creating all that should be shown is a tab with press space to play
ive looked at previous responses to similar questions and i have what i should need to make it work but it still doesn't... i hope someone can help, thanks.
import os
import sys
import pygame
Start_window_font = 'arial black'
pygame.init()
vector=pygame.math.Vector2
class RPMGame:
def __init__(self):
self.screen = pygame.display.set_mode((450, 600))
self.clock = pygame.time.Clock()
self.running = True
self.state = 'startwindow'
self.load()
self.node_width = 450//25
self.node_height = 600//30
def run(self):
while self.running:
if self.state == 'startwindow':
self.startwindow_events()
self.startwindow_update()
self.startwindow_draw()
if self.state == 'gaming':
self.gaming_events()
self.gaming_update()
self.gaming_draw()
#fps
self.clock.tick(60)
pygame.quit()
sys.exit()
def draw_text(self, words, screen, position, size, colour, font_name, centered = False):
font = pygame.font.SysFont(font_name, size)
text = font.render(words, False, colour)
text_size = text.get_size()
#centering the starting text##
if centered:
position[0] = position[0]-text_size[0]//2
position[1] = position[1]-text_size[1]//2
screen.blit(text, position)
def load(self):
self.gameboard = pygame.image.load('pacmanmaze.png')
self.gameboard = pygame.transform.scale(self.gameboard, (450, 600))
def draw_maze(self):
for x in range(450//self.node_width):
pygame.draw.line(self.screen, 107,107,107, (x*self.node_height, 0), (x*self.node_width, 600))
def startwindow_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
###key used to start game##
if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
self.state= 'gaming'
def startwindow_update(self):
pass
def startwindow_draw(self):
####colour of background##
self.screen.fill(0,0,0)
##start game text (text, position, text size, colour, centering)##
self.draw_text('CLICK TO START GAME', self.screen, (225,300), 16,(255,0,0),
Start_window_font, centered= True)
self.draw_text('HIGH SCORE', self.screen, [5,0] ,(225,300), 16,(255,255,255),
Start_window_font)
pygame.display.update()
def gaming_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
def gaming_update(self):
pass
def gaming_draw(self):
self.screen.blit(self.gameboard, (0,0))
self.draw_maze()
pygame.display.update()
game = RPMGame()
game.run()
**edit
i realised that i forgot to change the variable for one of my states which was needed for the stop part
You have a number of minor issues. When the app started and then immediately stopped, did you not notice that you're getting syntax and runtime errors on the console?
In this line:
self.screen.fill(0,0,0)
fill accepts one parameter, which is an RGB tuple:
self.screen.fill((0,0,0))
You have the same problem in draw_maze:
pygame.draw.line(self.screen, 107,107,107, (x*self.node_height, 0), (x*self.node_width, 600))
... should be ...
pygame.draw.line(self.screen, (107,107,107), (x*self.node_height, 0), (x*self.node_width, 600))
Then, in this call:
self.draw_text('HIGH SCORE', self.screen, [5,0] ,(225,300), 16,(255,255,255),
I don't know what the [5,0] was trying to do, but draw_text isn't expecting that parameter. Just remove it.
Then, in this code:
if centered:
position[0] = position[0]-text_size[0]//2
position[1] = position[1]-text_size[1]//2
screen.blit(text, position)
you pass in position as a tuple. You can't modify a tuple. You need to build a new tuple, or just change to a list:
pos = list(position)
if centered:
pos[0] = pos[0]-text_size[0]//2
pos[1] = pos[1]-text_size[1]//2
screen.blit(text, pos)
With that, your app at least starts for me.

Typewriter Effect Pygame

This question is really difficult to ask, but I know you guys here at Stack Overflow are the brightest minds.
I'm totally blinded by why this issue happens (I'm fairly at Python and Pygame, so any suggestions on how to improve the code will be received with the love of improving my skills).
What I'm creating:
It's really a gimmick project, I have a little 2.5" screen (PiTFT) attached to a Raspberry Pi and the code is creating a typewriter effect with a moving cursor in front of the text as it's being written.
Challenge 1 was that every time you move a sprite in pygame, you must redraw everything, otherwise you will see a trail, and since the cursor is moving in front of the text, the result would look like this:
I managed to solve this issue by blackening / clearing the screen. But then I lost all the previously written letters.
So I created a list (entireword), which I'm populing with all the previously written characters. I use this list every time I cycle through the loop to redraw all the previous written text.
So now:
As you can see, the text looks funny.
It's supposed to read:
[i] Initializing ...
[i] Entering ghost mode ... []
I've been spending hours and hours getting to this point - and the code ALMOST works perfectly! The magic happens in the function print_screen(), but WHAT in my code is causing the text to include a letter from the other line in the end? :>
Help is GREATLY appreciated <3
Here's the entire code:
import pygame
import time
import os
import sys
from time import sleep
from pygame.locals import *
positionx = 10
positiony = 10
entireword = []
entireword_pos = 10
counter = 0
entire_newline = False
#Sets the width and height of the screen
WIDTH = 320
HEIGHT = 240
speed = 0.05
#Importing the external screen
os.putenv('SDL_FBDEV', '/dev/fb1')
os.putenv('SDL_MOUSEDRV', 'TSLIB')
os.putenv('SDL_MOUSEDEV', '/dev/input/touchscreen')
#Initializes the screen - Careful: all pygame commands must come after the init
pygame.init()
#Sets mouse cursor visibility
pygame.mouse.set_visible(False)
#Sets the screen note: must be after pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
# initialize font; must be called after 'pygame.init()' to avoid 'Font not Initialized' error
myfont = pygame.font.SysFont("monospace", 18)
#Class
class cursors(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((10, 20))
self.image.fill((0,255,0))
self.rect = self.image.get_rect()
self.rect.center = (positionx + 10, positiony + 10)
def update(self):
self.rect.x = positionx + 10
self.rect.y = positiony
#Functions
#Prints to the screen
def print_screen(words, speed):
rel_speed = speed
for char in words:
#speed of writing
if char == ".":
sleep(0.3)
else:
sleep(rel_speed)
#re-renders previous written letters
global entireword
# Old Typewriter functionality - Changes position of cursor and text a newline
#Makes sure the previous letters are rendered and not lost
#xx is a delimter so the program can see when to make a newline and ofcourse ignore writing the delimiter
entireword.append(char)
if counter > 0:
loopcount = 1
linecount = 0 # This is to which line we are on
for prev in entireword:
if prev == 'xx':
global linecount
global positiony
global loopcount
linecount = linecount + 1
positiony = 17 * linecount
loopcount = 1
if prev != 'xx': #ignore writing the delimiter
pchar = myfont.render(prev, 1, (255,255,0))
screen.blit(pchar, (loopcount * 10, positiony))
loopcount = loopcount + 1
if char != 'xx':
# render text
letter = myfont.render(char, 1, (255,255,0))
#blits the latest letter to the screen
screen.blit(letter, (positionx, positiony))
# Appends xx as a delimiter to indicate a new line
if entire_newline == True:
entireword.append('xx')
global entire_newline
entire_newline = False
global positionx
positionx = positionx + 10
all_sprites.update()
all_sprites.draw(screen)
pygame.display.flip()
screen.fill((0,0,0)) # blackens / clears the screen
global counter
counter = counter + 1
#Positions cursor at new line
def newline():
global positionx
global positiony
positionx = 10
positiony = positiony + 17
all_sprites = pygame.sprite.Group()
cursor = cursors()
all_sprites.add(cursor)
#Main loop
running = True
while running:
global speed
global entire_newline
words = "[i] Initializing ..."
entire_newline = True
newline()
print_screen(words,speed)
words = "[i] Entering ghost mode ..."
entire_newline = True
newline()
print_screen(words,speed)
#Stops the endless loop if False
running = False
sleep(10)
Sorry if I don't answer your question directly, because your code is too confusing for me now, so I took the liberty to rewrite your code to get done what you want.
The idea is to have two sprites:
the cursor, which is a) displayed on the screen and b) keeps track of what text to write and where
the board, which is basically just a surface that the text is rendered on
Note how all the writing logic is on the Cursor class, and we have a nice, simple and dumb main loop.
import pygame
import os
#Sets the width and height of the screen
WIDTH = 320
HEIGHT = 240
#Importing the external screen
os.putenv('SDL_FBDEV', '/dev/fb1')
os.putenv('SDL_MOUSEDRV', 'TSLIB')
os.putenv('SDL_MOUSEDEV', '/dev/input/touchscreen')
#Initializes the screen - Careful: all pygame commands must come after the init
pygame.init()
clock = pygame.time.Clock()
#Sets mouse cursor visibility
pygame.mouse.set_visible(False)
#Sets the screen note: must be after pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
class Board(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((WIDTH, HEIGHT))
self.image.fill((13,13,13))
self.image.set_colorkey((13,13,13))
self.rect = self.image.get_rect()
self.font = pygame.font.SysFont("monospace", 18)
def add(self, letter, pos):
s = self.font.render(letter, 1, (255, 255, 0))
self.image.blit(s, pos)
class Cursor(pygame.sprite.Sprite):
def __init__(self, board):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((10, 20))
self.image.fill((0,255,0))
self.text_height = 17
self.text_width = 10
self.rect = self.image.get_rect(topleft=(self.text_width, self.text_height))
self.board = board
self.text = ''
self.cooldown = 0
self.cooldowns = {'.': 12,
'[': 18,
']': 18,
' ': 5,
'\n': 30}
def write(self, text):
self.text = list(text)
def update(self):
if not self.cooldown and self.text:
letter = self.text.pop(0)
if letter == '\n':
self.rect.move_ip((0, self.text_height))
self.rect.x = self.text_width
else:
self.board.add(letter, self.rect.topleft)
self.rect.move_ip((self.text_width, 0))
self.cooldown = self.cooldowns.get(letter, 8)
if self.cooldown:
self.cooldown -= 1
all_sprites = pygame.sprite.Group()
board = Board()
cursor = Cursor(board)
all_sprites.add(cursor, board)
text = """[i] Initializing ...
[i] Entering ghost mode ...
done ...
"""
cursor.write(text)
#Main loop
running = True
while running:
for e in pygame.event.get():
if e.type == pygame.QUIT:
running = False
all_sprites.update()
screen.fill((0, 0, 0))
all_sprites.draw(screen)
pygame.display.flip()
clock.tick(60)
Use the pygame.event module. Use pygame.time.set_timer() to repeatedly create a USEREVENT in the event queue. The time has to be set in milliseconds. e.g.:
typewriter_event = pygame.USEREVENT+1
pygame.time.set_timer(typewriter_event, 100)
Add a new letter to the text, when the timer event occurs:
while run:
for event in pygame.event.get():
# [...]
if event.type == typewriter_event:
text_len += 1
See also Typewriter
Minimal example:
repl.it/#Rabbid76/PyGame-Typewriter
import pygame
pygame.init()
window = pygame.display.set_mode((500, 150))
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 100)
background = pygame.Surface(window.get_size())
ts, w, h, c1, c2 = 50, *window.get_size(), (32, 32, 32), (64, 64, 64)
tiles = [((x*ts, y*ts, ts, ts), c1 if (x+y) % 2 == 0 else c2) for x in range((w+ts-1)//ts) for y in range((h+ts-1)//ts)]
for rect, color in tiles:
pygame.draw.rect(background, color, rect)
text = 'Hello World'
text_len = 0
typewriter_event = pygame.USEREVENT+1
pygame.time.set_timer(typewriter_event, 100)
text_surf = None
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == typewriter_event:
text_len += 1
if text_len > len(text):
text_len = 0
text_surf = None if text_len == 0 else font.render(text[:text_len], True, (255, 255, 128))
window.blit(background, (0, 0))
if text_surf:
window.blit(text_surf, text_surf.get_rect(midleft = window.get_rect().midleft).move(40, 0))
pygame.display.flip()
pygame.quit()
exit()

text not showing up dynamically, pygame

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?

Categories