是否可以在 pygame 中创建可以 deleted/messed 左右的弹出窗口而不影响主屏幕上显示的内容

Is it possible to create popups in pygame that can be deleted/messed around with without effecting what is displaying on the main screen

基本上我想要的是当你将鼠标悬停在我游戏中的一个单位或项目上时,它会显示一些关于 item/unit 的基本统计数据(理想情况下是鼠标所在的位置),就像你将鼠标悬停在开始按钮上一样在 windows 上它会显示一个小框,上面写着开始。

是否可以在 pygame 中执行此操作,以便当我将鼠标悬停在设备上时,它可以在屏幕的新表面或水平面上显示此数据,以便当我停止悬停时,我可以简单地 delete/do 去掉统计数据框并恢复之前弹出窗口背后的内容,即 background/level/current 战斗

我只是在鼠标位置 blit 一个方框,然后把统计数据放在里面,但我不确定如何摆脱它,因为它后面有很多以前的 blit,需要我重新绘制整个每次我将鼠标悬停在屏幕上时,不幸的是,这是不可行的

非常感谢

我自己没有尝试过,但不明白为什么 copy() 它不能像在任何其他屏幕上那样在显示表面上工作。

尝试在放置弹出窗口之前使用 Surface copy() 方法(文档 here)制作显示表面的副本,然后当您想要重新绘制弹出窗口下方的区域时您可以将副本中需要的部分 blit 回正确的位置。这当然假设您要重绘的区域没有任何变化,但这无论如何都是一个问题(如果有的话,可以通过对副本进行 blit'ing 更改来处理)。

您可以使用 blit()area 参数来限制从副本到显示器的复制重绘量。

好的,因为您不是每帧都更新屏幕,所以有必要保存弹出窗口“下面”的位图,然后用它来擦除它。

Python 没有 Surface.copy_area(),但是将 Surface 制作成需要复制的区域的大小相当简单,然后使用Surface.blit() 的第三个参数 - 这是要复制的源表面区域:

def copyArea( screen, x, y, width, height ):
    """ Copy a region of the given Surface, returning a new Surface """
    # create surface to save the background into
    copy_to = pygame.Surface( ( width, height ) )                      
    # copy the background
    copy_to.blit( screen, ( 0, 0 ), ( x, y, width, height ) )
    return copy_to

复杂的部分是将文本元素渲染到弹出框中!

我不想详细说明,但基本上您可以使用给定的字体将每行文本转换为位图。每一个都有自己的高度和宽度。代码需要对高度求和,求出最大宽度,才能知道目标弹出框的大小。然后可以将它们依次绘制到目标位图,为每个位图应用边距和行距。

然后是悬停。代码需要首先检测悬停。这很简单,就是鼠标光标是否在矩形内。当它进入时绘制弹出窗口,当它离开时再次取消绘制。

参考代码:

import pygame

# Window size
WINDOW_WIDTH    = 500
WINDOW_HEIGHT   = 500
WINDOW_SURFACE  = pygame.HWSURFACE|pygame.DOUBLEBUF|pygame.RESIZABLE

RED       = ( 200,   0,   0 )

class ErasablePopup:
    FOREGROUND_COLOUR = (  86,  54,   8 )  # dark chocolate brown
    BACKGROUND_COLOUR = ( 255, 228, 157 )  # sepia yellowish white
    SIDE_MARGIN       = 7                  # size of corners and margin
    LINE_SPACING      = 1                  # pixels between lines

    def __init__( self, font, message ):
        # First render the text to an image, line by line
        self.image = self._textToBitmap( font, message )
        self.rect  = self.image.get_rect()
        self.under = None                                                                # The underneath image when drawn

    def drawAt( self, screen, position ):
        """ Draw the popup at the given location, saving the underneath """
        x, y = position
        self.rect.topleft = ( x, y )
        self.under = pygame.Surface( ( self.rect.width, self.rect.height ) )              # create surface to save
        self.under.blit( screen, ( 0, 0 ), ( x, y, self.rect.width, self.rect.height ) )  # copy the background
        screen.blit( self.image, self.rect )                                              # draw the rendered-text

    def isShown( self ):
        """ Is this popup drawn to the screen? """
        return ( self.under != None )                                                     # if we're on-screen there's an under

    def unDraw( self, screen ):
        """ Erase the pop-up by re-drawing the previous background """
        # Only erase if we're drawn
        if ( self.under != None ):
            screen.blit( self.under, self.rect )                                          # restore the background
            self.under = None                                                             # release the RAM

    def _textToBitmap( self, font, message ):
        """ Given a (possibly) multiline text message
            convert it into a bitmap represenation with the
            given font """

        height_tally  = 2 * self.SIDE_MARGIN     # height-sum of message lines
        maximum_width = 0                        # maximum message width
        message_lines = []                       # the text-rendered image
        message_rects = []                       # where it's painted to
        # cleanup messages, remove blank lines, et.al
        for line in message.split( '\n' ):    # for each line
            if ( len( line ) == 0 ):
                line = ' '   # make empty lines non-empty
            # Make each line into a bitmap
            message_line = font.render( line, True, self.FOREGROUND_COLOUR, self.BACKGROUND_COLOUR )
            message_lines.append( message_line )
            # do the statistics to determine the bounding-box
            maximum_width = max( maximum_width, message_line.get_width() )
            height_tally  += self.LINE_SPACING + message_line.get_height() 
            # remember where to draw it later
            position_rect = message_line.get_rect()
            if ( len( message_rects ) == 0 ):
                position_rect.move_ip( self.SIDE_MARGIN, self.SIDE_MARGIN )
            else:
                y_cursor = message_rects[-1].bottom + self.LINE_SPACING + 1
                position_rect.move_ip( self.SIDE_MARGIN, y_cursor )
            message_rects.append( position_rect )
        # Render the underlying text-box
        maximum_width += 2 * self.SIDE_MARGIN                                       # add the margin
        image = pygame.Surface( ( maximum_width, height_tally ), pygame.SRCALPHA )  # transparent bitmap
        image.fill( self.BACKGROUND_COLOUR )
        # draw the lines of text
        for i in range( len ( message_lines ) ):
            image.blit( message_lines[i], message_rects[i] )
        return image





### initialisation
pygame.init()
pygame.mixer.init()
window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), WINDOW_SURFACE )
pygame.display.set_caption("text player")

