pygame Font to Surface to mainSurface [duplicate] - pygame

Is there a way I can display text on a pygame window using python?
I need to display a bunch of live information that updates and would rather not make an image for each character I need.
Can I blit text to the screen?

Yes. It is possible to draw text in pygame:
# initialize font; must be called after 'pygame.init()' to avoid 'Font not Initialized' error
myfont = pygame.font.SysFont("monospace", 15)
# render text
label = myfont.render("Some text!", 1, (255,255,0))
screen.blit(label, (100, 100))

You can use your own custom fonts by setting the font path using pygame.font.Font
pygame.font.Font(filename, size): return Font
example:
pygame.font.init()
font_path = "./fonts/newfont.ttf"
font_size = 32
fontObj = pygame.font.Font(font_path, font_size)
Then render the font using fontObj.render and blit to a surface as in veiset's answer above. :)

I have some code in my game that displays live score. It is in a function for quick access.
def texts(score):
font=pygame.font.Font(None,30)
scoretext=font.render("Score:"+str(score), 1,(255,255,255))
screen.blit(scoretext, (500, 457))
and I call it using this in my while loop:
texts(score)

There are 2 possibilities. In either case PyGame has to be initialized by pygame.init.
import pygame
pygame.init()
Use either the pygame.font module and create a pygame.font.SysFont or pygame.font.Font object. render() a pygame.Surface with the text and blit the Surface to the screen:
my_font = pygame.font.SysFont(None, 50)
text_surface = myfont.render("Hello world!", True, (255, 0, 0))
screen.blit(text_surface, (10, 10))
Or use the pygame.freetype module. Create a pygame.freetype.SysFont() or pygame.freetype.Font object. render() a pygame.Surface with the text or directly render_to() the text to the screen:
my_ft_font = pygame.freetype.SysFont('Times New Roman', 50)
my_ft_font.render_to(screen, (10, 10), "Hello world!", (255, 0, 0))
See also Text and font
Minimal pygame.font example: repl.it/#Rabbid76/PyGame-Text
import pygame
pygame.init()
window = pygame.display.set_mode((500, 150))
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 100)
text = font.render('Hello World', True, (255, 0, 0))
background = pygame.Surface(window.get_size())
ts, w, h, c1, c2 = 50, *window.get_size(), (128, 128, 128), (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)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.blit(background, (0, 0))
window.blit(text, text.get_rect(center = window.get_rect().center))
pygame.display.flip()
pygame.quit()
exit()
Minimal pygame.freetype example: repl.it/#Rabbid76/PyGame-FreeTypeText
import pygame
import pygame.freetype
pygame.init()
window = pygame.display.set_mode((500, 150))
clock = pygame.time.Clock()
ft_font = pygame.freetype.SysFont('Times New Roman', 80)
background = pygame.Surface(window.get_size())
ts, w, h, c1, c2 = 50, *window.get_size(), (128, 128, 128), (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)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.blit(background, (0, 0))
text_rect = ft_font.get_rect('Hello World')
text_rect.center = window.get_rect().center
ft_font.render_to(window, text_rect.topleft, 'Hello World', (255, 0, 0))
pygame.display.flip()
pygame.quit()
exit()

I wrote a wrapper, that will cache text surfaces, only re-render when dirty. googlecode/ninmonkey/nin.text/demo/

