Multi-line dialog box in pygame ?

Started by
8 comments, last by Furkan125 1 year, 10 months ago

Hi, I'm trying to make a dialog box with pygame, and initially everything was going well. I printed the letters sequentially on the screen and it works very well in a single line of text, but when I use \n to add an extra line it does not go to the new line. When I searched, I found that pygame.font.Font and pygame.font.SysFont could not print \n. So what do you think, how should I go about printing multi-line messages?

Advertisement

Since you cannot draw \n, the system cannot draw multi-line things. Instead, draw multiple single-line messages.

In other words, how would you draw several lines of text if they are separate pieces of text, eg

lines = ["Hello", "how are you?"]
for line in lines:
    # draw letters of 'line'

Next, getting from a \n multi-line to the above form is a simple

multi_line = "Hello\nhow are you?"
lines = multi_line.split('\n')

When you cannot do one thing, be creative and start thinking a way around it. How can you make it look like a multi-line message even though it isn't?

Games do this a lot, any trick you can think of is fine, as long as you can fool the user into believing it's “real” ?

Thanks. Now I was able to print text on multiple lines, but when I try to print letters of line char by char it doesn't work. Where do you think I am doing wrong?

pygame.display.get_surface().blit(self.surf, self.rect)
pygame.draw.rect(self.surf, "white", pygame.Rect(0, 0, 1000, 250), 4)

y = 25
lines = self.text.split("\n")
    for line in lines:
         for i in range(len(line)):
            pygame.time.delay(35)
            font_surf = self.font.render(line[0:i+1], False, "white")
            font_rect = font_surf.get_rect(topleft = (25, y))
            self.surf.blit(font_surf, font_rect)
            pygame.display.update()
        y += 50

“it does not work” is not a very useful indication. In particular, it doesn't say how it is not working.

The only possible answer here would something like “your code is wrong then”. Correct, but useless advice since it doesn't say how to to fix it.

So please tell **how** it is “not working”. Is the code rejected by the Python interpreter (vertical code alignment look off), is the code not running, do lines get painted on top of each other, do lines move, do you get purple blobs while running the code?

Ie

- What do you want to achieve?

- What is the code doing?

- How is that not what you want, at what point does it make a wrong turn?

- Did you try finding the cause of the problem? If yes, what was the result?

The only thing I can see now is that y is never reset to its initial value, could that be the cause?

EDIT: Instead of jumping directly to loop for many lines, you may want to first try to work out an intermediate form. Try to paint 2 lines of text below each other first (thus “code to paint the first line” and “code to paint the second line” below each other). This solution has the advantage that you can tune what to do with each line independently. That will of course duplicate the paint-code at first, but it may give insights in what you need to do get the results what you want, in particular how to treat line 1 different from line 2.

Once that gives the desired effect, work out how to move the code around so you do the same but with the for-loop (where the duplicate paint code disappears, but any difference in treatment between the lines must be folded around the for loop).

I want to print the text letter by letter in the dialog box, but instead it prints the black part of the dialog first and after a few seconds the white frame and the entire text. By the way the code is not rejected by the python interpreter. It must have caused a misunderstanding as I didn't throw out the whole code. I just threw out the printing part. Here is the whole code:

main.py

import pygame
import sys
from dialogbox import DialogBox

class Game:
    def __init__(self):
        pygame.init()
        self.screen = pygame.display.set_mode((1920, 1080), pygame.FULLSCREEN|pygame.NOFRAME|pygame.SCALED)
        pygame.display.set_caption("DialogueBox")
        self.clock = pygame.time.Clock()

        x = (pygame.display.get_surface().get_width() // 2) - 500 # 500 = w (1000) // 2
        y = pygame.display.get_surface().get_height() - 250

        self.dialog = "* Hello World\n* Test"

        self.dialogbox = DialogBox(x, y, 1000, 250, self.dialog)

    def run(self):
        while True:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                    sys.exit(0)

            self.screen.fill("purple")
            self.dialogbox.render()
            pygame.display.update()
            self.clock.tick(60)

if __name__ == "__main__":
    game = Game()
    game.run()

dialogbox.py

import pygame

class DialogBox:
    def __init__(self, x, y, w, h, text):
        self.x = x
        self.y = y
        self.w = w
        self.h = h

        self.surf = pygame.Surface((w, h))
        self.surf.fill("black")
        self.rect = self.surf.get_rect(topleft = (x, y))

        self.text = text
        self.index = 0

        self.font = pygame.font.Font(None, 50)


    def render(self):
        ### Dialog box
        pygame.display.get_surface().blit(self.surf, self.rect)
        pygame.draw.rect(self.surf, "white", pygame.Rect(0, 0, 1000, 250), 4)
        ###

        ### Text
        y = 25
        lines = self.text.split("\n")
        for line in lines:
            for i in range(len(line)):
                pygame.time.delay(35)
                font_surf = self.font.render(line[0:i+1], False, "white")
                font_rect = font_surf.get_rect(topleft = (25, y))
                self.surf.blit(font_surf, font_rect)
                pygame.display.update()
            y += 50
        ###

Alberth said:
EDIT: Instead of jumping directly to loop for many lines, you may want to first try to work out an intermediate form. Try to paint 2 lines of text below each other first (thus “code to paint the first line” and “code to paint the second line” below each other). This solution has the advantage that you can tune what to do with each line independently. That will of course duplicate the paint-code at first, but it may give insights in what you need to do get the results what you want, in particular how to treat line 1 different from line 2. Once that gives the desired effect, work out how to move the code around so you do the same but with the for-loop (where the duplicate paint code disappears, but any difference in treatment between the lines must be folded around the for loop).

I'll try to put your suggestions into code. Thank you.

Furkan125 said:
I want to print the text letter by letter in the dialog box, but instead it prints the black part of the dialog first and after a few seconds the white frame and the entire text.

Ok, didn't try to run the code (it's too late in the evening here currently), but this text got me thinking about the timing of things. The documentation doesn't say how ‘update’ works exactly, but assuming you don't draw a large amount of text, the python code is likely very fast in running through the entire iteration. Depending on how update works, the system may simply never reach the point to update the screen, as you're throwing a large number of updates to the display probably faster than the screen can keep up with.

