Mariorayden
Mariorayden

Reputation: 21

How to stop lag between user input and display on pygame

Im a newbie in the pygame community and python in general. For my computers assignment we have to make a video game. What Im trying to do is have a loop where it starts off by getting user input, then it takes that user input and then displays a new view based on that. Im trying to make a main menu screen where the user can press the up and down arrows to select options on the screen. However, when I run it, its very glitchy and its slow to respond to user input. Only when you hold it down it will move, but it goes all the way to the 4th option.

while Loop_2 == True:
    pygame.time.delay(75)
    #Checks what keys have been pressed
    keys = pygame.key.get_pressed()
   
    #Checks if player has pressed the exit button
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()

    #Checks if the player has pressed escape and will end python        
    if keys[pygame.K_ESCAPE]:
     pygame.quit()

    #The second main menu if statements for moving the curser and selecting
    if keys[pygame.K_DOWN] and Position < 4:
        #If the player presses the down arrow to move the pointer and its not in the 4th position at the bottom. it will move down
        Position = Position + 1
    else:
        if keys[pygame.K_UP] and Position > 1:
            #If the player presses the up arrow to move the pointer and its not in the 1st position at the top. it will move up
            Position = Position - 1
        else:
            if keys[pygame.K_LSHIFT]:
                Loop_3 = True

                if Position == 1:
                    Play_Menu()
                elif Position == 2:
                    Saves_Menu()
                elif Position == 3:
                    Settings_Menu()
                elif Position == 4:
                    Quit_Menu()
       
           
    #Loads the second main Menu
    window.blit(main_menu_2,(0,0))

    if Position == 1:
        window.blit(main_menu_pointer,(650,-525))
       
    elif Position == 2:
        window.blit(main_menu_pointer,(650,-385))
       
    elif Position == 3:
        window.blit(main_menu_pointer,(650,-240))
       
    elif Position == 4:
        window.blit(main_menu_pointer,(650,-100))
       
   
   


    pygame.display.update()

I’ve tried changing the pygame.delay to different values and it will either make it too responsive or non-responsive. I just want it so that I can press down or up and when its released it will move onto the next option, not all the way down the bottom. I cant find anything about this on this website here. Thanks

Upvotes: 2

Views: 486

Answers (1)

Kingsley
Kingsley

Reputation: 14926

The core issue is that you want to be able to cycle through menu options without it being too-fast for a normal human.

Currently the code is delaying the entire program to maintain a speed. But what is really needed here is a restriction on the time between selecting a next item.

One way of doing this is looking at the Pygame Time when the selection "event" happens. That is, at what time did the user highlight the menu item. A handy function of the time is .get_ticks(), which returns a millisecond relative time.

# user just changed selection
time_now = pygame.time.get_ticks()

This allows us to create a future-time, after which a new selection is allowed.

# user just changed selection
time_now = pygame.time.get_ticks()
next_allowed  = time_now + 300       # 300 milliseconds in the future

Now when the user attempts to change the selection, don't allow it until the current time is past this time in the future. By our example, that wont be for another 300 milliseconds (which is quite slow).

# user has tried to change selection
time_now = pygame.time.get_ticks()
if ( time_now > next_allowed ):
    # ( TODO - menu selection change code )
    next_allowed = time_now + 300   # future time next change allowed

Of course, we should set a constant for the time delay, so it's easily changed without having to hunt through the code for constants

MENU_DELAY = 300

...

next_allowed = time_now + MENU_DELAY   # future time next change allowed

Here's some example code which implements such a menu:

menu screen shot

import pygame

WIDTH = 500
HEIGHT= 500
FPS   = 60

MENU_DELAY = 200

BLACK = (  0,   0,   0)
WHITE = (255, 255, 255)
GREEN = ( 20, 180,  20)

pygame.init()
screen = pygame.display.set_mode( ( WIDTH, HEIGHT ) )
pygame.display.set_caption( "Menuosity" )

font  = pygame.font.Font( None, 32 )  # font used to make menu Items
                

class MenuItem:
    """ Simple sprite-like object representing a Menu Button """
    def __init__( self, label ):
        super().__init__()
        self.image    = pygame.Surface( ( 200, 50 ) )   # long rectangle
        self.rect     = self.image.get_rect()
        self.label    = label.strip().capitalize()
        self.selected = False
        self.fore     = BLACK
        self.back     = WHITE
        self.makeImage( self.fore, self.back )

    def makeImage( self, foreground, background ):
        """ Make the image used for the menu item, in the given colour """
        self.image.fill( background )
        pygame.draw.rect( self.image, foreground, [0, 0, self.image.get_width(), self.image.get_height()], 3 )

        # centred text for Label
        text = font.render( self.label, True, foreground )
        text_centre_rect = text.get_rect( center = self.image.get_rect().center )
        self.image.blit( text, text_centre_rect )

    def moveTo( self, x, y ):
        """ Reposition the menu item """
        self.rect.x = x
        self.rect.y = y

    def makeSelected( self, selected=True ):
        """ If the button is selected, invert it's colours """
        if ( self.selected != selected ):
            # Only re-generate if different
            if ( selected ):
                self.makeImage( self.back, self.fore )  # inverted colours on selection
            else:
                self.makeImage( self.fore, self.back )  # non-selected, normal colours
        self.selected = selected

    def draw( self, surface ):
        """ Paint the item on the given surface """
        surface.blit( self.image, self.rect )



### Create a Menu of Items
menu = []
menu.append( MenuItem( "First Item" ) )
menu.append( MenuItem( "Second Item" ) )
menu.append( MenuItem( "Third Item" ) )
menu.append( MenuItem( "Fourth Item" ) )

### Highlight the first item
current_option = 0
menu[0].makeSelected()

### Lay-out the menu
for i,item in enumerate( menu ):
    item.moveTo( 150, 50 + ( i * 80 ) )   # spread out in a single column

### Used to slow down the UI Changes
clock = pygame.time.Clock()
next_change_time = 0  

###
### MAIN
###
while True:
    time_now = pygame.time.get_ticks()   # milliseconds elapsed time 

    # Handle events
    keys = pygame.key.get_pressed()
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            exit()

    # if the user pressed [space]
    if ( keys[pygame.K_SPACE] ):
        # When space is pushed move to the next item, but not more than every 300ms
        # So, has enough time elapsed since the last keypress?
        if ( time_now > next_change_time ):
            # enough time elapsed, un-select the current, and select the next menu-item
            menu[current_option].makeSelected( False )    
            current_option += 1
            if ( current_option >= len( menu ) ):
                current_option = 0
            menu[current_option].makeSelected( True )
            next_change_time = time_now + MENU_DELAY  # remember the time of the change

    # paint the screen
    screen.fill( GREEN )  # paint background

    # Paint the menu
    for item in menu:
        item.draw( screen );

    pygame.display.flip()
    clock.tick( FPS )       # keep a sane frame-rate

Upvotes: 2

Related Questions