打字机效果 Pygame

Typewriter Effect Pygame

这个问题真的很难问,但我知道 Stack Overflow 的你们是最聪明的人。

我完全不知道为什么会出现这个问题(我在 Python 和 Pygame,所以任何关于如何改进代码的建议都会随着改进的热情而被接受我的技能)。

我正在创建的内容: 这真的是一个噱头项目,我有一个 2.5" 的小屏幕 (PiTFT) 连接到一个 Raspberry Pi 并且代码正在创建一个打字机效果,在编写文本时光标会在文本前面移动。

挑战1是在pygame中每次移动一个sprite,都必须重新绘制,否则你会看到一条轨迹,由于光标在文本前面移动,结果看起来像这样:

我设法通过黑屏/清屏解决了这个问题。但后来我丢失了所有以前写的信件。 所以我创建了一个列表(整个单词),其中填充了所有以前编写的字符。每次循环时我都使用这个列表来重绘所有以前写的文本。 所以现在:

如您所见,文字看起来很有趣。 它应该是:

[i] 正在初始化...

[i]进入幽灵模式...[]

为了达到这一点,我已经花费了数小时和数小时 - 代码几乎可以完美运行!魔术发生在函数 print_screen() 中,但是我的代码中的什么导致文本最后包含来自另一行的字母? :>

非常感谢您的帮助 <3

完整代码如下:

import pygame
import time
import os
import sys
from time import sleep
from pygame.locals import *

positionx = 10
positiony = 10
entireword = []
entireword_pos = 10
counter = 0
entire_newline = False


#Sets the width and height of the screen
WIDTH = 320
HEIGHT = 240
speed = 0.05

#Importing the external screen
os.putenv('SDL_FBDEV', '/dev/fb1')
os.putenv('SDL_MOUSEDRV', 'TSLIB')
os.putenv('SDL_MOUSEDEV', '/dev/input/touchscreen')

#Initializes the screen - Careful: all pygame commands must come after the init
pygame.init()

#Sets mouse cursor visibility
pygame.mouse.set_visible(False)
#Sets the screen note: must be after pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))

# initialize font; must be called after 'pygame.init()' to avoid 'Font not Initialized' error
myfont = pygame.font.SysFont("monospace", 18)

#Class

class cursors(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface((10, 20))
        self.image.fill((0,255,0))
        self.rect = self.image.get_rect()
        self.rect.center = (positionx + 10, positiony + 10)

    def update(self):
        self.rect.x = positionx + 10
        self.rect.y = positiony

#Functions

#Prints to the screen
def print_screen(words, speed):
    rel_speed = speed
    for char in words:
        #speed of writing
        if char == ".":
            sleep(0.3)
        else:
            sleep(rel_speed)

        #re-renders previous written letters
        global entireword

        # Old Typewriter functionality - Changes position of cursor and text a newline

        #Makes sure the previous letters are rendered and not lost
        #xx is a delimter so the program can see when to make a newline and ofcourse ignore writing the delimiter
        entireword.append(char)
        if counter > 0:
            loopcount = 1
            linecount = 0 # This is to which line we are on
            for prev in entireword:
                if prev == 'xx':
                    global linecount
                    global positiony
                    global loopcount
                    linecount = linecount + 1
                    positiony = 17 * linecount
                    loopcount = 1
                if prev != 'xx':  #ignore writing the delimiter
                    pchar = myfont.render(prev, 1, (255,255,0))
                    screen.blit(pchar, (loopcount * 10, positiony))
                    loopcount = loopcount + 1

        if char != 'xx':
            # render text
            letter = myfont.render(char, 1, (255,255,0))
            #blits the latest letter to the screen
            screen.blit(letter, (positionx, positiony))

        # Appends xx as a delimiter to indicate a new line
        if entire_newline == True:
            entireword.append('xx')
            global entire_newline
            entire_newline = False

        global positionx
        positionx = positionx + 10
        all_sprites.update()
        all_sprites.draw(screen)
        pygame.display.flip()
        screen.fill((0,0,0)) # blackens / clears the screen

        global counter
        counter = counter + 1

#Positions cursor at new line
def newline():
    global positionx
    global positiony
    positionx = 10
    positiony = positiony + 17

all_sprites = pygame.sprite.Group()
cursor = cursors()
all_sprites.add(cursor)

#Main loop
running = True
while running:
    global speed
    global entire_newline

    words = "[i] Initializing ..."
    entire_newline = True
    newline()
    print_screen(words,speed)

    words = "[i] Entering ghost mode ..."
    entire_newline = True
    newline()
    print_screen(words,speed)

    #Stops the endless loop if False
    running = False
sleep(10)

抱歉,如果我没有直接回答你的问题,因为你的代码现在对我来说太混乱了,所以我冒昧地重写了你的代码来完成你想要的。

想法是有两个精灵:

  • 光标,a) 显示在屏幕上 b) 跟踪要写的文本和位置

  • 板,它基本上只是呈现文本的表面