I wrote a TextBox class. It can use many custom fonts relatively easily and specify colors.
I wanted to have text in several places on the screen, some of which would update such as lives, scores (of all players) high score, time passed and so on.
Firstly, I created a fonts folder in the project and loaded in the fonts I wanted to use. As an example, I had 'arcade.ttf' in my fots folder. When making an instance of the TextBox, I could specify that font using the fontlocation (optional) arg.
e.g.
self.game_over_text = TextBox("GAME OVER", 100, 80, 420, RED, 'fonts/arcade.ttf')
I found making the text and updating it each time "clunky" so my solution was an update_text method.
For example, updating the Player score:
self.score1_text.update_text(f'{self.p1.score}')
It could be refactored to accept a list of str, but it suited my needs for coding a version of "S
# -*- coding: utf-8 -*-
'''
#author: srattigan
#date: 22-Mar-2022
#project: TextBox class example
#description: A generic text box class
to simplify text objects in PyGame
Fonts can be downloaded from
https://www.dafont.com/
and other such sites.
'''
# imports
import pygame
# initialise and globals
WHITE = (255, 255, 255)
pygame.font.init() # you have to call this at the start
class TextBox:
'''
A text box class to simplify creating text in pygame
'''
def __init__(self, text, size, x=50, y=50, color=WHITE, fontlocation=None):
'''
Constuctor
text: str, the text to be displayed
size: int, the font size
x: int, x-position on the screen
y: int, y-position on the screen
color: tuple of int representing color, default is (255,255,255)
fontlocation: str, location of font file. If None, default system font is used.
'''
pygame.font.init()
self.text = text
self.size = size
self.color = color
self.x = x
self.y = y
if fontlocation == None:
self.font = pygame.font.SysFont('Arial', self.size)
else:
self.font = pygame.font.Font(fontlocation, self.size)
def draw(self, screen):
'''
Draws the text box to the screen passed.
screen: a pygame Surface object
'''
text_surface = self.font.render(f'{self.text}', False, self.color)
screen.blit(text_surface, [self.x, self.y])
def update_text(self, new_text):
'''
Modifier- Updates the text variable in the textbox instance
new_text: str, the updated str for the instance.
'''
if not isinstance(new_text, str):
raise TypeError("Invalid type for text object")
self.text = new_text
def set_position(self, x, y):
'''
Modifier- change or set the position of the txt box
x: int, x-position on the screen
y: int, y-position on the screen
'''
self.x = x
self.y = y
def __repr__(self):
rep = f'TextBox instance, \n\ttext: {self.text} \n\tFontFamly:{self.font} \n\tColor: {self.color} \n\tSize: {self.size} \n\tPos: {self.x, self.y}'
return rep
if __name__ == "__main__":
test = TextBox("Hello World", 30, 30, 30)
print(test)
To use this in my Game class
from textbox import TextBox
and in the initialisation part of the game, something like this:
self.time_text = TextBox("Time Left: 100", 20, 20, 40)
self.cred_text = TextBox("created by Sean R.", 15, 600, 870)
self.score1_text = TextBox("0", 100, 40, 650)
self.score2_text = TextBox("0", 100, 660, 650)
self.lives1_text = TextBox("[P1] Lives: 3", 20, 40, 750)
self.lives2_text = TextBox("[P2] Lives: 3", 20, 660, 750)
self.game_over_text = TextBox("GAME OVER", 100, 80, 420, RED)
self.textbox_list = []
self.textbox_list.append(self.time_text)
self.textbox_list.append(self.cred_text)
self.textbox_list.append(self.score1_text)
self.textbox_list.append(self.score2_text)
self.textbox_list.append(self.lives1_text)
self.textbox_list.append(self.lives2_text)
so that when I want to draw all on the screen:
for txt in self.textbox_list:
txt.draw(screen)
In the update section of the game, I only update directly the boxes that have updated text using the update_text method- if there is nothing to be updated, the text stays the same.

I wrote a TextElement class to handle text placement. It's still has room for improvement. One thing to improve is to add fallback fonts using SysFont in case the font asset isn't available.
import os
from typing import Tuple, Union
from pygame.font import Font
from utils.color import Color
class TextElement:
TEXT_SIZE = 50
def __init__(self, surface, size=TEXT_SIZE, color=Color('white'), font_name='Kanit-Medium') -> None:
self.surface = surface
self._font_name = font_name
self._size = size
self.color = color
self.font = self.__initialize_font()
#property
def font_name(self):
return self._font_name
#font_name.setter
def font_name(self, font_name):
self._font_name = font_name
self.font = self.__initialize_font()
#font_name.deleter
def font_name(self):
del self._font_name
#property
def size(self):
return self._size
#size.setter
def size(self, size):
self._size = size
self.font = self.__initialize_font()
#size.deleter
def size(self):
del self._size
def write(self, text: str, coordinates: Union[str, Tuple[int, int]] = 'center'):
rendered_text = self.font.render(text, True, self.color)
if isinstance(coordinates, str):
coordinates = self.__calculate_alignment(rendered_text, coordinates)
self.surface.blit(rendered_text, coordinates)
return self
def __calculate_alignment(self, rendered_text, alignment):
# https://www.pygame.org/docs/ref/surface.html#pygame.Surface.get_rect
# Aligns rendered_text to the surface at the given alignment position
# e.g: rendered_text.get_rect(center=self.surface.get_rect().center)
alignment_coordinates = getattr(self.surface.get_rect(), alignment)
return getattr(rendered_text, 'get_rect')(**{alignment: alignment_coordinates}).topleft
def __initialize_font(self):
return Font(os.path.join(
'assets', 'fonts', f'{self._font_name}.ttf'), self._size)
Here is how you can use it:
TextElement(self.screen, 80).write('Hello World!', 'midtop')
TextElement(self.screen).write('Hello World 2!', (250, 100))
# OR
text = TextElement(self.screen, 80)
text.size = 100
text.write('Bigger text!', (25, 50))
text.write('Bigger text!', 'midbottom')
I hope this can help someone! Cheers!

Related

I am trying to display a message to the screen of my game put pygame continues to give me an error [duplicate]

