在pygame中渲染多行文本

Posted

技术标签:

【中文标题】在pygame中渲染多行文本【英文标题】:Rendering text with multiple lines in pygame 【发布时间】:2017-06-20 05:33:22 【问题描述】:

我正在尝试制作游戏,并且正在尝试渲染大量文本。当文本呈现时,文本的其余部分会离开屏幕。有什么简单的方法可以让文本转到pygame窗口的下一行?

helpT = sys_font.render \
                ("This game is a combination of all of the trends\n of 2016. When you press 'Start Game,' a menu will pop up. In order to beat the game, you must get a perfect score on every single one of these games.",0,(hecolor))
        screen.blit(helpT,(0, 0))

【问题讨论】:

您必须自己计算。使用surface.get_width() 渲染每个单词并检查有多少单词可以适合屏幕。然后在另一行上 blit 其余部分,该行将低于 surface.get_height() 像素。 PyGame 不会呈现 \n,因此您必须为每一行分别使用 render 正如@TedKleinBergman alreadu 所说-您可以获得表面高度。你也可以得到next_line_rect = surface.get_rect()然后next_line_rect.top = prev_line_rect.bottom 【参考方案1】:

在 pygame 中没有简单的方法在多行上渲染文本,但是这个辅助函数可以为您提供一些用处。只需传入您的文本(带换行符)、x、y 和字体大小。

def render_multi_line(text, x, y, fsize)
        lines = text.splitlines()
        for i, l in enumerate(lines):
            screen.blit(sys_font.render(l, 0, hecolor), (x, y + fsize*i))

【讨论】:

字体大小与字符高度不同。 There's no standard way to figure out how tall a certain character might be at any given size, aside from rendering the font. 此外,sys_fonthecolor 在您的函数中未定义。我建议传递一个字体对象而不是字体大小,然后设置text_height = font.size(lines[0])[1]。这将允许您执行screen.blit(font.render(l, 0, color), (x, y + text_height*i))。您还需要为color 设置一个参数。 可以使用font.get_linesize()获取字体的行高。【参考方案2】:

正如我在 cmets 中所说的那样;您必须分别渲染每个单词并计算文本的宽度是否扩展了表面(或屏幕)的宽度。这是一个例子:

import pygame
pygame.init()


SIZE = WIDTH, HEIGHT = (1024, 720)
FPS = 30
screen = pygame.display.set_mode(SIZE, pygame.RESIZABLE)
clock = pygame.time.Clock()


def blit_text(surface, text, pos, font, color=pygame.Color('black')):
    words = [word.split(' ') for word in text.splitlines()]  # 2D array where each row is a list of words.
    space = font.size(' ')[0]  # The width of a space.
    max_width, max_height = surface.get_size()
    x, y = pos
    for line in words:
        for word in line:
            word_surface = font.render(word, 0, color)
            word_width, word_height = word_surface.get_size()
            if x + word_width >= max_width:
                x = pos[0]  # Reset the x.
                y += word_height  # Start on new row.
            surface.blit(word_surface, (x, y))
            x += word_width + space
        x = pos[0]  # Reset the x.
        y += word_height  # Start on new row.


text = "This is a really long sentence with a couple of breaks.\nSometimes it will break even if there isn't a break " \
       "in the sentence, but that's because the text is too long to fit the screen.\nIt can look strange sometimes.\n" \
       "This function doesn't check if the text is too high to fit on the height of the surface though, so sometimes " \
       "text will disappear underneath the surface"
font = pygame.font.SysFont('Arial', 64)

while True:

    dt = clock.tick(FPS) / 1000

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

    screen.fill(pygame.Color('white'))
    blit_text(screen, text, (20, 20), font)
    pygame.display.update()

结果

【讨论】:

【参考方案3】:

我就是这样做的

amfolyt_beskrivelse_text = ['en amfolyt er et stof som både kan være en base, eller syre','så som']
    for x in amfolyt_beskrivelse_text:
        descriptioncounter += 1
        screen.blit((pygame.font.SysFont('constantia',12).render(x, True, BLACK)),(300,10*descriptioncounter))
    descriptioncounter = 0

当然,我只能这样做,因为我的文本从屏幕顶部开始有一行距离。如果您从屏幕下方开始,您可以这样做

(300,12+12*descriptioncounter)

【讨论】:

【参考方案4】:

