How to make new window in pygame?

I've been making game using pygame and found out necessity of new window in this game. screenshot of the game

When boy collide with one of the companies(Web Canape, Smolenskiye Brillianty...) new window must be opened to do quize there. The main game also need to continue working, because boy's task is to go through all companies.

Can someone help me with solving this problem, please?
Probably, it is possible to use new module such as PyQt5 or Tkinter in order not to terminate whole game.

https://github.com/TotumRevolutum/shadows-game

import sys
from map import *
import pygame.display


pygame.init()    

WIDTH = 11 * 100
HEIGHT = 7 * 100
clock = pygame.time.Clock()


def text_show(number):
    intro_text_1 = ["Привет! Меня зовут Емеля.", "Я приглашаю тебя на         День",
                "Теней на предприятия", "Смоленской области.", " ", " ДАЛЕЕ"]
intro_text_2 = ['"День Теней" - это день,', "в течение которого школьники",
                "могут лично следить за работой ", "специалистов с целью проверки",
                "правильности выбора профессии.", " ДАЛЕЕ"]
intro_text_3 = ['Мы с тобой будем определяться', "с профессией по принципу ",
                'индукции от "частного" к "общему",', 'от "предприятия" к "профессии."',
                "", " ДАЛЕЕ"]
intro_text_4 = ['В конце Дня Теней', "ты сможешь выбрать предприятие,",
                'на котором хотел бы работать!', '',
                "", " ДАЛЕЕ"]
if number == 1:
    text = intro_text_1
elif number == 2:
    text = intro_text_2
elif number == 3:
    text = intro_text_3
else:
    text = intro_text_4

back = Background('bg/boy_start.png', [0, 0])
screen.blit(back.image, back.rect)
font = pygame.font.SysFont("Typewriter", 33)
tmp = 0
for line in text:
    if line == " ДАЛЕЕ":
        lines = font.render(line, 1, pygame.Color('red'))
    else:
        lines = font.render(line, 1, pygame.Color('black'))
    display_rect = lines.get_rect()
    tmp += 10
    display_rect.y = 140 + tmp
    display_rect.x = 640
    tmp += display_rect.height
    screen.blit(lines, display_rect)

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
        elif event.type == pygame.MOUSEBUTTONDOWN:
            return
    pygame.display.flip()
    clock.tick(30)


text_show(1)
text_show(2)
text_show(3)
text_show(4)
running = True
generate_level(load_level())

while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

keys = pygame.key.get_pressed()
move_boy(keys, player_group)

all_sprites.draw(screen)
tiles_group.draw(screen)
player_group.draw(screen)
pygame.display.flip()
clock.tick(60)
# other parts are located in the git 
# https://github.com/TotumRevolutum/shadows-game