Is there a way I can display text on a pygame window using python?
I need to display a bunch of live information that updates and would rather not make an image for each character I need.
Can I blit text to the screen?
Yes. It is possible to draw text in pygame:
# initialize font; must be called after 'pygame.init()' to avoid 'Font not Initialized' error
myfont = pygame.font.SysFont("monospace", 15)
# render text
label = myfont.render("Some text!", 1, (255,255,0))
screen.blit(label, (100, 100))
You can use your own custom fonts by setting the font path using pygame.font.Font
pygame.font.Font(filename, size): return Font
example:
pygame.font.init()
font_path = "./fonts/newfont.ttf"
font_size = 32
fontObj = pygame.font.Font(font_path, font_size)
Then render the font using fontObj.render and blit to a surface as in veiset's answer above. :)
I have some code in my game that displays live score. It is in a function for quick access.
def texts(score):
font=pygame.font.Font(None,30)
scoretext=font.render("Score:"+str(score), 1,(255,255,255))
screen.blit(scoretext, (500, 457))
and I call it using this in my while loop:
texts(score)
There are 2 possibilities. In either case PyGame has to be initialized by pygame.init.
import pygame
pygame.init()
Use either the pygame.font module and create a pygame.font.SysFont or pygame.font.Font object. render() a pygame.Surface with the text and blit the Surface to the screen:
my_font = pygame.font.SysFont(None, 50)
text_surface = myfont.render("Hello world!", True, (255, 0, 0))
screen.blit(text_surface, (10, 10))
Or use the pygame.freetype module. Create a pygame.freetype.SysFont() or pygame.freetype.Font object. render() a pygame.Surface with the text or directly render_to() the text to the screen:
my_ft_font = pygame.freetype.SysFont('Times New Roman', 50)
my_ft_font.render_to(screen, (10, 10), "Hello world!", (255, 0, 0))
See also Text and font
Minimal pygame.font example: repl.it/#Rabbid76/PyGame-Text
import pygame
pygame.init()
window = pygame.display.set_mode((500, 150))
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 100)
text = font.render('Hello World', True, (255, 0, 0))
background = pygame.Surface(window.get_size())
ts, w, h, c1, c2 = 50, *window.get_size(), (128, 128, 128), (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)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.blit(background, (0, 0))
window.blit(text, text.get_rect(center = window.get_rect().center))
pygame.display.flip()
pygame.quit()
exit()
Minimal pygame.freetype example: repl.it/#Rabbid76/PyGame-FreeTypeText
import pygame
import pygame.freetype
pygame.init()
window = pygame.display.set_mode((500, 150))
clock = pygame.time.Clock()
ft_font = pygame.freetype.SysFont('Times New Roman', 80)
background = pygame.Surface(window.get_size())
ts, w, h, c1, c2 = 50, *window.get_size(), (128, 128, 128), (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)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.blit(background, (0, 0))
text_rect = ft_font.get_rect('Hello World')
text_rect.center = window.get_rect().center
ft_font.render_to(window, text_rect.topleft, 'Hello World', (255, 0, 0))
pygame.display.flip()
pygame.quit()
exit()
I wrote a wrapper, that will cache text surfaces, only re-render when dirty. googlecode/ninmonkey/nin.text/demo/
I wrote a TextBox class. It can use many custom fonts relatively easily and specify colors.
I wanted to have text in several places on the screen, some of which would update such as lives, scores (of all players) high score, time passed and so on.
Firstly, I created a fonts folder in the project and loaded in the fonts I wanted to use. As an example, I had 'arcade.ttf' in my fots folder. When making an instance of the TextBox, I could specify that font using the fontlocation (optional) arg.
e.g.
self.game_over_text = TextBox("GAME OVER", 100, 80, 420, RED, 'fonts/arcade.ttf')
I found making the text and updating it each time "clunky" so my solution was an update_text method.
For example, updating the Player score:
self.score1_text.update_text(f'{self.p1.score}')
It could be refactored to accept a list of str, but it suited my needs for coding a version of "S
# -*- coding: utf-8 -*-
'''
#author: srattigan
#date: 22-Mar-2022
#project: TextBox class example
#description: A generic text box class
to simplify text objects in PyGame
Fonts can be downloaded from
https://www.dafont.com/
and other such sites.
'''
# imports
import pygame
# initialise and globals
WHITE = (255, 255, 255)
pygame.font.init() # you have to call this at the start
class TextBox:
'''
A text box class to simplify creating text in pygame
'''
def __init__(self, text, size, x=50, y=50, color=WHITE, fontlocation=None):
'''
Constuctor
text: str, the text to be displayed
size: int, the font size
x: int, x-position on the screen
y: int, y-position on the screen
color: tuple of int representing color, default is (255,255,255)
fontlocation: str, location of font file. If None, default system font is used.
'''
pygame.font.init()
self.text = text
self.size = size
self.color = color
self.x = x
self.y = y
if fontlocation == None:
self.font = pygame.font.SysFont('Arial', self.size)
else:
self.font = pygame.font.Font(fontlocation, self.size)
def draw(self, screen):
'''
Draws the text box to the screen passed.
screen: a pygame Surface object
'''
text_surface = self.font.render(f'{self.text}', False, self.color)
screen.blit(text_surface, [self.x, self.y])
def update_text(self, new_text):
'''
Modifier- Updates the text variable in the textbox instance
new_text: str, the updated str for the instance.
'''
if not isinstance(new_text, str):
raise TypeError("Invalid type for text object")
self.text = new_text
def set_position(self, x, y):
'''
Modifier- change or set the position of the txt box
x: int, x-position on the screen
y: int, y-position on the screen
'''
self.x = x
self.y = y
def __repr__(self):
rep = f'TextBox instance, \n\ttext: {self.text} \n\tFontFamly:{self.font} \n\tColor: {self.color} \n\tSize: {self.size} \n\tPos: {self.x, self.y}'
return rep
if __name__ == "__main__":
test = TextBox("Hello World", 30, 30, 30)
print(test)
To use this in my Game class
from textbox import TextBox
and in the initialisation part of the game, something like this:
self.time_text = TextBox("Time Left: 100", 20, 20, 40)
self.cred_text = TextBox("created by Sean R.", 15, 600, 870)
self.score1_text = TextBox("0", 100, 40, 650)
self.score2_text = TextBox("0", 100, 660, 650)
self.lives1_text = TextBox("[P1] Lives: 3", 20, 40, 750)
self.lives2_text = TextBox("[P2] Lives: 3", 20, 660, 750)
self.game_over_text = TextBox("GAME OVER", 100, 80, 420, RED)
self.textbox_list = []
self.textbox_list.append(self.time_text)
self.textbox_list.append(self.cred_text)
self.textbox_list.append(self.score1_text)
self.textbox_list.append(self.score2_text)
self.textbox_list.append(self.lives1_text)
self.textbox_list.append(self.lives2_text)
so that when I want to draw all on the screen:
for txt in self.textbox_list:
txt.draw(screen)
In the update section of the game, I only update directly the boxes that have updated text using the update_text method- if there is nothing to be updated, the text stays the same.
I wrote a TextElement class to handle text placement. It's still has room for improvement. One thing to improve is to add fallback fonts using SysFont in case the font asset isn't available.
import os
from typing import Tuple, Union
from pygame.font import Font
from utils.color import Color
class TextElement:
TEXT_SIZE = 50
def __init__(self, surface, size=TEXT_SIZE, color=Color('white'), font_name='Kanit-Medium') -> None:
self.surface = surface
self._font_name = font_name
self._size = size
self.color = color
self.font = self.__initialize_font()
#property
def font_name(self):
return self._font_name
#font_name.setter
def font_name(self, font_name):
self._font_name = font_name
self.font = self.__initialize_font()
#font_name.deleter
def font_name(self):
del self._font_name
#property
def size(self):
return self._size
#size.setter
def size(self, size):
self._size = size
self.font = self.__initialize_font()
#size.deleter
def size(self):
del self._size
def write(self, text: str, coordinates: Union[str, Tuple[int, int]] = 'center'):
rendered_text = self.font.render(text, True, self.color)
if isinstance(coordinates, str):
coordinates = self.__calculate_alignment(rendered_text, coordinates)
self.surface.blit(rendered_text, coordinates)
return self
def __calculate_alignment(self, rendered_text, alignment):
# https://www.pygame.org/docs/ref/surface.html#pygame.Surface.get_rect
# Aligns rendered_text to the surface at the given alignment position
# e.g: rendered_text.get_rect(center=self.surface.get_rect().center)
alignment_coordinates = getattr(self.surface.get_rect(), alignment)
return getattr(rendered_text, 'get_rect')(**{alignment: alignment_coordinates}).topleft
def __initialize_font(self):
return Font(os.path.join(
'assets', 'fonts', f'{self._font_name}.ttf'), self._size)
Here is how you can use it:
TextElement(self.screen, 80).write('Hello World!', 'midtop')
TextElement(self.screen).write('Hello World 2!', (250, 100))
# OR
text = TextElement(self.screen, 80)
text.size = 100
text.write('Bigger text!', (25, 50))
text.write('Bigger text!', 'midbottom')
I hope this can help someone! Cheers!