您可以做的一件事是使用等宽字体。它们对于所有字符都具有相同的大小,因此受到程序员的喜爱。这将是我处理高度/宽度问题的解决方案。

【讨论】:

【参考方案5】:

您可以使用 .json 文件来加载每一行。

.json 文件(称为 first.json):

["Hello!", "How's it going?"]

然后将其加载到文件中:

sys_font = pygame.font.SysFont(("Arial"),30)

def message_box(text):
    pos = 560 # depends on message box location
    pygame.draw.rect(root, (0,0,0), (100, 550, 800, 200)) #rectangle position varies
    for x in range(len(text)):
        rendered = sys_font.render(text[x], 0, (255,255,255))
        root.blit(rendered, ( 110, pos))
        pos += 30 # moves the following line down 30 pixels

with open('first.json') as text:
    message_box(json.load(text))

别忘了import json

结果:

希望这会有所帮助!

【讨论】:

【参考方案6】:

我推荐ptext 库,它能够识别换行符 (\n) 字符。您只需拨打ptext.draw(text, position)即可。

import pygame as pg
import ptext


pg.init()
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
BG_COLOR = pg.Color('gray12')
BLUE = pg.Color('dodgerblue')
# Triple quoted strings contain newline characters.
text = """Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua.

Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
culpa qui officia deserunt mollit anim id est laborum."""

done = False
while not done:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            done = True

    screen.fill(BG_COLOR)
    ptext.draw(text, (10, 10), color=BLUE)  # Recognizes newline characters.
    pg.display.flip()
    clock.tick(60)

pg.quit()

【讨论】:

【参考方案7】:

在之前的答案的基础上,我做了一个比较全面的 blit text 函数,我可以用一个简短的命令来使用:

def blittext(text, **kwargs):
    #blit text into screen, uses defaults and works for multiline strings
    fontface = kwargs.get('font', 'PressStart2P-Regular.ttf')
    b = kwargs.get('bold', False)
    fontsize = kwargs.get('size', 30)
    color = kwargs.get('color', (255, 255, 255))
    topleft = kwargs.get('topleft',(w/2,h/2))
    center = kwargs.get('center')
    textsurf = kwargs.get('surface',surface)
    maxwidth = kwargs.get('width',w)
    try:
        myfont = pygame.font.Font('/storage/emulated/0/games/' + fontface, fontsize)
    except:
        myfont = pygame.font.SysFont(fontface, fontsize, bold=b)
    x,y = topleft
    charwidth = myfont.size(' ')[0]
    charheight = fontsize + 3
    
    if center:
        for l in text.splitlines():
            mytext = myfont.render(l, False, color)
            textrect = mytext.get_rect(center=center)
            center = (center[0],center[1]+charheight)
            textsurf.blit(mytext,textrect)      
    else:
        for line in text.splitlines():
            for word in line.split(' '):
                mytext = myfont.render(word, False, color)
                textrect = mytext.get_rect(topleft=(x,y))
                wordwidth = textrect.width
                if x + wordwidth >= maxwidth:
                    x,y = (topleft[0], y + charheight)
                textsurf.blit(mytext,(x,y))
                x += charwidth + wordwidth
            x,y = (topleft[0], y + charheight)

【讨论】:

【参考方案8】:

创建了这个可能会有所帮助的函数 :) 只需确保每个新段落都是您在此函数上调用的列表中的一个新项目。

def multilineText(Surface, textAsList: list, font: str, size: int, colour, antialias: bool, centerTupleCoord: tuple, spaceBetweenLines: int):
    xPosition = centerTupleCoord[0]
    yPosition = centerTupleCoord[1]
    for paragraph in textAsList:
        fontObjsrt = pygame.font.SysFont(font, size)
        TextSurf = fontObjsrt.render(paragraph, antialias, colour)
        TextRect = TextSurf.get_rect()
        TextRect.center = (xPosition, yPosition)
        Surface.blit(TextSurf, TextRect)
        yPosition += spaceBetweenLines

【讨论】:

【参考方案9】:

没有自动解决方案。您必须自己实现文本换行并逐行分别逐字绘制文本。 幸运的是 PyGame wiki 为这个任务提供了一个函数。请参阅 PyGame wiki Simple Text Wrapping for pygame。

我已经扩展了函数并添加了一个额外的参数,它提供了 leftright 对齐的文本,centerd 文本甚至 阻止模式。

最小示例: repl.it/@Rabbid76/PyGame-TextWrap

import pygame