1 answer

  • answered 2019-03-14 07:56 sloth

    You don't need and don't want a new window. Just create another Surface/Sprite that renders the text and handles the events.

    Here's a simple example I hacked together. Note the comments, as they explain what's going on:

    import pygame
    import pygame.freetype
    
    # So our game has 2 states.
    # Either we're in the world and run around;
    # or we're displaying a menu and the player has to make a choice.
    WORLD = 0
    MENU = 1
    
    # from https://www.pygame.org/docs/ref/freetype.html#pygame.freetype.Font.render_to
    def word_wrap(surf, text, font, color=(0, 0, 0)):
        font.origin = True
        words = text.split(' ')
        width, height = surf.get_size()
        line_spacing = font.get_sized_height() + 2
        x, y = 0, line_spacing
        space = font.get_rect(' ')
        for word in words:
            bounds = font.get_rect(word)
            if x + bounds.width + bounds.x >= width:
                x, y = 0, y + line_spacing
            if x + bounds.width + bounds.x >= width:
                raise ValueError("word too wide for the surface")
            if y + bounds.height - bounds.y >= height:
                raise ValueError("text to long for the surface")
            font.render_to(surf, (x, y), None, color)
            x += bounds.width + space.width
        return x, y
    
    # This sprite handles the menu.
    # It renders a box and a text and listens for key presses.
    # If a key we're interessed in is pressed, we call the callback function.
    class TextMenu(pygame.sprite.Sprite):
        def __init__(self, font, text, listen_to, callback):
            super().__init__()
            self.image = pygame.Surface((400, 400))
            self.image.fill(pygame.Color('white'))
            self.image.fill(pygame.Color('black'), self.image.get_rect().inflate((-50, -50)))
            self.rect = self.image.get_rect(topleft=(50, 50))
            word_wrap(self.image.subsurface(self.image.get_rect().inflate((-100, -100))), text, font, pygame.Color('white'))
            self.callback = callback
            self.listen_to = listen_to
    
        def update(self, events, dt):
            for e in events:
                if e.type == pygame.KEYDOWN and e.key in self.listen_to:
                    self.callback(self, e.key)
    
    # This sprite represents a building the player can "walk in" to trigger 
    # a menu pop up. In this case, we want the user to either press 1 or 2.
    # Then we change the color, because why not, something should happen.
    class House(pygame.sprite.Sprite):
        def __init__(self, pos, player, show_text):
            super().__init__()
            self.image = pygame.Surface((64, 64))
            self.image.fill(pygame.Color('darkred'))
            self.rect = self.image.get_rect(center=pos)
            self.show_text = show_text
            self.player = player
            # Since the menu is triggered when the player touches the building,
            # we don't want an endless loop, so we need a flag that prevents
            # the menu until the player "leaves the building"
            self.triggered = False
    
        def change_color(self, key):
            if key == pygame.K_1:
                self.image.fill(pygame.Color('yellow'))
            if key == pygame.K_2:
                self.image.fill(pygame.Color('darkblue'))
    
        def update(self, events, dt):
            if pygame.sprite.collide_rect(self, self.player):
                if not self.triggered:
                    self.show_text('Welcome, little blue rect. Please press (1) or (2).', (pygame.K_1, pygame.K_2), self.change_color)
                    self.triggered = True
            else:
                self.triggered = False
    
    # This is the player. 
    # Does basically nothing but run around
    class Player(pygame.sprite.Sprite):
        def __init__(self):
            super().__init__()
            self.image = pygame.Surface((32, 32))
            self.image.fill(pygame.Color('dodgerblue'))
            self.rect = self.image.get_rect()
            self.pos = pygame.Vector2((100, 200))
    
        def update(self, events, dt):
            pressed = pygame.key.get_pressed()
            move = pygame.Vector2((0, 0))
            if pressed[pygame.K_w]: move += (0, -1)
            if pressed[pygame.K_a]: move += (-2, 0)
            if pressed[pygame.K_s]: move += (0, 2)
            if pressed[pygame.K_d]: move += (2, 0)
            if move.length() > 0: move.normalize_ip()
            self.pos += move*(dt/5)
            self.rect.center = self.pos
    
    def main():
        pygame.init()
        screen = pygame.display.set_mode((500, 500))
        font = pygame.freetype.SysFont(None, 32)
        clock = pygame.time.Clock()
        dt = 0
    
        player = Player()
    
        # keep track of the state we're in.
        # we start in the WORLD state, a.k.a. running around.
        # the state just tells us which sprites are "active", 
        # a.k.a. if they are updated by calling thier update function
        state = WORLD
    
        # sprite group for all MENU-sprites
        menu_sprites = pygame.sprite.Group()
    
        # sprite group for all WORLD-sprites
        sprites = pygame.sprite.Group(player)
    
        # this function allows other sprites to trigger a menu
        def show_text(text, listen_to, callback):
    
            # this function is called by the menu.
            # we change the state back to world and kill the TextMenu sprite
            def wrapped_callback(sprite, *args):
                nonlocal state
                state = WORLD
                callback(*args)
                sprite.kill()
    
            # so when this function is called , let's switch to the MENU state
            nonlocal state
            state = MENU
            # add the TextMenu sprite to the menu_sprites group so it "lives"
            menu_sprites.add(TextMenu(font, text, listen_to, wrapped_callback))
    
        # create some buildings. They are all the same...
        for pos in ((300, 300), (200, 400), (100, 100)):
            sprites.add(House(pos, player, show_text))
    
        while True:
            events = pygame.event.get()
            for e in events:
                if e.type == pygame.QUIT:
                    return
    
            # see which sprites are "active". The WORLD sprites or the MENU sprites
            if state == WORLD:
                sprites.update(events, dt)
            else:
                menu_sprites.update(events, dt)
    
            screen.fill((30, 30, 30))
            sprites.draw(screen)
            menu_sprites.draw(screen)
            pygame.display.update()
            dt = clock.tick(60)
    
    if __name__ == '__main__':
        main()
    

    enter image description here

    Note how all the game logic is cleanly seperated, and also this approach makes it easy to add other states, like a pause function or a menu.

    Of course there are dozen other ways to do this, but you'll get the idea. For another idea of how to implement different states in your game, maybe look at this question.