To examine this, what happens if you add a delay in the loop?

A second thing is that you don't do event processing while painting letters. Don't know if that is essential, but you could move the “for event ..” loop into a function and call that function in its original spot and within the letter printing loop.

A third thing is maybe, why didn't you have this problem in your one-line sentence program. In other words, what has changed in the program that may affect painting or timing between the first working (I assume) version and this one?

Furkan125 said:
It must have caused a misunderstanding as I didn't throw out the whole code. I just threw out the printing part.

It's a balance, short code is nice to explain an idea, actual working code is a must if you want other people to try it.

I guessed your previous code was correct, it just went wrong in formatting to the forum. However, I haven't coded in pygame for some years, so I don't remember all the details. It would cost me several hours to get your code fragment running, and even then I may have made different choices in the parts you didn't show, such that my version would have different behavior than yours.

Together without any description of what was wrong, it becomes a shot in absolute darkness what happens and why.

Alberth said:
To examine this, what happens if you add a delay in the loop?

I used pygame.time.delay for the delay in the loop but on your suggestion I increased the delay time and the result did not change.

Alberth said:
Don't know if that is essential, but you could move the “for event ..” loop into a function and call that function in its original spot and within the letter printing loop.

Unfortunately that didn't work either.

Alberth said:
A third thing is maybe, why didn't you have this problem in your one-line sentence program. In other words, what has changed in the program that may affect painting or timing between the first working (I assume) version and this one?

The first version where I printed a single line of text was working as I wanted. I think the reason why I didn't have the same error in the first version is because of the loops. In the first version, I didn't need to use a loop while writing code for a single line, but I couldn't think of any other idea other than a loop for a multi-line dialog box, but I guess I can't see the results because of the for loops I used for the multi-line dialog break the while True loop in main.py.

Not sure why, but your code didn't run for me at all. Likely you have a newer pygame version than me.

To avoid confusion, I left out the surface calls in my code below as they didn't work for me at all. It's likely better that you add them, than me guessing what it might be without having a way to check that they are working. Sorry about that.

Furkan125 said:
I couldn't think of any other idea other than a loop for a multi-line dialog box, but I guess I can't see the results because of the for loops I used for the multi-line dialog break the while True loop in main.py.

I was suspecting that too, that's why I suggested to perform event processing in a function and call it from 2 spots.

What you can do is handle partial text printing inside the dialog box. See below

class DialogBox:
    def __init__(self, x, y, w, h, text):
    	# .... Initialize data

        self.lines = []
        self.char_count = 0 # Number of characters in self.lines.
        self.paint_count = 0 # Number of characters to paint in self.render
        self.set_text(text)

    def set_text(self, text):
        self.lines = text.split('\n')
        self.char_count = sum(len(line) for line in self.lines)
        self.paint_count = 0 # Start adding letters from the start.

    def render(self):
        # .... Get surface to paint.
	
        # Draw one more character if possible.
        self.paint_count = min(self.paint_count + 1, self.char_count)

        # Draw the text.
        y = 25
        chars_to_draw = self.paint_count
        for line in self.lines:
            if len(line) < chars_to_draw:
                line = line[:chars_to_draw]

            # .... paint 'line' at (25, y)
            y += 50

            chars_to_draw = chars_to_draw - len(line)
            if chars_to_draw <= 0:
                break
                
        # .... Display painted text

There is a variable ‘paint_count’ that tracks how many characters to paint. It is incremented each time you call ‘self.render’ until you paint all text.

In the loop you paint text until you reach ‘paint_count’ (by tracking how many characters you paint at each line). When it runs out, you abort the loop and blit the result to the screen. So in essence this is a somewhat more complicated version of painting one line. The dialogue box class handles all the details.

Hi, i have very good news. I did it! Thanks for your help. I finally have the dialog box i wanted. It's able to print multiple lines and it can render text char by char. Again thank you.

Edit: Your code is works btw. I just add blit to your code and it works very well.

This topic is closed to new replies.

Advertisement