Cant seem to render multiple lines in pygame on a sprite

285 views Asked by At

I'm having some issues rendering multiple lines of text onto a sprite in pygame. The text is read from a txt file, and then shown on the sprite which is made by the following Text class. However, since pygame doesnt render multiple lines correctly, I made the commented part to display every word in the text the way it should be shown, not realizing that according to the way I set everything up, I need an image in this Text class. (Yes, actually need.) SO MY QUESTION IS Is there a way to get the final product my commented out part creates, into the self.image as if it were a surface like my previous textsurface?

class Text(pg.sprite.Sprite):
    def __init__(self, game, x, y, text):
        self.groups = game.all_sprites, game.text
        pg.sprite.Sprite.__init__(self, self.groups)
        self.game = game
        myfont = pg.font.SysFont('lucidaconsole', 20, bold = True)

        '''words = [word.split(' ') for word in text.splitlines()]
        space = myfont.size(' ')[0]
        max_width = 575
        max_height = 920
        pos = (0, 0)
        text_x, text_y = pos
        for line in words:
            for word in line:
                text_surface = myfont.render(word, False, (0, 255, 0))
                text_width, text_height = text_surface.get_size()
                if (text_x + text_width >= max_width):
                    text_x = pos[0]
                    text_y += text_height
                game.screen.blit(text_surface, (text_x, text_y))
                text_x += text_width + space
            text_x = pos[0]
            text_y += text_height'''

        textsurface = myfont.render(text, False, (0, 255, 0))
        self.image = textsurface
        game.screen.blit(textsurface, (0, 0))
        self.rect = game.text_img.get_rect()
        self.x = x
        self.y = y
        self._layer = 2
        self.rect.x = (x * TILESIZE) - 49
        self.rect.y = (y * TILESIZE) - 45

Maybe a little vague as of why it has to be done like this, because a large part of the game is drawn like this:

def draw(self):
    for sprite in sorted(self.all_sprites, key=lambda s: s._layer):
        self.screen.blit(sprite.image, self.camera.apply(sprite))
    pg.display.flip()

And to be able to draw it, it has to be part of the all_sprites group making it so it really does need the image, perhaps there's a way to seclude the Text class somehow while still drawing it?

To pass the text, I use

if event.type == pg.USEREVENT + 1:
            for row, tiles in enumerate(self.map.data):
                for col, tile in enumerate(tiles):
                    if tile == 'T':
                        with open('text_code.txt') as f:
                            for line in f:
                                Text(self, col, row, line)

So the text comes from a .txt file, where text like this is found:

Player has moved left!
Player has moved down!
Player has moved down!
Player has moved right!
Player has moved right!

Which was written here by any player movement:

def move(self, dx=0, dy=0):
    if (dx == -1):
        with open('text_code.txt', 'a') as f:
            f.write('Player has moved left!\n')
    if (dx == 1):
        with open('text_code.txt', 'a') as f:
            f.write('Player has moved right!\n')
    if (dy == -1):
        with open('text_code.txt', 'a') as f:
            f.write('Player has moved up!\n')
    if (dy == 1):
        with open('text_code.txt', 'a') as f:
            f.write('Player has moved down!\n')

I hope this clears things up?

1

There are 1 answers

11
skrx On BEST ANSWER

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()