Pygame - Text buchstabenweise - mehrzeilig

Hier werden alle anderen GUI-Toolkits sowie Spezial-Toolkits wie Spiele-Engines behandelt.
Antworten
ooHeinzz
User
Beiträge: 18
Registriert: Sonntag 30. Oktober 2022, 07:03

Ich möchte Text so erscheinen lassen, als würde er gerade getippt. Das habe ich soweit hinbekommen. Bei mehreren Zeilen werden die aber gleichzeitig geschrieben - statt nacheinander. Dafür suche ich eine Lösung. Mein Code sieht derzeit so aus (statt des rotbraun ist eigentlich ein Bild als Hintergrund):

Code: Alles auswählen

import pygame

SCREEN_WIDTH = 1200
SCREEN_HEIGTH = 892
SCREEN_TITLE = "Spiel"
SCREEN_BACKGROUND_COLOR = (50, 50, 50)
SCREEN_BORDER = 600
TICK = 60


pygame.init()
clock = pygame.time.Clock()

# Screen
pygame.display.set_caption(SCREEN_TITLE)
screen = pygame.display.set_mode([SCREEN_WIDTH, SCREEN_HEIGTH])
screen_background = pygame.image.load("./Images/hintergrund.png")

font = pygame.font.Font(None, 24)
snip1 = font.render("", True, "white")
text_counter = 0
text_speed = 3
level = 1


def draw_screen() -> None:
    pygame.display.update()


# ++++++++++++++++++++++++++++++++++ Game loop +++++++++++++++++++++++++++++++++++++++
run = True
while run:
    pygame.draw.rect(screen, (150, 50, 0), [0, 0, SCREEN_WIDTH, SCREEN_HEIGTH])
    pygame.draw.rect(screen, SCREEN_BACKGROUND_COLOR, [0, SCREEN_BORDER, SCREEN_WIDTH, SCREEN_HEIGTH])

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

    # Die messages sollen später die Reaktion auf einen user-input sein. new input ist True, wenn eine weitere User-Eingabe erfolgen soll
    # messages, new_input = create_message("ja", level)
    messages = ["Das ist die erste Zeile. Die soll fertig geschrieben sein", "...bevor die zweite Zeile erscheint."]

    messages_lens = [len(m) for m in messages]
    len_all_messages = sum(messages_lens)
    text_counter_max = len_all_messages * text_speed

    # snips enthält einen Eintrag für jedes Elemten der Liste messages
    snips = [font.render(message[0 : (text_counter // text_speed) + 1], True, "white") for message in messages]

    # text_counter muss weiterlaufen, bis der gesamte Text ausgegeben wurde
    if text_counter < (len_all_messages * text_speed):
        text_counter += 1

    line_spacing = 0

    for snip in snips:  # TODO Die Zeilen sollen nacheinander erscheinen
        screen.blit(snip, (20, (SCREEN_BORDER + line_spacing)))
        line_spacing += 25

    print(f"a: {messages_lens}, b: {len_all_messages}, c: {text_counter_max}")
    draw_screen()
    clock.tick(TICK)

pygame.quit()

Benutzeravatar
grubenfox
User
Beiträge: 432
Registriert: Freitag 2. Dezember 2022, 15:49

ooHeinzz hat geschrieben: Mittwoch 27. Dezember 2023, 12:07 Ich möchte Text so erscheinen lassen, als würde er gerade getippt. Das habe ich soweit hinbekommen.
Sicher? Soll "als würde er gerade getippt" bedeuten: mit Pausen zwischen den einzelnen Buchstaben? Mir fehlt da irgendwie die Nutzung vom Modul pygame.time im Code.
https://www.pygame.org/docs/ref/time.html
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@grubenfox: dafür gibt es ja clock.tick.

@ooHeinzz: in `snips` malst Du ja jede die ersten Zeichen von jeder Zeile. Wenn Du das so nicht haben möchtest, mußt Du anders vorgehen.

Code: Alles auswählen

    # snips enthält einen Eintrag für jedes Elemten der Liste messages
    snips = []
    characters_to_plot = text_counter // text_speed + 1
    for message in messages:
        if characters_to_plot > len(message):
            text = message
            characters_to_plot -= len(message)
        elif characters_to_plot > 0:
            text = message[:characters_to_plot]
            characters_to_plot = 0
        else:
            text = ""
        snips.append(font.render(text, True, "white"))
`snip1` und `level` werden definiert, aber nie benutzt.
ooHeinzz
User
Beiträge: 18
Registriert: Sonntag 30. Oktober 2022, 07:03

@Sirius3: Vielen Dank für die schnelle Antwort!

(snip1 ist von einem meiner Lösungsversuche übrig geblieben. level ist für die Funktion create_message(). Für das Forum habe ich messages hard gecodet.)
Benutzeravatar
__blackjack__
User
Beiträge: 13117
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@ooHeinzz: Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

`draw_screen()` ist als Funktion ja nicht so wirklich sinnvoll. Und wenn dann würde man da einfacher die `update()`-Funktion beim importieren umbenennen.

`screen_background` und `text_counter_max` werden auch nicht verwendet. Letzteres sollte verwendet werden, statt den Wert noch mal zu berechnen.

`pygame.draw.rect()` für die gesamte Fläche eines Surface ist eigentlich `fill()` auf dem `Surface`-Objekt.

Zwischenstand:

Code: Alles auswählen

#!/usr/bin/env python3
import pygame
from pygame.display import update as draw_screen

SCREEN_WIDTH = 1200
SCREEN_HEIGTH = 892
SCREEN_TITLE = "Spiel"
SCREEN_BACKGROUND_COLOR = (50, 50, 50)
SCREEN_BORDER = 600
TICK = 60


def main():
    pygame.init()
    try:
        clock = pygame.time.Clock()
        screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGTH))
        pygame.display.set_caption(SCREEN_TITLE)

        font = pygame.font.Font(None, 24)
        text_counter = 0
        text_speed = 3

        while True:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    return

            screen.fill((150, 50, 0))
            pygame.draw.rect(
                screen,
                SCREEN_BACKGROUND_COLOR,
                (0, SCREEN_BORDER, SCREEN_WIDTH, SCREEN_HEIGTH),
            )
            #
            # Die messages sollen später die Reaktion auf einen user-input sein.
            # new input ist True, wenn eine weitere User-Eingabe erfolgen soll
            # messages, new_input = create_message("ja", level)
            #
            messages = [
                "Das ist die erste Zeile. Die soll fertig geschrieben sein",
                "...bevor die zweite Zeile erscheint.",
            ]
            text_counter_max = sum(map(len, messages)) * text_speed
            #
            # TODO Die Zeilen sollen nacheinander erscheinen.
            #
            for i, snip in enumerate(
                font.render(
                    message[0 : text_counter // text_speed + 1], True, "white"
                )
                for message in messages
            ):
                screen.blit(snip, (20, SCREEN_BORDER + i * 25))
            #
            # `text_counter` muss weiterlaufen, bis der gesamte Text ausgegeben
            # wurde.
            #
            if text_counter < text_counter_max:
                text_counter += 1

            draw_screen()
            clock.tick(TICK)

    finally:
        pygame.quit()


if __name__ == "__main__":
    main()
Den Zustand für diese Animation sollte man in einem Objekt zusammenfassen. Das ist total unübersichtlich das als mehrere Einzelwerte über die Hauptfunktion zu verteilen, und das würde ja immer mehr, je mehr bewegliche oder animierte Teile man hinzufügt.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten