Rendering Text with Multiple Lines in Pygame

Rendering text with multiple lines in pygame

As I said in the comments; you have to render each word separately and calculate if the width of the text extends the width of the surface (or screen). Here's an example:

import pygame
pygame.init()

SIZE = WIDTH, HEIGHT = (1024, 720)
FPS = 30
screen = pygame.display.set_mode(SIZE, pygame.RESIZABLE)
clock = pygame.time.Clock()

def blit_text(surface, text, pos, font, color=pygame.Color('black')):
words = [word.split(' ') for word in text.splitlines()] # 2D array where each row is a list of words.
space = font.size(' ')[0] # The width of a space.
max_width, max_height = surface.get_size()
x, y = pos
for line in words:
for word in line:
word_surface = font.render(word, 0, color)
word_width, word_height = word_surface.get_size()
if x + word_width >= max_width:
x = pos[0] # Reset the x.
y += word_height # Start on new row.
surface.blit(word_surface, (x, y))
x += word_width + space
x = pos[0] # Reset the x.
y += word_height # Start on new row.

text = "This is a really long sentence with a couple of breaks.\nSometimes it will break even if there isn't a break " \
"in the sentence, but that's because the text is too long to fit the screen.\nIt can look strange sometimes.\n" \
"This function doesn't check if the text is too high to fit on the height of the surface though, so sometimes " \
"text will disappear underneath the surface"
font = pygame.font.SysFont('Arial', 64)

while True:

dt = clock.tick(FPS) / 1000

for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()

screen.fill(pygame.Color('white'))
blit_text(screen, text, (20, 20), font)
pygame.display.update()

Result

Sample Image

Can't find a way to make multiple lines in pygame

It is not Surface.blit which doesn't support multiple lines. blit simply draw a Surface on another Surface, it doesn't care what the Surface contains.

It's pygame.Font.render which doesn't support multilines. The docs clearly say:

The text can only be a single line: newline characters are not rendered.

So I don't know what DisplayRoom.prompt is in your code, but if is not a string, is bound to fail: render raises a TypeError: text must be a unicode or bytes.

And if is a string with newlines, newlines are just not rendered.

You have to split the text and render each line separately.

In the following example I create a simple function blitlines which illustrates how you could do.

import sys
import pygame

def blitlines(surf, text, renderer, color, x, y):
h = renderer.get_height()
lines = text.split('\n')
for i, ll in enumerate(lines):
txt_surface = renderer.render(ll, True, color)
surf.blit(txt_surface, (x, y+(i*h)))

background_colour = (0, 0, 0)
textcolor = (255, 255, 255)

multitext = "Hello World!\nGoodbye World!\nI'm back World!"

pygame.init()
screen = pygame.display.set_mode((500, 500))
userfont = pygame.font.Font(None, 40)

screen.fill(background_colour)
blitlines(screen, multitext, userfont, textcolor, 100, 100)

pygame.display.flip()

#main loop
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()

This is the result on the screen.

sample image

Pygame's Message-multiple lines?

The livewires games.Message class cannot do this.

Going through the source code for games.Message shows that it inherits from games.Text, which has a method _create_surface(). Finally, this calls pygame.font.Font.render() to draw the text to a surface. If you look at the documentation for render(), it states

"The text can only be a single line:
newline characters are not rendered."

As for how you'd do what you want, I'd suggest making several Message objects, one for each line. Then display them on the screen one after the other.

Pygame - rendering multiline text using Sprites

You could render the strings with FONT.render first, then create a transparent surface and blit the separate text surfaces onto it. To figure out the width of the transparent base image, you can use the .get_width() method of the separate text surfaces and for the height you can use FONT.get_height().

import pygame as pg

pg.init()
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
FONT = pg.font.Font(None, 40)
BLUE = pg.Color('dodgerblue1')

class Score(pg.sprite.Sprite):

def __init__(self, pos):
super(Score, self).__init__()
self.lives = 3
self.score = 0
self.rect = pg.Rect(pos, (1, 1))
self.update_image()

def update_image(self):
height = FONT.get_height()
# Put the rendered text surfaces into this list.
text_surfaces = []
for txt in ('lives {}'.format(self.lives),
'score {}'.format(self.score)):
text_surfaces.append(FONT.render(txt, True, BLUE))
# The width of the widest surface.
width = max(txt_surf.get_width() for txt_surf in text_surfaces)

