Можно ли создавать всплывающие окна в pygame, которые можно удалять/использовать, не влияя на то, что отображается на главном экране?

По сути, я хочу, чтобы когда вы наводите курсор мыши на юнит или предмет в моей игре, он отображал некоторые основные характеристики предмета / юнита (в идеале там, где находится мышь), например, если вы наводите курсор на кнопку «Пуск» в окнах, он отображает маленькое поле что говорит начало.

Есть ли способ сделать это в pygame, чтобы, когда я наводил курсор на устройство, он мог отображать эти данные на новой поверхности или уровне экрана, чтобы, когда я переставал наводить курсор, я мог просто удалить/покончить с полем статистики и восстановить то, что было за этим всплывающим окном ранее, т.е. фон/уровень/текущая битва

Я бы просто поставил поле в позиции мыши и поместил бы в него статистику, но тогда я не уверен, как мне избавиться от этого, поскольку у меня есть много предыдущих блитов, и мне потребовалось бы перерисовывать весь экран каждый раз, когда я зависаю. выключать и включать устройство, что, к сожалению, невозможно

Большое спасибо


person Data Man    schedule 14.07.2020    source источник
comment
Как ты рисуешь все в окно? На что похож цикл основного обновления? Многие программы PyGame перерисовывают все окно в каждом кадре, делает ли это ваше приложение?   -  person Kingsley    schedule 15.07.2020
comment
Я просто переношу изображения на экран и использую инструмент рисования, когда это необходимо. Это не перерисовка каждого кадра, а перерисовка только определенных областей, когда необходимы изменения или действия игрока. Многие из них выполняются и сохраняются в отдельных функциях, таких как поле битвы, журнал боя, инвентарь и т. д., поэтому охватывая наведение на все к сожалению, это нежизнеспособно, я, вероятно, должен был реализовать это в начале, но я слишком далеко продвинулся сейчас, вы можете увидеть немного отстающую версию здесь: dropbox.com/sh/fqmuzuf9hliwb1u/AAAhFdT_xx0n0F_TJQUpAXzCa?dl=0   -  person Data Man    schedule 15.07.2020


Ответы (2)


Хорошо, поскольку вы не обновляете экран каждый кадр, необходимо сохранить растровое изображение того, что будет под всплывающим окном, а затем использовать его, чтобы стереть его позже.

В 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()
person Kingsley    schedule 16.07.2020
comment
Ты бог? Шутки в сторону, большое спасибо, это было именно то, что я искал! - person Data Man; 17.07.2020
comment
@DataMan - Нет, мне просто нравится писать программы. - person Kingsley; 17.07.2020

Я не пробовал это сам, но не понимаю, почему copy() это не будет работать с поверхностью дисплея, как с любым другим экраном.

Попробуйте просто сделать копию поверхности дисплея с помощью метода Surface copy() (документы здесь), прежде чем разместить всплывающее окно, а затем, когда вы захотите перекрасить область под всплывающим окном, вы можете просто скопировать нужный раздел из копии обратно в нужное место. Это предполагает, что ничего не изменилось в области, которую вы хотите перерисовать, конечно, но это в любом случае будет проблемой (и может быть решено путем переноса изменений в копию, если она есть).

Вы можете использовать аргумент area blit() для ограничения сумма, которую вы копируете, перерисовывается с копии на дисплей.

person Glenn Mackintosh    schedule 15.07.2020