I'm fairly new to pygame and was wondering how I can choose the position of where this text box would be displayed, because right now it is always displaying the textbox in the same position.
Ideally, I'd call the function like so: "function("Hi, how are you", (x,y)), though I'm not sure how to implement this.
(I've looked around and I cant find any answers on stackoverflow or reddit and found nothing).
def textBox(Question, Position):
#screen.fill((255,255,255))# fill the screen w/ white
question = eztext.Input(maxlength=100, color=black, prompt=Question)#Create an input with max length 45.
clock = pygame.time.Clock() # create the pygame clock
run = True
while run == True:
clock.tick(30) # make sure the program is running at 30 fps
events = pygame.event.get() # events for txtbx
for event in events:
if event.type == QUIT: # close it if x button is pressed
return
if event.type == KEYDOWN:
if event.key == K_RETURN:
textBox.userResponse = question.value
run = False
screen.fill((255,255,255)) # clear the screen
question.update(events) # update txtbx
question.draw(screen) # blit txtbx on the sceen
pygame.display.flip() # refresh the display
Here is a very simple method of creating a text render function :
def RenderText(Text, Font, Target, X, Y, R, G, B): # The target is your screen
""""Text , font, target surface, X, Y,
and color (RGB)"""
RenderedText = Font.render(Text, True, (R, G, B))
Target.blit(RenderedText, (X, Y))
To use it :
Load font :
default_font = pygame.font.SysFont(None, 30)
See the wiki for font customization
Apply in your loop :
... # Loop Stuff
# I assume that your screen is named `screen` X Y Color in RGB
RenderText('Hi, how are you?', default_font, screen, 50, 50, 255, 255, 255)
# Experiment with all the above args
I'm not using your eztext class, but try this method, it's quite simple.
Get source code of eztext, open in editor and you will see what you can do:
question = eztext.Input(x=10, y=20, ...)
or
question.set_pos(10, 20)
Source code:
class Input:
""" A text input for pygame apps """
def __init__(self, **options):
""" Options: x, y, font, color, restricted, maxlength, prompt """
self.options = Config(options, ['x', '0'], ['y', '0'], ['font', 'pygame.font.Font(None, 32)'],
['color', '(0,0,0)'], ['restricted', '\'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!"#$%&\\\'()*+,-./:;<=>?#[\]^_`{|}~\''],
['maxlength', '-1'], ['prompt', '\'\''])
self.x = self.options.x; self.y = self.options.y
self.font = self.options.font
self.color = self.options.color
self.restricted = self.options.restricted
self.maxlength = self.options.maxlength
self.prompt = self.options.prompt; self.value = ''
self.shifted = False
def set_pos(self, x, y):
""" Set the position to x, y """
self.x = x
self.y = y
To render text
choose font and size
font = pygame.font.Font(None, 40)
render text as pygame.surface.Surface()
text = font.render("Hello World", 1, (255,0,0))
display in position (10,20)
surface.blit(text, (10, 20))
You can use pygame.Rect() to keep position
create rect with size of text
text_rect = text.get_rect()
change position
text_rect.x = 10
text_rect.y = 20
or
text_rect.topleft = (10, 20)
or center on screen (or on other surface like button)
text_rect.cetner = screen.get_rect().center
display using rect
surface.blit(text, text_rect)
Related
I am new to Pygame and I am in the experimentation phase.
What I have here is a simple display surface onto which I have rendered a few overlapping Rectangles with different colors with the same width and height (96 by 144) to simulate playing cards.
I have written some code in order to be able to highlight a card with the color Yellow when I click on it. If you run the code, you'll notice that when I click on the visible part of one of the cards, all the other cards that are behind it, as well as all of those that are overlapping it are being highlighted. I cannot figure out, what should I do to click the visible part of the card and only that card to highlight.
import pygame, sys
class Card(pygame.sprite.Sprite):
def __init__(self, color):
super().__init__()
self.color = color
self.is_highlight = False
def show_card(self, x, y):
self.rect = pygame.rect.Rect(x, y, 96, 144)
pygame.draw.rect(display_surf, self.color, self.rect)
def highlight_card(self):
pos = pygame.mouse.get_pos()
if self.rect.collidepoint(pos):
self.is_highlight = True
else:
self.is_highlight = False
def update(self):
if self.is_highlight:
pygame.draw.rect(display_surf, 'Yellow', self.rect.inflate(5, 5), width=3, border_radius=5)
class Deck(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.card_deck = []
def build_deck(self):
self.card_deck.append(Card('Green'))
self.card_deck.append(Card('Red'))
self.card_deck.append(Card('Blue'))
self.card_deck.append(Card('brown'))
self.card_deck.append(Card('cadetblue'))
self.card_deck.append(Card('cornsilk'))
self.card_deck.append(Card('darkolivegreen'))
self.card_deck.append(Card('darkorchid'))
self.card_deck.append(Card('darksalmon'))
return self.card_deck
pygame.init()
WINDOW_WIDTH = 1280
WINDOW_HEIGHT = 720
display_surf = pygame.display.set_mode((WINDOW_WIDTH,WINDOW_HEIGHT))
pygame.display.set_caption('Simple Card Game')
clock = pygame.time.Clock()
card_deck = Deck().build_deck()
while True:
display_surf.fill((0, 150,0))
distance = 20
for card in card_deck:
card.show_card(distance, 20)
distance += 20
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONDOWN:
for card in card_deck:
card.highlight_card()
for card in card_deck:
card.update()
pygame.display.update()
So I think I have solved my own question and wish to share the solution.
In the show_card method I have added a new scaled_rect which represents only the visible part of the colored card with a width of only 20, while I draw on the display surface the color using the original full sized rectangle:
def show_card(self, x, y):
self.rect = pygame.rect.Rect(x, y, 96, 144)
self.scaled_rect = pygame.rect.Rect((x, y), (20, 144))
pygame.draw.rect(display_surf, self.color, self.rect)
In the highlight_card method I have made only the visible part of the color interactable with the mouse click, by using the new scaled_rect:
def highlight_card(self):
pos = pygame.mouse.get_pos()
if self.scaled_rect.collidepoint(pos):
self.is_highlight = True
else:
self.is_highlight = False
In the end, in the update method that draws the yellow colored highlight, I use the original full sized rectangle:
def update(self):
if self.is_highlight:
pygame.draw.rect(display_surf, 'Yellow', self.rect.inflate(5, 5), width=3, border_radius=5)
I am trying to make a tic-tac-toe game with pygame. An important thing I want is being able to make my images (eg. X and O) slightly translucent for when my user is only hovering over a grid tile. I also use opacity to visually show whose turn it is.
This is what I have tried:
x_tile = pygame.image.load('x_tile').convert()
x_tile.set_alpha(100)
This works fine when I'm blitting x_tile directly onto the display like this:
# This is for simplicity's sake. The actual blit process is all being done in an infinite loop
screen = pygame.display.set_mode((300, 300))
screen.blit(x_file, x_file.get_rect())
But my game is using another image that represents the grid, and that is what I'm blitting onto. So I'm blitting this board onto the display, then blitting the actual X and O tiles on the board.
screen = pygame.display.set_mode((300, 300))
screen.blit(board, board_rect)
board.blit(x_tile, x_tile.get_rect(center=grid[0].center)) # I have a list of Rects that make a grid on the board image. grid[0] is the top left
When I do it that way, x_tile.set_alpha(100) seems to have no effect and I don't know what to do.
Edit: I am using pygame 2.0.1. I'm on Windows 10.
Here is the entire code
import os
import pygame
from pygame.locals import *
# Game constants
WIN_SIZE = WIN_WIDTH, WIN_HEIGHT = 800, 600
BLACK = 0, 0, 0
WHITE = 255, 255, 255
RED = 255, 0, 0
BLUE = 0, 0, 255
# Game functions
class NoneSound:
"""dummy class for when pygame.mixer did not init
and there is no sound available"""
def play(self): pass
def load_sound(file):
"""loads a sound file, prepares it for play"""
if not pygame.mixer:
return NoneSound()
music_to_load = os.path.join('sounds', file)
try:
sound = pygame.mixer.Sound(music_to_load)
except pygame.error as message:
print('Cannot load following sound:', music_to_load)
raise SystemExit(message)
return sound
def load_image(file, colorkey=None, size=None):
"""loads image into game"""
image_to_load = os.path.join('images', file)
try:
image = pygame.image.load(image_to_load).convert()
except pygame.error as message:
print('Cannot load following image:', image_to_load)
raise SystemExit(message)
if colorkey is not None:
if colorkey == -1:
colorkey = image.get_at((0, 0))
image.set_colorkey(colorkey, RLEACCEL)
if size is not None:
image = pygame.transform.scale(image, size)
return image
# Game class
class TTTVisual:
"""Controls game visuals"""
def __init__(self, win: pygame.Surface):
self.win = win
# Load in game images
self.board = load_image('board.png', size=(600, 450), colorkey=WHITE)
self.x_tile = load_image('X_tile.png', size=(100, 100), colorkey=BLACK)
self.o_tile = load_image('O_tile.png', size=(100, 100), colorkey=BLACK)
# Translucent for disabled looking tile
self.x_tile_trans = self.x_tile.copy()
self.o_tile_trans = self.o_tile.copy()
self.x_tile_trans.set_alpha(100)
self.o_tile_trans.set_alpha(100)
# Used to let user know whose turn it is
self.x_turn = pygame.transform.scale(self.x_tile, (50, 50))
self.o_turn = pygame.transform.scale(self.o_tile, (50, 50))
self.x_turn_trans = pygame.transform.scale(self.x_tile_trans, (50, 50))
self.o_turn_trans = pygame.transform.scale(self.o_tile_trans, (50, 50))
self.get_rects()
self.grid = self.setup_grid()
def get_rects(self):
"""Creates coords for some visual game assets"""
self.board_rect = self.board.get_rect(
center=self.win.get_rect().center)
self.x_turn_rect = self.x_turn.get_rect(top=10, left=10)
self.o_turn_rect = self.o_turn.get_rect(top=10, left=WIN_WIDTH-60)
def setup_grid(self):
grid = []
left = 0
top = 150
row = 0
for i in range(9):
if (i != 0) and (i % 3 == 0):
row += 1
left = 0
grid.append(pygame.Rect(left, row*top, 200, 150))
left += 200
return grid
def update_turn_status(self):
"""Updates the X and O tiles on the top left and right to
let user know whose turn it is"""
self.win.blits((
(self.x_turn_trans, self.x_turn_rect),
(self.o_turn, self.o_turn_rect)
))
def update_grid(self):
"""Updates board"""
self.win.blit(self.board, self.board_rect)
# Here is where you could change board to win and see that the tile changes in opacity
self.board.blit(self.x_tile_trans, self.x_tile_trans.get_rect(center=self.grid[0].center))
def update(self):
self.win.fill(WHITE)
self.update_turn_status()
self.update_grid()
pygame.display.flip()
def main():
pygame.init()
win = pygame.display.set_mode(WIN_SIZE)
tttvisual = TTTVisual(win)
tttfunc = TTTFunc(tttvisual)
clock = pygame.time.Clock()
running = True
while running:
clock.tick(60)
for event in pygame.event.get():
if event.type == QUIT:
running = False
tttvisual.update()
pygame.quit()
if __name__ == "__main__":
main()
The issue is caused by the line:
self.board.blit(self.x_tile_trans, self.x_tile_trans.get_rect(center=self.grid[0].center))
You don't blit the image on the display Surface, but on the self.board Surface. When a Surface is blit, it is blended with the target. When you draw on a Surface, it changes permanently. Since you do that over and over again, in every frame, the source Surface appears to by opaque. When you decrease the alpha value (e.g. self.x_tile_trans.set_alpha(5)), a fade in effect will appear.
Never draw on an image Surface. Always draw on the display Surface. Cleat the display at begin of a frame. Draw the entire scene in each frame and update the display once at the end of the frame.
class TTTVisual:
# [...]
def update_grid(self):
"""Updates board"""
self.win.blit(self.board, self.board_rect)
# Here is where you could change board to win and see that the tile changes in opacity
x, y = self.grid[0].center
x += self.board_rect.x
y += self.board_rect.y
self.win.blit(self.x_tile_trans, self.x_tile_trans.get_rect(center=(x, y)))
The typical PyGame application loop has to:
handle the events by either pygame.event.pump() or pygame.event.get().
update the game states and positions of objects dependent on the input events and time (respectively frames)
clear the entire display or draw the background
draw the entire scene (blit all the objects)
update the display by either pygame.display.update() or pygame.display.flip()
im not sure why im getting errors, as it was working perfectly fine before i started to add a button to my game. Ultimately my goal is to create a game menu with the options to start game which then moves onto the next screen, the level platform, and quit, which ultimately will close the window. Bonus, if i can get help on this, im thinking about also creating a sign up and login page, i already have one set up using tkinter but im not sure how to access the stored information using pygames, so it saves game progress from username and password. Also, what website do you guys use to get free images for your games? I can't draw that well and am having trouble finding images that match what im trying to accomplish.
# imports
import pygame, sys
import pygame.freetype
from pygame.sprite import Sprite
from pygame.rect import Rect
# colors
green = (0, 255, 0)
black = (0, 0, 0)
# buttons
def create_text(text, font_size, text_color, bg_color):
font = pygame.freetype.Sysfont("Courioer", font_size, bold=True)
surface, _ = font.render(text=text, fgcolor=text_color, bgcolor = bg_color)
return surface.convert_alpha()
class UIElement(Sprite):
def __init__(self, center_position, text, font_size, bg_color, text_color):
super().__init__()
self.mouse_over = False
default_image = create_text(text, font_size, text_color, bg_text)
highlighted_image = create_text(text, font_size * 1.2, text_color, bg_text)
self.images = [default_image, highlighted_image]
self.rects = [
default_image.get_rect(center = center_position),
highlighted_image.get_rect(center = center_position)]
super().__init__()
#property
def image(self):
return self.image[1] if self.mouse_over else self.image[0]
#property
def rect(self):
return self.rects[1] if self.mouse_over else self.rects[0]
def update(self, mouse_pos):
if self.rect.collidepoint(mouse_pos):
self.mouse_over = True
else:
self.mouse_over = False
def draw(self, surface):
surface.blit(self.image, self.rect)
# Title
pygame.display.set_caption("Zombrio")
icon = pygame.image.load("zombie.png")
pygame.display.set_icon(icon)
# character
survivorImg = pygame.image.load("frankenstein.png")
survivorx = 1
survivory = 400
def player():
screen.blit(survivorImg, (survivorx, survivory))
def main():
pygame.init()
# screen
screen = pygame.display.set_mode((800, 800))
clock = pygame.time.Clock()
uielement = UIElement(
center_position = (400, 400),
font_size = 30,
bg_color = black,
text_color = green,
text = "Start"
)
# Game Loop
while True:
screen.fill(black) # not defined error, only occurred after creating buttons
for event in pygame.event.get(): # video system not initialized
if event.type == pygame.QUIT: # indent error
pygame.quit()
sys.exit()
uielement.update(pygame.mouse.get_pos()) # says uielement is not defined
uielement.draw(screen)
pygame.display.flip()
player()
pygame.display.update()
clock.tick(120)
main()
pygame.quit()
So """Text""" is a string literal. You can use it for multi-line strings.
https://developers.google.com/edu/python/strings#:~:text=String%20literals%20inside%20triple%20quotes,go%20to%20represent%20computed%20values.
You can also use it to create a docstring.
The way you are using it here does not look correct. If you want to print those statements, then trying print("""Text""") on the next line.
If you want to use it as a comment on a line of code, then follow it with the "#"
For more examples of using tripple quotes, see here: https://www.geeksforgeeks.org/triple-quotes-in-python/
In this program when the user types more text I want the rectangle to automatically get longer when the user types to keep the letters inside of the rectangle. However, it doesn't update the rectangle when the text gets longer. How do I fix this?
from pygame import *
init()
screen = display.set_mode((800, 600))
name_font = font.Font(None, 32)
name_text = ''
class Rectangle:
def __init__(self, x, y):
self.x = x
self.y = y
self.input_rect = Rect(x, y, 140, 32)
self.text_surface = name_font.render(name_text, True, (255, 255, 255))
color = Color('lightskyblue3')
draw.rect(screen, color, self.input_rect, 2)
self.input_rect.w = self.text_surface.get_width() + 10
screen.blit(self.text_surface, (self.input_rect.x + 5, self.input_rect.y + 5))
def naming():
global name_text
if events.type == KEYDOWN:
if keys[K_BACKSPACE]:
name_text = name_text[:-1]
screen.fill((0, 0, 0))
rect_1 = Rectangle(200, 200)
else:
name_text += events.unicode
while True:
rect_1 = Rectangle(200, 200)
for events in event.get():
keys = key.get_pressed()
naming()
if events.type == QUIT:
quit()
display.update()
time.delay(1)
The Rectangle.text_surface is a PyGame Surface. So you can easily get the precise width of the bounding box by simply calling self.text_surface.get_width().
But you start the size of the border-rect at 140, so this size has to be the maximum of 140 or whatever the new (longer) width is. Another problem is that when the rectangle re-sizes, the old rectangle is left behind. So whenever we now re-draw the rectangle, it erases the background to black.
This is all pretty easily encapsulated into the exiting __init__():
def __init__(self, x, y):
self.x = x
self.y = y
self.text_surface = name_font.render(name_text, True, (255, 255, 255))
rect_width = max( 140, 10 + self.text_surface.get_width() ) # Adjust the width
color = Color('lightskyblue3')
self.input_rect = Rect(x, y, rect_width, 32) # Use new width (if any)
draw.rect(screen, (0,0,0) , self.input_rect, 0) # Erase any existing rect
draw.rect(screen, color, self.input_rect, 2)
self.input_rect.w = self.text_surface.get_width() + 10
screen.blit(self.text_surface, (self.input_rect.x + 5, self.input_rect.y + 5))
This question already has answers 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)
How to detect collisions between two rectangular objects or images in pygame
(1 answer)
Closed 2 years ago.
I am trying to make a canvas for pixel art.
class Canvas:
def __init__(self):
self.__blocks = []
self.__positions = []
for i in range(1830):
self.__blocks.append(pygame.Surface((20, 20)).convert())
for y in range(30):
y *= 20
for x in range(61):
x = x* 20
self.__positions.append([x, y])
self.__color = False
def draw(self, window):
for i in range(1830):
self.__color = not self.__color
if self.__color:
self.__blocks[i].fill((200, 200, 200))
else:
self.__blocks[i].fill((50, 50, 50))
window.blit(self.__blocks[i], (self.__positions[i][0]
, self.__positions[i][1]))
Here I am trying to generate and draw 1830 unique surfaces and this works. I then tried implementing collision detection between each block and the mouse and failed.
def collided(self, pos):
for i in range(1380):
block = self.__blocks[i].get_rect()
if block.collidepoint(pos[0], pos[1]):
print(block.x, block.y)
Then I did different tests on why it might be failing. Here is one of them. I will change a single block's color, in our case the 10th block self.__blocks[10].fill((255, 0, 0)) to red so we know which box to click on. Then we will try to check for collision for that particular block.
def testBlock(self, pos):
block = self.__blocks[10].get_rect()
if block.collidepoint(pos[0], pos[1]):
print(block.x)
And it doesn't work, but the weird thing is it works for the first block(in the 0th index) and only the first block no matter which surface I test. Any idea on how to fix this would be appreciated. The following is copy and paste code.
import pygame
pygame.init()
win = pygame.display
D = win.set_mode((1220, 600))
class Canvas:
def __init__(self):
self.__blocks = []
self.__positions = []
for i in range(1830):
self.__blocks.append(pygame.Surface((20, 20)).convert())
for y in range(30):
y *= 20
for x in range(61):
x = x* 20
self.__positions.append([x, y])
self.__color = False
self.testBlock = 10
def draw(self, window):
for i in range(1830):
self.__color = not self.__color
if self.__color:
self.__blocks[i].fill((200, 200, 200))
else:
self.__blocks[i].fill((50, 50, 50))
self.__blocks[self.testBlock].fill((255, 0, 0)) # Changing the color for testing
window.blit(self.__blocks[i], (self.__positions[i][0]
, self.__positions[i][1]))
def test(self, pos):
block = self.__blocks[self.testBlock].get_rect()
if block.collidepoint(pos[0], pos[1]):
print(block.x, block.y)
canvas = Canvas()
while True:
D.fill((0, 0, 0))
pygame.event.get()
mousepos = pygame.mouse.get_pos()
canvas.draw(D)
canvas.test(mousepos)
win.flip()
When you call .get_rect() on a Surface, it does not know its current position, because that is not Surface information. So you need to assign the location to the Rect before collision detection.
With your current code layout, you could do this during the construction. With the Canvass blocks position now held in the __rects list, the __positions list becomes superfluous.
class Canvass:
def __init__(self):
self.__blocks = []
self.__rects = []
for y in range( 30 ):
for x in range( 61 ):
self.__blocks.append(pygame.Surface((20, 20)).convert())
self.__rects.append( self.__blocks[-1].get_rect() )
self.__rects[-1].topleft = ( x, y )
self.__color = False
self.testBlock = 10
This gives you a simple test:
def collided(self, pos):
hit = False
for i in range( len( self.__rects ) ):
if ( self.__rects[i].collidepoint( pos[0], pos[1] ) ):
print( "Click on block %d" % ( i ) )
hit = True
break
return hit, i
.get_rect() gives rect with block's size but with position (0, 0)
you have real position in __positions and you would need
.get_rect(topleft=self.__positions[self.testBlock])
def test(self, pos):
block = self.__blocks[self.testBlock].get_rect(topleft=self.__positions[self.testBlock])
if block.collidepoint(pos[0], pos[1]):
print(block.x, block.y)
But it would be better to get rect and set its position at start and later not use get_rect().
You could also create class Pixel similar to class Sprite with self.image to keep surface and self.rect to keep its size and position. And then you could use Group to check collision with all pixels.
EDIT:
Example which uses class pygame.sprite.Sprite to create class Pixel and it keeps all pixels in pygame.sprite.Group
It also handle events (MOUSEBUTTONDOWN) to change color in any pixel when it is clicked.
import pygame
# --- classes ---
class Pixel(pygame.sprite.Sprite):
def __init__(self, x, y, color, width=20, height=20):
super().__init__()
self.color_original = color
self.color = color
self.image = pygame.Surface((20, 20)).convert()
self.image.fill(self.color)
self.rect = pygame.Rect(x, y, width, height)
def handle_event(self, event):
if event.type == pygame.MOUSEBUTTONDOWN:
if self.rect.collidepoint(event.pos):
if self.color != self.color_original:
self.color = self.color_original
else:
self.color = (255,0,0)
self.image.fill(self.color)
# event handled
return True
# event not handled
return False
class Canvas:
def __init__(self):
# create group for sprites
self.__blocks = pygame.sprite.Group()
# create sprites
self.__color = False
for y in range(30):
y *= 20
for x in range(61):
x *= 20
self.__color = not self.__color
if self.__color:
color = (200, 200, 200)
else:
color = (50, 50, 50)
self.__blocks.add(Pixel(x, y, color))
# changing the color for testing
self.testBlock = 10
all_sprites = self.__blocks.sprites()
block = all_sprites[self.testBlock]
block.image.fill((255, 0, 0))
def draw(self, window):
# draw all sprites in group
self.__blocks.draw(window)
def test(self, pos):
# test collision with one sprite
all_sprites = self.__blocks.sprites()
block = all_sprites[self.testBlock]
if block.rect.collidepoint(pos):
print(block.rect.x, block.rect.y)
def handle_event(self, event):
for item in self.__blocks:
if item.handle_event(event):
# don't check other pixels if event already handled
return True
# --- main ---
pygame.init()
win = pygame.display
D = win.set_mode((1220, 600))
canvas = Canvas()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
canvas.handle_event(event)
#mousepos = pygame.mouse.get_pos()
#canvas.test(mousepos)
# draws (without updates, etc)
#D.fill((0, 0, 0)) # no need clean screen if it will draw all elements again
canvas.draw(D)
win.flip()