User input recorded from multiple text boxes and put into MySQL

I'm running into a brick wall and have spent multiple hours trying to research how to do this and I'm at the point I'm not sure if I'm asking the right question.
I have a page for recording user input in multiple boxes. I've made a separate file for my text box function which feeds into the main page. I want to be able to click next and record all the use input from the text boxes into a MySQL database.
The problem I'm running into seems to be that the main page doesn't know there's user input just that there is a text box, so I can't record anything, while if I change the text input file it defeats the purpose of creating one so that I can use text boxes anywhere.
Main page
import pygame
import Test
import button
import sys
import mysql.connector
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Main Page")
icon = pygame.image.load('icons8-robber-32.png')
pygame.display.set_icon(icon)
colour_active = pygame.Color('orange')
colour_inactive = pygame.Color('Yellow')
font = pygame.font.Font(None, 32)
start_image = pygame.image.load('play.png').convert_alpha()
exit_image = pygame.image.load('exit.png').convert_alpha()
menu_image = pygame.image.load('menu.png').convert_alpha()
back_image = pygame.image.load('back.png').convert_alpha()
next_image = pygame.image.load('next.png').convert_alpha()
def register():
input_box1 = Test.inputBox(210, 105, 140, 32, "First Name: ")
input_box2 = Test.inputBox(210, 160, 140, 32)
input_boxes = [input_box1, input_box2]
back_button = button.Button(100, 450, back_image, 0.2)
next_button = button.Button(450, 450, next_image, 0.2)
done = False
while not done:
for event in pygame.event.get():
for box in input_boxes:
box.handle_event(event)
for box in input_boxes:
box.update()
screen.fill((30, 30, 30))
for box in input_boxes:
box.draw(screen)
if back_button.draw(screen):
print("no")
if next_button.draw(screen):
sys.exit()
pygame.display.update()
start_button = button.Button(100, 150, start_image, 0.2)
exit_button = button.Button(250, 150, exit_image, 0.2)
menu_button = button.Button(400, 150, menu_image, 0.2)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill((150, 255, 100))
if start_button.draw(screen):
print("no")
if exit_button.draw(screen):
sys.exit()
if menu_button.draw(screen):
register()
pygame.display.update()
pygame.QUIT()
sys.exit()
Text box file
import pygame
pygame.init()
colour_active = pygame.Color('orange')
colour_inactive = pygame.Color('Yellow')
font = pygame.font.Font(None, 32)
class inputBox():
def __init__(self, x, y, w, h, text=''):
self.rect = pygame.Rect(x,y,w,h)
self.color = colour_inactive
self.text = text
self.text_surface = font.render(text, True, self.color)
self.active = False
def handle_event(self,event):
if event.type == pygame.MOUSEBUTTONDOWN:
if self.rect.collidepoint(event.pos):
self.active = not self.active
else:
self.active = False
self.color = colour_active if self.active else colour_inactive
if event.type == pygame.KEYDOWN:
if self.active:
if event.key == pygame.K_RETURN:
print(self.text)
elif event.key == pygame.K_BACKSPACE:
self.text = self.text [:-1]
else:
self.text += event.unicode
self.text_surface = font.render(self.text, True, self.color)
def update(self):
self.rect.w = max(100, self.text_surface.get_width() + 10)
def draw(self, screen):
screen.blit(self.text_surface, (self.rect.x+5, self.rect.y+5))
pygame.draw.rect(screen, self.color, self.rect, 2)
You do not need the self.text file in the main code. So instead of writing input_box1.self.text, it was just input_box1.text.