### Message Text For Displaying
popup_font = pygame.font.Font( None, 18  )
popups     = []                               # list of popup objects
hover_rects= []                               # list of hover locations

popups.append( ErasablePopup( popup_font, "The Owl and the Pussy-Cat went to sea\n   In a beautiful pea-green boat:\nThey took some honey,\n   and plenty of money\nWrapped up in a five-pound note." ) )
popups.append( ErasablePopup( popup_font, "I smell a Wumpus!" ) )
hover_rects.append( pygame.Rect( 150, 150, 70, 70 ) )  # hot-spot1
hover_rects.append( pygame.Rect( 300, 300, 70, 70 ) )  # hot-spot2

### Background image
grassy_background = pygame.image.load( "big_grass_texture.jpg" )  # ref: https://jooinn.com/images/grass-texture-10.jpg
grassy_background = pygame.transform.smoothscale( grassy_background, ( WINDOW_WIDTH, WINDOW_HEIGHT ) )

### Main Loop
do_once = True
clock = pygame.time.Clock()
done = False
while not done:

    # Paint the background, but just once
    if ( do_once ):
        do_once = False
        window.blit( grassy_background, ( 0, 0 ) )
        for i in range( len ( hover_rects ) ):
            pygame.draw.rect( window, RED, hover_rects[i], 2 )

    # Handle user-input
    for event in pygame.event.get():
        if ( event.type == pygame.QUIT ):
            done = True

    # Do the hover and popup
    mouse_pos = pygame.mouse.get_pos()
    for i in range( len ( hover_rects ) ):
        if ( hover_rects[i].collidepoint( mouse_pos ) ):  # mouse inside the rect?
            if ( not popups[i].isShown() ):
                popups[i].drawAt( window, mouse_pos )
        else:
            # not inside the rect
            if ( popups[i].isShown() ):
                popups[i].unDraw( window )

    # Update the window, but not more than 60fps
    pygame.display.flip()

    # Clamp FPS
    clock.tick_busy_loop(60)


pygame.quit()