pygame.init()
font = pygame.font.SysFont(None, 40)

textAlignLeft = 0
textAlignRight = 1
textAlignCenter = 2
textAlignBlock = 3

def drawText(surface, text, color, rect, font, align=textAlignLeft, aa=False, bkg=None):
    lineSpacing = -2
    spaceWidth, fontHeight = font.size(" ")[0], font.size("Tg")[1]

    listOfWords = text.split(" ")
    if bkg:
        imageList = [font.render(word, 1, color, bkg) for word in listOfWords]
        for image in imageList: image.set_colorkey(bkg)
    else:
        imageList = [font.render(word, aa, color) for word in listOfWords]

    maxLen = rect[2]
    lineLenList = [0]
    lineList = [[]]
    for image in imageList:
        width = image.get_width()
        lineLen = lineLenList[-1] + len(lineList[-1]) * spaceWidth + width
        if len(lineList[-1]) == 0 or lineLen <= maxLen:
            lineLenList[-1] += width
            lineList[-1].append(image)
        else:
            lineLenList.append(width)
            lineList.append([image])

    lineBottom = rect[1]
    lastLine = 0
    for lineLen, lineImages in zip(lineLenList, lineList):
        lineLeft = rect[0]
        if align == textAlignRight:
            lineLeft += + rect[2] - lineLen - spaceWidth * (len(lineImages)-1)
        elif align == textAlignCenter:
            lineLeft += (rect[2] - lineLen - spaceWidth * (len(lineImages)-1)) // 2
        elif align == textAlignBlock and len(lineImages) > 1:
            spaceWidth = (rect[2] - lineLen) // (len(lineImages)-1)
        if lineBottom + fontHeight > rect[1] + rect[3]:
            break
        lastLine += 1
        for i, image in enumerate(lineImages):
            x, y = lineLeft + i*spaceWidth, lineBottom
            surface.blit(image, (round(x), y))
            lineLeft += image.get_width() 
        lineBottom += fontHeight + lineSpacing

    if lastLine < len(lineList):
        drawWords = sum([len(lineList[i]) for i in range(lastLine)])
        remainingText = ""
        for text in listOfWords[drawWords:]: remainingText += text + " "
        return remainingText
    return ""

msg = "Simple function that will draw text and wrap it to fit the rect passed.  If there is any text that will not fit into the box, the remaining text will be returned."
textRect = pygame.Rect(100, 100, 300, 300)

window = pygame.display.set_mode((500, 500))
run = True
while run:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False

    window.fill((255, 255, 255))
    pygame.draw.rect(window, (0, 0, 0), textRect, 1)
    drawTextRect = textRect.inflate(-5, -5)
    drawText(window, msg, (0, 0, 0), drawTextRect, font, textAlignBlock, True)
    pygame.display.flip()

pygame.quit()
exit()

【讨论】:

【参考方案10】:

这将类似于其他人发布的内容,但我想我也会上传自己的代码:

    def box_text(surface, font, x_start, x_end, y_start, text, colour):
        x = x_start
        y = y_start
        words = text.split(' ')

        for word in words:
            word_t = font.render(word, True, colour)
            if word_t.get_width() + x <= x_end:
                surface.blit(word_t, (x, y))
                x += word_t.get_width() + 2
            else:
                y += word_t.get_height() + 4
                x = x_start
                surface.blit(word_t, (x, y))
                x += word_t.get_width() + 2

我认为这很容易解释,您输入您希望文本开始的位置 (x_start) 以及应该结束的位置,然后它几乎会下降,直到您输入的字符串完成。

我在个人项目中使用的一个例子:

x_start = self.W / 2 - info_box.get_width() / 2 + 10

self.box_text(self.WINDOW, self.info_font, x_start, x_start + 430, self.H / 3 + 10, self.mage_text, self.white)

如果您希望文本是合理的,则必须进行一些小的更改。

【讨论】:

以上是关于在pygame中渲染多行文本的主要内容,如果未能解决你的问题,请参考以下文章

在pygame中的透明表面上渲染抗锯齿文本

使用 Rails 渲染多行文本?

PyGame 应用程序中的 SVG 渲染。在 Pygame 2.0 之前,Pygame 不支持 SVG。那你是怎么加载的?

Win GDI DrawText 渲染错误

标签渲染器问题,而点击阅读更多...文本不是仅在 ios 中自动换行

在python中没有可见窗口的OpenGL渲染视图