# A transparent surface onto which we blit the text surfaces.
self.image = pg.Surface((width, height*2), pg.SRCALPHA)
for y, txt_surf in enumerate(text_surfaces):
self.image.blit(txt_surf, (0, y*height))
# Get a new rect (if you maybe want to make the text clickable).
self.rect = self.image.get_rect(topleft=self.rect.topleft)

def main():
score = Score((100, 100))
all_sprites = pg.sprite.Group(score)

done = False

while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
elif event.type == pg.KEYDOWN:
if event.key == pg.K_s:
# Increment the score and update the image.
# It would be nicer to turn the score into a
# property and update the image automatically.
score.score += 1
score.update_image()

all_sprites.update()
screen.fill((30, 30, 30))
all_sprites.draw(screen)

pg.display.flip()
clock.tick(30)

if __name__ == '__main__':
main()
pg.quit()

Cant seem to render multiple lines in pygame on a sprite

To blit several lines onto a surface you need to figure out the size of the text first.

In the example below, I pass a list of strings to the Text class.

Then I find the width of the longest line max(font.size(line)[0] for line in self.text) and the line height font.get_linesize() and create a surface with this size (pass pg.SRCALPHA to make it transparent).

Now you can use a for loop to render and blit the lines in the self.text list onto the surface. Enumerate the list of lines to get the y position and mutliply it by the line height.

import pygame as pg

pg.init()

MYFONT = pg.font.SysFont('lucidaconsole', 20, bold=True)

s = """Lorem ipsum dolor sit amet, consectetur adipiscing
elit, sed do eiusmod tempor incididunt ut labore et
dolore magna aliqua. Ut enim ad minim veniam, quis
nostrud exercitation ullamco laboris nisi ut aliquip
ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore
eu fugiat nulla pariatur. Excepteur sint occaecat
cupidatat non proident, sunt in culpa qui officia
deserunt mollit anim id est laborum."""

class Text(pg.sprite.Sprite):

def __init__(self, pos, text, font):
super().__init__()
self.text = text
width = max(font.size(line)[0] for line in self.text)
height = font.get_linesize()
self.image = pg.Surface((width+3, height*len(self.text)), pg.SRCALPHA)
# self.image.fill(pg.Color('gray30')) # Fill to see the image size.
# Render and blit line after line.
for y, line in enumerate(self.text):
text_surf = font.render(line, True, (50, 150, 250))
self.image.blit(text_surf, (0, y*height))
self.rect = self.image.get_rect(topleft=pos)

class Game:

def __init__(self):
self.done = False
self.clock = pg.time.Clock()
self.screen = pg.display.set_mode((1024, 768))
text = Text((20, 10), s.splitlines(), MYFONT)
self.all_sprites = pg.sprite.Group(text)

def run(self):
while not self.done:
self.clock.tick(30)
self.handle_events()
self.draw()

def handle_events(self):
for event in pg.event.get():
if event.type == pg.QUIT:
self.done = True

def draw(self):
self.screen.fill((50, 50, 50))
self.all_sprites.draw(self.screen)
pg.display.flip()

if __name__ == '__main__':
Game().run()
pg.quit()

Pygame: Writing Multiple Lines

@Maxime Lorant answer is what you want. Suppose you have a list with your names.

names = ["Name1", "Name2", "Name3"]
f = open("names.txt", "a")
for i in names:
f.write(i + "\n")
f.close()

If names.txt was a blank file, now the content of it should like:

Name1
Name2
Name3

EDIT




Now I see what you are trying to achieve. Please see this link :
http://sivasantosh.wordpress.com/2012/07/18/displaying-text-in-pygame/

Basically, the newline character won't work in pygame - you have to change the coordinates of your text rectangle. I am kinda new to pygame but I managed to do what you want and here's my naive approach (I edited code from the link above):

#...
f = open("t.txt")
lines = f.readlines()
f.close()

basicfont = pygame.font.SysFont(None, 48)
text = basicfont.render('Hello World!', True, (255, 0, 0), (255, 255, 255))
textrect = text.get_rect()
textrect.centerx = screen.get_rect().centerx
textrect.centery = screen.get_rect().centery

screen.fill((255, 255, 255))
for i in lines:
# each i has a newline character, so by i[:-1] we will get rid of it
text = basicfont.render(i[:-1], True, (255, 0, 0), (255, 255, 255))
# by changing the y coordinate each i from lines will appear just
# below the previous i
textrect.centery += 50
screen.blit(text, textrect)
#...

Here's the result:Sample Image



Related Topics



Leave a reply



Submit