python-3.xpygamepygame-surface

First Dialogue Box


Here is the piece I am trying to figure out right now, which is to draw the dialogue box, then the text on top of it. When ran, I see the displayed dialogue box, but no text.

import pygame
from pygame.locals import *
import os

pygame.init()


SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
screen = pygame.display.set_mode([SCREEN_WIDTH, SCREEN_HEIGHT])

class Text(pygame.sprite.Sprite):
    def __init__(self, text):
        # Call the parent class (Sprite) constructor
        pygame.sprite.Sprite.__init__(self)
        os.chdir(r"<my directory to the dialogue box>.png")
        self.surf = pygame.image.load("spikey_box.png").convert()  
        os.chdir(r"<my directory to my font>")
        self.font = pygame.font.Font("final_fantasy_36_font.ttf", 12)
        # set up dialogue box sprite
        self.rect = self.surf.get_rect() 
        self.rect.center = (400, 500)   
        screen.blit(self.surf, self.rect) 
        # for text
        self.textSurf = self.font.render(text, True, (255, 255, 255))
        self.textRect = self.textSurf.get_rect()
        self.textRect.center = (400, 500)
        screen.blit(self.textSurf, self.textRect)

test_message = Text("Hello, world!")
running = True
clock = pygame.time.Clock()
while running:
    for event in pygame.event.get():
        if event.type == KEYDOWN:
            if event.key == K_ESCAPE:
                running = False
        elif event.type == QUIT:
            running = False
    pressed_keys = pygame.key.get_pressed()
    screen.fill((255, 255, 255))
    screen.blit(test_message.surf, test_message.rect)
    pygame.display.flip()
    clock.tick(30)
pygame.quit()

I have some sample code that I was able to adapt a little and make work for another piece I'm working on that I tried to use for inspiration to build the class:

def text_objects(text, font):
    textSurface = font.render(text, True, (0, 0, 0))
    return textSurface, textSurface.get_rect()
def message_display(text):
    largeText = pygame.font.Font('freesansbold.ttf', 20)
    TextSurf, TextRect = text_objects(text, largeText)
    TextRect.center = ((SCREEN_WIDTH//2), (SCREEN_HEIGHT//2))
    screen.blit(TextSurf, TextRect)

The set of two functions were defined outside of the game loop, and worked fine when called from within the game loop as {message_display(f-string)}. I respectfully request guidance to figure out how to make the class implementation work. I want to build on it to the point that I can call the dialogue window and allow the player to scroll back over dialogue already given in that instanced conversation.


Solution

  • When you make a PyGame Sprite based on pygame.sprite.Sprite, you must define two variables as a minimum:

    1. sprite.image - used to hold the sprite bitmap
    2. sprite.rect - defines the location and size of the .image

    Your Text Sprite does not appear to be creating the .image so it wont work as a normal sprite. But since you directly blit() the Text.surf to the display, you've dodged this issue for now.

    The code is not writing the text image on top of the background dialogue. It's writing it directly to the screen in the sprite __init__(). Once the sprite is constructed, this screen update is lost.

    Probably you need something like:

    class Text(pygame.sprite.Sprite):
        def __init__(self, text):
            # Call the parent class (Sprite) constructor
            pygame.sprite.Sprite.__init__(self)
            os.chdir(r"<my directory to the dialogue box>.png")
            self.image = pygame.image.load("spikey_box.png").convert()  
            os.chdir(r"<my directory to my font>")
            # Draw the text, centred on the background 
            self.font = pygame.font.Font("final_fantasy_36_font.ttf", 12)
            text = self.font.render(text, True, (255, 255, 255))
            centre_x = ( self.rect.width - text.get_width() ) // 2
            centre_y = ( self.rect.height - text.get_height() ) // 2
            self.image.blit( text, ( centre_x, centre_y ) )
            # Set the rect
            self.rect = self.image.get_rect() 
            self.rect.center = (400, 500)   
    

    Which loads the dialogue background image, and then rendered the text centred into that box so there is just a single image.

    Since surf has been renamed to image, the main loop needs a tweak:

        ...
        screen.fill((255, 255, 255))
        screen.blit(test_message.image, test_message.rect)
        pygame.display.flip()
        clock.tick(30)
    pygame.quit()
    

    But really the code should use the PyGame Sprite functions for drawing, rather than accessing the internal Surface directly.