How do you fix: "AttributeError: 'Boundary' object has no attribute 'rect'"?

I am making a platformer game where there is a boundary in the beginning of the level, so the player can't just keep going to the left for no reason. I decided to make a class called boundary and add it into a list where the rules are you can't pass it. However, I keep getting this error:
"AttributeError: 'Boundary' object has no attribute 'rect'". Can anybody fix this? Also, a better way to do this would also be accepted. Thanks!
class Boundary(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.boundary = pygame.Surface([15,600])
self.boundary.fill(WHITE)
self.boundary.set_colorkey(WHITE)
self.boundary_rect =
self.boundary.get_rect()
self.boundary_rect.x = -50
self.boundary_rect.y = 0
class Level01(Level):
def __init__(self, player):
Level.__init__(self, player)
level_boundary = [Boundary()]
for _ in level_boundary:
boundary = Boundary()
boundary.player = self.player
self.platform_boundary_list.add
(boundary)
class Player(pygame.sprite.Sprite):
def__init__(self):
super().init()
self.rect.x += self.change_x
block_hit_list = pygame.sprite.spritecollide(self,
self.level.platform_boundary_list, False)
for block in block_hit_list:
if self.change_x > 0:
self.rect.right = block.rect.left
elif self.change_x < 0:
self.rect.left = block.rect.right
self.rect.y += self.change_y
block_hit_list = pygame.sprite.spritecollide(self,
self.level.platform_boundary_list, False)
for block in block_hit_list:
if self.change_y > 0:
self.rect.bottom = block.rect.top
elif self.change_y < 0:
self.rect.top = block.rect.bottom
self.change_y = 0
Haven't ran the code, but the error message seems reasonable. Your Boundary class has a property, boundary_rect rather than rect (which doesn't appear to be directly exposed by pygame's Sprite class). Replacing block.rect with block.boundary_rect should correct this.
Update:
Looking through your code, I saw a few issues, with both the Player and the Boundary classes referring to rect properties that did not directly belong their parent, pygame.sprite.Sprite. Based on your comments, I decided to rewrite the code into a demo collision test to not only fix the errors but also provide some ideas for how you could consider organizing your code.
The demo is pretty simple; a player and a bunch of random blocks are drawn to the screen. The player block bounces around the edges of the screen, and the colliding blocks are redrawn in a different color. The results look like this:
Here is the code for the above demo. I added a bunch of comments to clarify what the code does. If anything is unclear, let me know:
import random
import pygame
from pygame.rect import Rect
from pygame.sprite import Sprite
from pygame.surface import Surface
class Block(Sprite):
def __init__(self, rect):
super().__init__()
self.idle_color = (255, 255, 255, 255)#white - if not colliding
self.hit_color = (0, 255, 0, 255)#green - if colliding
self.image = Surface((rect.w, rect.h))
self.color = self.idle_color#default
#Do NOT set color here, decided by collision status!
self.rect = rect
class Player(Sprite):
def __init__(self, rect):
super().__init__()
self.color = (255, 0, 0, 255)#red
self.image = Surface((rect.w, rect.h))
self.image.fill(self.color)
self.rect = rect
class Level(object):
def __init__(self, screen, player, blocks):
self.color = (20, 20, 20, 255)#gray background
self.screen = screen
self.player = player
self.blocks = blocks
#hard-coded player x and y speed for bounding around
self.player_speed_x = 1
self.player_speed_y = 1
#Bounces player off the screen edges
#Simply dummy method - no collisions here!
def move_player(self):
p_rect = self.player.rect
s_rect = self.screen.get_rect()
if p_rect.right >= s_rect.right or p_rect.left <= s_rect.left:
self.player_speed_x *= -1
if p_rect.top <= s_rect.top or p_rect.bottom >= s_rect.bottom:
self.player_speed_y *= -1
p_rect.move_ip(self.player_speed_x, self.player_speed_y)#modifies IN PLACE!
def handle_collisions(self):
#First set all blocks to default color
for block in self.blocks:
block.color = block.idle_color
hit_blocks = pygame.sprite.spritecollide(self.player, self.blocks, False)
for block in hit_blocks:
block.color = block.hit_color
#Clear screen with background color, then draw blocks, then draw player on top!
def draw(self):
self.screen.fill(self.color)
for block in self.blocks:
#update fill to color decided by handle_collisions function...
block.image.fill(block.color)
self.screen.blit(block.image, block.rect)
self.screen.blit(self.player.image, self.player.rect)
def update(self):
self.move_player()
self.handle_collisions()
self.draw()
if __name__ == "__main__":
pygame.init()
width = 400
height = 300
fps = 60
title = "Collision Test"
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption(title)
clock = pygame.time.Clock()
running = True
#Create a player
player_size = 20
player_x = random.randint(0, width - player_size)
player_y = random.randint(0, height - player_size)
player_rect = Rect(player_x, player_y, player_size, player_size)
player = Player(player_rect)
#Create some random blocks
blocks = []
num_blocks = 50
for i in range(num_blocks):
block_size = 20
block_x = random.randint(0, width - block_size)
block_y = random.randint(0, height - block_size)
block_rect = Rect(block_x, block_y, block_size, block_size)
block = Block(block_rect)
blocks.append(block)
#Create the level
level = Level(screen, player, blocks)
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
level.update()
pygame.display.update()
clock.tick(fps)

Change colour of sprite rect from group

I have a group of rects, they display in a row. I want them to change their colour when they have been clicked, until they are clicked again
I have this code so far to create the sprites:
class DrawableRect(pygame.sprite.Sprite):
def __init__(self,color,width,height,value=0):
super().__init__()
self.image = pygame.Surface([width, height])
self.image.fill(color)
self.rect = self.image.get_rect()
self.value = value
self.x = 0
self.y = 0
def change_value(self,color,value):
self.image.fill(color)
self.value=value
def DrawRects(start_x, start_y, rect_spacing, colour_list):
current_x_pos = start_x
for rect_num in range(0,8):
rect = DrawableRect(colour_list[rect_num], boxW, boxH)
rect.rect.x = current_x_pos
rect.rect.y = start_y
current_x_pos = current_x_pos + rect.rect.width + rect_spacing
rects.add(rect)
rects.draw(screen)
The idea of the app is for each rectangle to represent a bit, and when pressed it alternates between 0 and 1, the makeup of each bit displays the decimal equivalent somewhere.
I read that groups are unordered therefore indexing wouldn't work, is that true?
Here's an example I've modified to suit your purposes. I have a bunch of sprites (coloured rectangles) in a sprite group and I change* the colour of any sprite that collides with the mouse pointer when a mouse button is pressed.
Here's the code, you're probably most interested in the change_color() method and the MOUSEBUTTONUP event handling code.
import random
import pygame
screen_width, screen_height = 640, 480
def get_random_position():
"""return a random (x,y) position in the screen"""
return (random.randint(0, screen_width - 1), #randint includes both endpoints.
random.randint(0, screen_height - 1))
color_list = ["red", "orange", "yellow", "green", "cyan", "blue", "blueviolet"]
colors = [pygame.color.Color(c) for c in color_list]
class PowerUp(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
width, height = 64, 32
self.image = pygame.Surface([width, height])
self.clicked = False # track whether we've been clicked or not
# initialise color
self.color = random.choice(colors)
self.image.fill(self.color)
# Fetch the rectangle object that has the dimensions of the image
self.rect = self.image.get_rect()
# then move to a random position
self.update()
def update(self):
#move to a random position
self.rect.center = get_random_position()
def random_color(self):
# randomise color
self.clicked = not self.clicked
if self.clicked:
color = random.choice(colors)
else:
color = self.color
self.image.fill(color)
if __name__ == "__main__":
pygame.init()
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption('Sprite Color Switch Demo')
clock = pygame.time.Clock() #for limiting FPS
FPS = 60
exit_demo = False
pygame.key.set_repeat(300, 200)
#create a sprite group to track the power ups.
power_ups = pygame.sprite.Group()
for _ in range(10):
power_ups.add(PowerUp()) # create a new power up and add it to the group.
# main loop
while not exit_demo:
for event in pygame.event.get():
if event.type == pygame.QUIT:
exit_demo = True
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
exit_demo = True
elif event.key == pygame.K_SPACE:
power_ups.update()
elif event.type == pygame.MOUSEBUTTONUP:
# check for collision
for p in power_ups:
if p.rect.collidepoint(event.pos): # maybe use event?
p.random_color()
screen.fill(pygame.Color("black")) # use black background
power_ups.draw(screen)
pygame.display.update()
clock.tick(FPS)
pygame.quit()
quit()
Let me know if you have any questions. Obviously this doesn't do row alignment of the sprites, I think you have a handle on that. I would suggest that you have all of your screen drawing operations in one place so your code can be clearer.
*The new colour is randomised from a short list, so there's a 14% chance it won't change from the starting colour.

wrong color channels, pygame cairo rsvg drawing

I have cairo+rsvg rendering a .svg file in pygame. But the color channels are wrong.
testing with lion.svg
But image is:
I believe I have my RGBA channel order swapped, (He's pink, not yellow). but am not clear on how it works. Here's my code, (which otherwise is rendering right.)
Maybe pygame.display.set_mode(...) or pygame.image.frombuffer(...) is the relevant problem?
import pygame
from pygame.locals import *
import os
import cairo
import rsvg
import array
WIDTH, HEIGHT = 60,60
class Lion(object):
"""load+draw lion.svg"""
def __init__(self, file=None):
"""create surface"""
# Sprite.__init__(self)
self.screen = pygame.display.get_surface()
self.image = None
self.filename = 'lion.svg'
self.width, self.height = WIDTH, HEIGHT
def draw_svg(self):
"""draw .svg to pygame Surface"""
svg = rsvg.Handle(file= os.path.join('data', self.filename))
dim = svg.get_dimension_data()
self.width , self.height = dim[0], dim[1]
data = array.array('c', chr(0) * self.width * self.height * 4 )
cairo_surf= cairo.ImageSurface.create_for_data( data,
cairo.FORMAT_ARGB32, self.width, self.height, self.width * 4 )
ctx = cairo.Context(cairo_surf)
svg.render_cairo(ctx)
self.image = pygame.image.frombuffer(data.tostring(), (self.width,self.height), "ARGB")
def draw(self):
"""draw to screen"""
if self.image is None: self.draw_svg()
self.screen.blit(self.image, Rect(200,200,0,0))
class GameMain(object):
"""game Main entry point. handles intialization of game and graphics, as well as game loop"""
done = False
color_bg = Color('black') # or also: Color(50,50,50) , or: Color('#fefefe')
def __init__(self, width=800, height=600):
pygame.init()
self.width, self.height = width, height
self.screen = pygame.display.set_mode(( self.width, self.height ))
# self.screen = pygame.display.set_mode(( self.width, self.height ),0,32) # 32bpp for format 0x00rrggbb
# fps clock, limits max fps
self.clock = pygame.time.Clock()
self.limit_fps = True
self.fps_max = 40
self.lion = Lion()
def main_loop(self):
while not self.done:
# get input
self.handle_events()
self.draw()
# cap FPS if: limit_fps == True
if self.limit_fps: self.clock.tick( self.fps_max )
else: self.clock.tick()
def draw(self):
"""draw screen"""
self.screen.fill( self.color_bg )
self.lion.draw()
pygame.display.flip()
def handle_events(self):
"""handle events: keyboard, mouse, etc."""
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT: self.done = True
# event: keydown
elif event.type == KEYDOWN:
if event.key == K_ESCAPE: self.done = True
if __name__ == "__main__":
print """Keys:
ESC = quit
"""
game = GameMain()
game.main_loop()
Indeed - the byte order for each channel is different from cairo to pygame.
You can either juggle with the array before converting it to a string, to post the data in the correct order for pygame (you'd have to swap the green and blue data for each pixel) - or use
an aditional dependency: Python's PIL, to properly handle the different data at native speeds.
There is an snippet for that in pygame's site:
def bgra_surf_to_rgba_string(cairo_surface):
# We use PIL to do this
img = Image.frombuffer(
'RGBA', (cairo_surface.get_width(),
cairo_surface.get_height()),
cairo_surface.get_data(), 'raw', 'BGRA', 0, 1)
return img.tostring('raw', 'RGBA', 0, 1)
...
# On little-endian machines (and perhaps big-endian, who knows?),
# Cairo's ARGB format becomes a BGRA format. PyGame does not accept
# BGRA, but it does accept RGBA, which is why we have to convert the
# surface data. You can check what endian-type you have by printing
# out sys.byteorder
data_string = bgra_surf_to_rgba_string(cairo_surface)
# Create PyGame surface
pygame_surface = pygame.image.frombuffer(
data_string, (width, height), 'RGBA')
Check the whole recipe at: http://www.pygame.org/wiki/CairoPygame
If you don't want to add an aditional dependence (PIL in this case), Python's native arrays allows slice assignment - with those it is possible to swap the data of your blue and green channels at native speeds - try this swapping (may need some tunning :-) this is what worked for me after loading your image above as a png file, not from a cairo context )
def draw_svg(self):
"""draw .svg to pygame Surface"""
svg = rsvg.Handle(file= os.path.join('data', self.filename))
dim = svg.get_dimension_data()
self.width , self.height = dim[0], dim[1]
data = array.array('c', chr(0) * self.width * self.height * 4 )
cairo_surf= cairo.ImageSurface.create_for_data( data,
cairo.FORMAT_ARGB32, self.width, self.height, self.width * 4 )
ctx = cairo.Context(cairo_surf)
blue = data[1::4]
green = data[3::4]
data[1::4] = green
data[3::4] = blue
svg.render_cairo(ctx)
self.image = pygame.image.frombuffer(data.tostring(), (self.width,self.height), "ARGB")
If the array slicing is not working, may I offer the PIL approach?
It was inspired by the same Pygame recipe jsbueno mentioned, but I cleaned it up and organized to a self-contained function. It takes a filename as argument, and return a pygame.Surface just like pygame.image.load() would.
import pygame # python-pygame
import rsvg # python-rsvg
import cairo # python-cairo
import PIL.Image # python-imaging
def load_svg(filename):
''' Load an SVG file and return a pygame.Surface '''
def bgra_rgba(surface):
''' Convert a Cairo surface in BGRA format to a RBGA string '''
img = PIL.Image.frombuffer(
'RGBA', (surface.get_width(), surface.get_height()),
surface.get_data(), 'raw', 'BGRA', 0, 1)
return img.tostring('raw', 'RGBA', 0, 1)
svg = rsvg.Handle(filename)
width, height = svg.props.width, svg.props.height
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
svg.render_cairo(cairo.Context(surface))
return pygame.image.frombuffer(bgra_rgba(surface), (width,height), "RGBA")
If you do not want to use PIL, the correct array slicing is:
def draw_svg(self):
"""draw .svg to pygame Surface"""
svg = rsvg.Handle(file= os.path.join('data', self.filename))
dim = svg.get_dimension_data()
self.width , self.height = dim[0], dim[1]
data = array.array('c', chr(0) * self.width * self.height * 4 )
cairo_surf= cairo.ImageSurface.create_for_data( data,
cairo.FORMAT_ARGB32, self.width, self.height, self.width * 4 )
ctx = cairo.Context(cairo_surf)
svg.render_cairo(ctx)
blue = data[0::4]
red = data[2::4]
data[0::4] = red
data[2::4] = blue
self.image = pygame.image.frombuffer(data.tostring(), (self.width,self.height), "ARGB")