请注意,所有的编写逻辑都在 Cursor class 上,我们有一个漂亮、简单且愚蠢的主循环。

import pygame
import os

#Sets the width and height of the screen
WIDTH = 320
HEIGHT = 240

#Importing the external screen
os.putenv('SDL_FBDEV', '/dev/fb1')
os.putenv('SDL_MOUSEDRV', 'TSLIB')
os.putenv('SDL_MOUSEDEV', '/dev/input/touchscreen')

#Initializes the screen - Careful: all pygame commands must come after the init
pygame.init()
clock = pygame.time.Clock()

#Sets mouse cursor visibility
pygame.mouse.set_visible(False)
#Sets the screen note: must be after pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))


class Board(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface((WIDTH, HEIGHT))
        self.image.fill((13,13,13))
        self.image.set_colorkey((13,13,13))
        self.rect = self.image.get_rect()
        self.font = pygame.font.SysFont("monospace", 18)

    def add(self, letter, pos):
        s = self.font.render(letter, 1, (255, 255, 0))
        self.image.blit(s, pos)

class Cursor(pygame.sprite.Sprite):
    def __init__(self, board):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface((10, 20))
        self.image.fill((0,255,0))
        self.text_height = 17
        self.text_width = 10
        self.rect = self.image.get_rect(topleft=(self.text_width, self.text_height))
        self.board = board
        self.text = ''
        self.cooldown = 0
        self.cooldowns = {'.': 12,
                        '[': 18,
                        ']': 18,
                        ' ': 5,
                        '\n': 30}

    def write(self, text):
        self.text = list(text)

    def update(self):
        if not self.cooldown and self.text:
            letter = self.text.pop(0)
            if letter == '\n':
                self.rect.move_ip((0, self.text_height))
                self.rect.x = self.text_width
            else:
                self.board.add(letter, self.rect.topleft)
                self.rect.move_ip((self.text_width, 0))
            self.cooldown = self.cooldowns.get(letter, 8)

        if self.cooldown:
            self.cooldown -= 1

all_sprites = pygame.sprite.Group()
board = Board()
cursor = Cursor(board)
all_sprites.add(cursor, board)

text = """[i] Initializing ...
[i] Entering ghost mode ...

done ...

"""

cursor.write(text)

#Main loop
running = True
while running:

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

    all_sprites.update()
    screen.fill((0, 0, 0))
    all_sprites.draw(screen)
    pygame.display.flip()
    clock.tick(60)

使用事件队列中的pygame.event module. Use pygame.time.set_timer() to repeatedly create a USEREVENT。时间必须以毫秒为单位设置。例如:

typewriter_event = pygame.USEREVENT+1
pygame.time.set_timer(typewriter_event, 100)

在文本中添加一个新字母,当定时器事件发生时:

while run:
    for event in pygame.event.get():
        # [...]

        if event.type == typewriter_event:
            text_len += 1

另见 Typewriter


最小示例:

repl.it/@Rabbid76/PyGame-Typewriter

import pygame

pygame.init()
window = pygame.display.set_mode((500, 150))
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 100)

background = pygame.Surface(window.get_size())
ts, w, h, c1, c2 = 50, *window.get_size(), (32, 32, 32), (64, 64, 64)
tiles = [((x*ts, y*ts, ts, ts), c1 if (x+y) % 2 == 0 else c2) for x in range((w+ts-1)//ts) for y in range((h+ts-1)//ts)]
for rect, color in tiles:
    pygame.draw.rect(background, color, rect)

text = 'Hello World'
text_len = 0
typewriter_event = pygame.USEREVENT+1
pygame.time.set_timer(typewriter_event, 100)
text_surf = None

run = True
while run:
    clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False
        if event.type == typewriter_event:
            text_len += 1
            if text_len > len(text):
                text_len = 0
            text_surf = None if text_len == 0 else font.render(text[:text_len], True, (255, 255, 128))

    window.blit(background, (0, 0))
    if text_surf:
        window.blit(text_surf, text_surf.get_rect(midleft = window.get_rect().midleft).move(40, 0))
    pygame.display.flip()

pygame.quit()
exit()