Related
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!
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!
First of all ,I loaded a picture of the ship and initialized its location. thereafter I add bullet to my program. After that, I found that no matter how I debug it, it can't be in the right place.
# 1. - import library
import pygame,sys
from pygame.locals import *
from pygame.sprite import Sprite
class Player(Sprite):
def __init__(self):
super().__init__()
self.image = pygame.image.load('image/pig.bmp')
self.rect = self.image.get_rect()
self.screen_rect = screen.get_rect()
class Bullet(Sprite):
def __init__(self, player):
super().__init__()
self.rect = pygame.Rect(0, 0, bullet_width, bullet_height )
self.color = bullet_color
self.rect.center = player.rect.center
self.rect.left = player.rect.right
# 2. - Initialize the game
pygame.init()
width,height = 800,600
screen = pygame.display.set_mode((width,height))
keys = [False,False,False,False]
playerpos = [0,288]
bullet_width = 15
bullet_height = 6
bullet_color = (200, 200 , 0)
player = Player()
bullet = Bullet(player)
grass = pygame.image.load("image/bg.bmp")
# 4. - keep looping through
while True:
# 5. - clear the screen before drawing it again.
screen.fill(0)
# 6. - Draw the screen elements.
screen.blit(grass,(0,0))
screen.blit(player.image, playerpos)
pygame.draw.rect(screen, bullet.color, bullet.rect)
# 7. - update the screen
pygame.display.flip()
# 8. - loop through the events
for event in pygame.event.get():
# check if the event is the X button.
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
and why bullet appear in top-left
enter image description here
I hope bullet appear in ship's right side,but I can't do it if I don't use coordinate(x,y),how can I do it?
You are drawing the ship in a position unrelated to its rect's position, using playerpos. You need to make the link the ship's position linked to its rect, so that the bullet can access it:
# 1. - import library
import pygame,sys
from pygame.locals import *
from pygame.sprite import Sprite
class Player(Sprite):
def __init__(self):
super().__init__()
self.image = pygame.image.load('image/pig.bmp')
self.image.fill((255, 0, 0))
self.rect = self.image.get_rect()
self.screen_rect = screen.get_rect()
class Bullet(Sprite):
def __init__(self, player):
super().__init__()
self.rect = pygame.Rect(0, 0, bullet_width, bullet_height )
self.color = bullet_color
self.rect.center = player.rect.center
self.rect.left = player.rect.right
# 2. - Initialize the game
pygame.init()
width,height = 800,600
screen = pygame.display.set_mode((width,height))
keys = [False,False,False,False]
bullet_width = 15
bullet_height = 6
bullet_color = (200, 200 , 0)
player = Player()
player.rect.topleft = [0,288]
bullet = Bullet(player)
grass = pygame.image.load("image/bg.bmp")
# 4. - keep looping through
while True:
# 5. - clear the screen before drawing it again.
screen.blit(grass, (0, 0))
# 6. - Draw the screen elements.
screen.blit(player.image, player.rect.topleft)
pygame.draw.rect(screen, bullet.color, bullet.rect)
# 7. - update the screen
pygame.display.flip()
# 8. - loop through the events
for event in pygame.event.get():
# check if the event is the X button.
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
This is because a surface's get_rect() method has no idea where the surface is going to be blitted on to another surface, so it just gives its position as (0, 0). get_rect() is only useful for obtaining a surface's dimensions.
I have been struggling for few days know trying to figure out how to make for example a image move in circular path. I have looked other posts here but i just can't get it.
So how do i move image in circular path. My code only moves my image 45 degrees and stops then. I would need it to go full circle and continue doing it.
Current Code:
import pygame
pygame.init()
screen = pygame.display.set_mode((400, 400))
CENTER = (200, 200)
RADIUS = 100
x = 0
y = 0
satelliteCenter = (CENTER[0]+RADIUS, CENTER[1])
run = 1
while run == 1:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = 0
pygame.quit()
mouse = pygame.mouse.get_pos()
vector = x-CENTER[0], y-CENTER[1]
x +=1
distance = (vector[0]**2 + vector[1]**2)**0.5
if distance > 0:
scalar = RADIUS / distance
satelliteCenter = (
int(round( CENTER[0] + vector[0]*scalar )),
int(round( CENTER[1] + vector[1]*scalar )) )
screen.fill((255,255,255))
pygame.draw.circle(screen, (71,153,192), CENTER, RADIUS)
pygame.draw.circle(screen, (243,79,79), satelliteCenter, 16)
pygame.display.update()
You can just use a pygame.math.Vector2 and rotate it each frame, scale it by the radius and add it to the CENTER position to get the current center of the small circle.
import sys
import pygame
pygame.init()
screen = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()
CENTER = (200, 200)
RADIUS = 100
# A unit vector pointing to the right.
direction = pygame.math.Vector2(1, 0)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
pygame.quit()
sys.exit()
direction.rotate_ip(4) # By 4 degrees.
# Normalize it, so that the length doesn't change because
# of floating point inaccuracies.
direction.normalize_ip()
# Scale direction vector, add it to CENTER and convert to ints.
ball_pos = [int(i) for i in CENTER+direction*RADIUS]
screen.fill((255,255,255))
pygame.draw.circle(screen, (71,153,192), CENTER, RADIUS)
pygame.draw.circle(screen, (243,79,79), ball_pos, 16)
pygame.display.update()
clock.tick(30)
Edit: If you want the red ball to follow the mouse, then your example actually works if you set x and y to the mouse pos x, y = pygame.mouse.get_pos().
import sys
import pygame
pygame.init()
screen = pygame.display.set_mode((400, 400))
CENTER = (200, 200)
RADIUS = 100
x = 0
y = 0
satelliteCenter = (CENTER[0]+RADIUS, CENTER[1])
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
pygame.quit()
sys.exit()
x, y = pygame.mouse.get_pos()
vector = x-CENTER[0], y-CENTER[1]
distance = (vector[0]**2 + vector[1]**2)**0.5
if distance > 0:
scalar = RADIUS / distance
satelliteCenter = (
int(round( CENTER[0] + vector[0]*scalar )),
int(round( CENTER[1] + vector[1]*scalar ))
)
screen.fill((255,255,255))
pygame.draw.circle(screen, (71,153,192), CENTER, RADIUS)
pygame.draw.circle(screen, (243,79,79), satelliteCenter, 16)
pygame.display.update()
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")