在pygame中渲染多行文本

问题描述 投票:0回答:12

我正在尝试制作一个游戏,并且正在尝试渲染大量文本。当文本呈现时,文本的其余部分就会从屏幕上消失。有没有什么简单的方法可以让文本转到 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))
python pygame render
12个回答
39
投票

正如我在评论中所说;您必须单独渲染每个单词并计算文本的宽度是否延伸了表面(或屏幕)的宽度。这是一个例子:

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()

结果


7
投票

在 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))

3
投票

没有自动解决方案。你必须自己实现文本换行并逐行逐字绘制文本。
幸运的是 PyGame wiki 提供了一个用于此任务的函数。请参阅 PyGame wiki pygame 的简单文本换行

我扩展了该函数并添加了一个附加参数,它提供了 leftright 对齐文本、centerd 文本甚至 block 模式。

最小示例: 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()

3
投票

这与其他人发布的内容类似,但我想我也应该上传自己的代码:

    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() * 1.1
            else:
                y += word_t.get_height() * 1.1
                x = x_start
                surface.blit(word_t, (x, y))
                x += word_t.get_width() * 1.1

我认为这是不言自明的:

  • 您输入希望文本开始的位置(x_start、y_start)和结束位置(x_end)的坐标
  • 代码循环遍历文本中的所有单词,仅当它们的宽度小于行中剩余的空间时才将它们添加到屏幕上。如果不是这种情况,则会从 x_start 开始新行,y 坐标的增加略大于字体的高度。

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

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)

如果您希望文本对齐,您可以直到行尾而不实际将单词添加到屏幕上,计算行尾剩余的空间,然后使用修改后的“再次循环该行”空间长度”,从而使您的最后一个词准确地到达您的盒子边缘。


2
投票

我推荐

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()

pygame multiline text


2
投票

pygame-ce
pygame
的现代发行版)版本
2.1.4
开始,
pygame.Font.render
现在具有换行符(
"\n"
)和wraplength(换行到新行之前的像素宽度)支持。

因此,有了这些新的、闪亮的功能,解决这个问题就变得轻而易举了,你只需将你想要的文本传递给

pygame.Font.render
,并将窗口宽度指定为最后一个参数,这样一旦文本达到窗口的宽度,它就会换行。窗户。由于此方法尚不接受关键字参数,因此您还必须提供背景颜色参数,它可以是
None
,或者您可以提供 alpha 值为 0 的颜色值。

这是一个简短的代码示例:

import pygame


WIDTH, HEIGHT = 640, 360
FPS = 60

pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
clock = pygame.time.Clock()

font = pygame.Font(None, 32)
text_surf = 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.",
    True,
    "white",
    None,  # or (0, 0, 0, 0) for example
    WIDTH,
)

running = True
while running:
    clock.tick(FPS)
    screen.fill("black")

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

    screen.blit(text_surf, (0, 0))

    pygame.display.flip()

结果:

此外,要安装最新版本的

pygame-ce
,只需按照以下步骤操作:

  1. pip uninstall pygame
    (为了避免冲突,只有当您已经安装了
    pygame
    时才必须这样做)
  2. pip install pygame-ce
  3. 享受新的闪亮功能

有关分叉的更多信息可以在这里找到:Pygame:社区版公告
具体版本公告在这里:Pygame-ce 2.1.4 release


1
投票

我就是这样做的

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)

1
投票

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

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

-1
投票

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


-1
投票

您可以使用 .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

结果:

希望这有帮助!


-1
投票

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

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)

-1
投票

text_ml_surface 创建表面。

这有助于有效地使用您的资源并管理输出,例如滚动。

from pygame import SRCALPHA
from pygame import Color, Surface
from pygame.font import Font

SURFACE_ARGS = dict(flags=SRCALPHA, depth=32)

def text_ml_surface(font_obj: Font, ml_text: str, color: str, width: int) -> Surface:
    words_by_lines = [line.split(' ') for line in ml_text.splitlines()]
    space_width = font_obj.size(' ')[0]
    color_obj = Color(color)
    font_line_height = font_obj.get_linesize()

    # get height
    x, y = 0, 0
    for line in words_by_lines:
        for word in line:
            word_width = font_obj.size(word)[0]
            if x + word_width >= width:
                x = 0
                y += font_line_height
            x += word_width + space_width
        x = 0
        y += font_line_height

    # render
    surface = Surface((width, y), **SURFACE_ARGS)
    x, y = 0, 0
    for line in words_by_lines:
        for word in line:
            word_surface = font_obj.render(word, True, color_obj)
            word_width = word_surface.get_size()[0]
            if x + word_width >= width:
                x = 0
                y += font_line_height
            surface.blit(word_surface, (x, y))
            x += word_width + space_width
        x = 0
        y += font_line_height

    return surface
© www.soinside.com 2019 - 2024. All rights reserved.