Custom Keyboard Navigation for Choice Menu

Discuss how to use the Ren'Py engine to create visual novels and story-based games. New releases are announced in this section.
Forum rules
This is the right place for Ren'Py help. Please ask one question per thread, use a descriptive subject like 'NotFound error in option.rpy' , and include all the relevant information - especially any relevant code and traceback messages. Use the code tag to format scripts.
Post Reply
Message
Author
User avatar
TaoistFruitbat
Newbie
Posts: 20
Joined: Wed Jun 29, 2016 12:46 am
Projects: Soulshine
Contact:

Custom Keyboard Navigation for Choice Menu

#1 Post by TaoistFruitbat »

I've been working on a text based rpg. In it the player constantly makes menu choices (such as which attack to use and which direction to move in). I would like to have robust way to navigate the menus with the keyboard, but I couldn't find any documentation for what I want to do.

Take this screenshot of the game during a battle. (I've numbered the buttons.)

Image

What I would like is
  1. At each menu we start unfocused.
  2. Enter or arrow keys focus us to button 1 if we were previously unfocused.
  3. Once focused, the right arrow moves to the next numbered button, wrapping back to button 1 when we reach the end. So 1 goes to 2, 2 goes to 3, ... , and 5 goes to 1. Same with left arrow key, except backwards.
  4. Up/down arrow keys go to the button above or below, warping around as necessary. Ex. if we are on 1 and press down we go to 4. If we are on 4 and press down we go to 1.
  5. Hotkeys: If I press key 3 on the keyboard it focuses button 3. If I press key 3 a second time we press button 3.
For reference the default Ren'py behavior is
  1. At each menu start relative to previous selection.
  2. Enter or arrow keys focus us to button 1 if we were previously unfocused. (Assuming no quickmenu/other buttons)
  3. Once focused, the right arrow moves to button to the right. If there is no button to the right it stays. (ex. right on button 3 stays at 3.) Likewise for left.
  4. Up/down arrow keys are like left and right. They go to the button above or below but do not warp around.
  5. Hotkeys: nope.
For a game where we are choosing a menu option every few seconds this default behavior breaks the flow of the game. (Point 1 is the greatest offender -- players never start where they expect on the menu.)

I've tried looking through the documentation. Ren'py has its own internal focus mechanism but I couldn't figure out how it works. I tried writing my own and disabling Ren'py's, but can't figure out how to focus on a specific button or select that button. A skeleton of my attempted solution is below.

Code: Select all

# Choice screen code

screen choice(items):

    window:
        at fadeSide # Animate Menu
        style "menu_window"
        xoffset 0
        xalign 0.5
        yalign .9

        # We use a viewport grid to get rectangular layout
        vpgrid: 
            yalign 0
            cols 3 # has three columns
            style "menu"
            spacing 15

            # We make a counter that gives each 
            # menu button a number
            $ counter = 0

            # This makes each button.
            for caption, action, chosen in items:

                if action:
                    $ counter += 1

                    button:
                        tag counter # <- make the counter number the tag 
                                    # (As is this makes 'counter' the tag, not the number???
                                    # idk how to make it evaluate counter and make that the tag.)
                        action action
                        style "menu_choice_button"

                        text caption style "menu_choice"

                # I don't know that this is for...
                else:
                    text caption style "menu_caption"
In essence we (somehow) tag the buttons with a number so we can get the correct buttons in this later piece of machinery.

Code: Select all

# Snippet of a potential way of coding this up. Functions left out are similar.

# This screen overides the arrow keys to run custom functions on them???
screen keymapper:
    key "K_LEFT" action menu_left
    key "K_RIGHT" action menu_right
    key "K_UP"  action menu_up
    key "K_DOWN" action menu_down
    key "K_1" action menu_one
    # .
    # .
    # .
    key "K_6" action menu_six

# At some point when the game starts
show keymapper

init -3 python:

    # This navigates the menus
    class Menu_Navigator(object):

        def __init__(self):
            # The 0 focus means unfocused.
            self.focus = 0

            # The number of buttons being displayed
            self.button_number = 1

        # Reset focus when new menu appears
        def reset(self):
            self.focus = 0
            self.button_number = somehow_get_the_number_of_buttons_being_displayed()

        # When we press right arrow key.
        def menu_right(self):
            # Modulo to warp around
            self.focus = (self.focus + 1) % (self.button_number + 1) 

            # Never allow button_number to return to 0. 
            # Instead jump to 1
            if self.focus == 0:
                self.focus = 1

            # Focus on the correct button
            somehow_make_renpy_focus_on_the_menu_button_with_the_same_tag_as_focus_variable()

        # When we try to select the sixth menu option.
        # IDK HOW TO DO MOST OF THIS D:
        def menu_six(self):

            somehow_see_if_menu_button_6_currently_exists()

            if button_6_totally_does_exist and button_6_is_not_focused:
                somehow_focus_on_button_6()
            elif button_6_totally_does_exist and button_6_is_focused:
                somehow_press_button_6()
            # If button 6 does not exist do nothing.
            else: 
                pass
I don't know if there is a cleaner way to do the above, but if somebody could help me with this I would love you lots.

User avatar
xavimat
Eileen-Class Veteran
Posts: 1461
Joined: Sat Feb 25, 2012 8:45 pm
Completed: Yeshua, Jesus Life, Cops&Robbers
Projects: Fear&Love
Organization: Pilgrim Creations
Github: xavi-mat
itch: pilgrimcreations
Location: Spain
Discord: xavimat
Contact:

Re: Custom Keyboard Navigation for Choice Menu

#2 Post by xavimat »

I can only give some simple ideas. Not the full required behaviour (it seems pretty difficult for me).
First: I don't get why are you using the choice screen and not creating your own screen.

- I suggest to use the "default True" property to the first button. So the player knows where the focus starts. Doc: https://www.renpy.org/doc/html/screens. ... statements
- I suggest also to define the hotkeys with the same action than the button (Why do you want the hotkey to be pressed twice?, maybe it gives some info when focused but not pressed?)

Code: Select all

label start:
    while True:
        call screen combat
        $ r = _return
        "[r]"

screen combat():

    vbox:
        align (.5, .8)
        hbox:
            textbutton "1 Light" action Return(1) default True
            textbutton "2 Heavy" action Return(2)
            textbutton "3 Denotation" action Return(3)
        hbox:
            textbutton "4 Blast" action Return(4)
            textbutton "5 Heal" action Return(5)

    key "1" action Return(1)
    key "2" action Return(2)
    key "3" action Return(3)
    key "4" action Return(4)
    key "5" action Return(5)
Comunidad Ren'Py en español: ¡Únete a nuestro Discord!
Rhaier Kingdom A Ren'Py Multiplayer Adventure Visual Novel.
Cops&Robbers A two-player experiment | Fear&Love Why can't we say I love you?
Honest Critique (Avatar made with Chibi Maker by ~gen8)

User avatar
xavimat
Eileen-Class Veteran
Posts: 1461
Joined: Sat Feb 25, 2012 8:45 pm
Completed: Yeshua, Jesus Life, Cops&Robbers
Projects: Fear&Love
Organization: Pilgrim Creations
Github: xavi-mat
itch: pilgrimcreations
Location: Spain
Discord: xavimat
Contact:

Re: Custom Keyboard Navigation for Choice Menu

#3 Post by xavimat »

Adding number hotkeys to the normal menu:

Code: Select all

screen choice(items):

    window:
        style "menu_window"
        xalign 0.5
        yalign 0.5

        vbox:
            style "menu"
            spacing 2

            $ the_key = 0    ## <---- One line here

            for caption, action, chosen in items:

                $ the_key += 1    ## <---- Another line here

                if action:

                    button:
                        action action
                        style "menu_choice_button"

                        # text caption style "menu_choice"  ## <-- Original line
                        text "[the_key]. " + caption style "menu_choice"  ## <-- Changed line

                    key str(the_key) action action   ## <---- And the fourth here

                else:
                    text caption style "menu_caption"
Comunidad Ren'Py en español: ¡Únete a nuestro Discord!
Rhaier Kingdom A Ren'Py Multiplayer Adventure Visual Novel.
Cops&Robbers A two-player experiment | Fear&Love Why can't we say I love you?
Honest Critique (Avatar made with Chibi Maker by ~gen8)

User avatar
TaoistFruitbat
Newbie
Posts: 20
Joined: Wed Jun 29, 2016 12:46 am
Projects: Soulshine
Contact:

Re: Custom Keyboard Navigation for Choice Menu

#4 Post by TaoistFruitbat »

Thanks for the suggestions! The first one works flawlessly. The second one... it ends up doing some fun stuff.

Image

The hotkeys work, but the numbering is off. Sometimes we'll even have two 9's appearing (probably because Ren'py wants to assign the non-existent key number 12 to a button, so I'm forcing keynumbers to max out at 9.) I don't know what is doing this. Maybe the Ren'py screen prediction?
(Why do you want the hotkey to be pressed twice?, maybe it gives some info when focused but not pressed?)
That's exactly right. Eventually a tool-tip will pop up with details for the focused button. (Eh, maybe. It's a design decision that I'll implement and test.)
I don't get why are you using the choice screen and not creating your own screen.
Because I want these controls to work for the entire game, not just combat. They will be universal controls for walking around, talking to people, answering questions, etc.

If I create my own screen then I'll need to make all menus through that screen, which would mean forsaking the very convenient renpy menu notation. I'll have to make my own system to easily create menus with and send that information to my custom screen to display. It's a lot of reinventing the wheel -- easier to just adapt the existing menu system.

I think the navigation system is easy enough to make if I could only
  1. label the buttons correctly
  2. could focus on a button via label
  3. could select a button via label
I'm looking through the source code trying to figure this out, but my lack of CS background is showing. :/

User avatar
xavimat
Eileen-Class Veteran
Posts: 1461
Joined: Sat Feb 25, 2012 8:45 pm
Completed: Yeshua, Jesus Life, Cops&Robbers
Projects: Fear&Love
Organization: Pilgrim Creations
Github: xavi-mat
itch: pilgrimcreations
Location: Spain
Discord: xavimat
Contact:

Re: Custom Keyboard Navigation for Choice Menu

#5 Post by xavimat »

I still think that a custom made screen could be easier than looking at the renpy code.
Something like this (sorry about the messy code, I,m on tje phone now):

Code: Select all

screen mychoice(items):
    $ ky = 0
    vbox:
        for i in items:
            ky += 1
            textbutton i action Return(i)
            key str(ky) action Return(i)

label some_chapter:
    "Where to go now?"
    call screen mychoice(["up", "down"])
    $ result = _return
Comunidad Ren'Py en español: ¡Únete a nuestro Discord!
Rhaier Kingdom A Ren'Py Multiplayer Adventure Visual Novel.
Cops&Robbers A two-player experiment | Fear&Love Why can't we say I love you?
Honest Critique (Avatar made with Chibi Maker by ~gen8)

User avatar
xavimat
Eileen-Class Veteran
Posts: 1461
Joined: Sat Feb 25, 2012 8:45 pm
Completed: Yeshua, Jesus Life, Cops&Robbers
Projects: Fear&Love
Organization: Pilgrim Creations
Github: xavi-mat
itch: pilgrimcreations
Location: Spain
Discord: xavimat
Contact:

Re: Custom Keyboard Navigation for Choice Menu

#6 Post by xavimat »

Btw look at the menu variable in the doc. Maybe it's possible to change every menu to use your custom screen instead of the default "choice"
Comunidad Ren'Py en español: ¡Únete a nuestro Discord!
Rhaier Kingdom A Ren'Py Multiplayer Adventure Visual Novel.
Cops&Robbers A two-player experiment | Fear&Love Why can't we say I love you?
Honest Critique (Avatar made with Chibi Maker by ~gen8)

User avatar
TaoistFruitbat
Newbie
Posts: 20
Joined: Wed Jun 29, 2016 12:46 am
Projects: Soulshine
Contact:

Re: Custom Keyboard Navigation for Choice Menu

#7 Post by TaoistFruitbat »

You're right about the menu variable. The following works.

Code: Select all

screen custom_choice(items):

    window:

        # do menu stuff

        .
        .
        .
    

init -2 python:

    # Function for custom choice screen
    def custom_choice(items, **kwargs):
        return renpy.display_menu(items, interact=True, screen='custom_choice')

# Run this to change menu to custom choice.
# It makes the menu use the custom_choice function to display.
menu = custom_choice
After reflecting on the game design I realized that I don't want the focus-wrapping or the two press hotkeys. But instant hotkeys and maybe default to unfocus would still be nice.

Hotkeys: I think I can get these working with the code you gave me. key str(hotkey) action b_action messes up the layout of vpgrid, but it should be easy enough to write a replacement grid-like screen. As for the incorrect numbering... turns out I was incrementing the counter twice. :p

Defaulting to unfocus: I'm thinking I'll make hidden/invisible button right next to button 1 and set it as the default focus. (Is it possible to make a button invisible but still focusable? It's that or hide it behind a background.)

I'm traveling for the next week or two, but after that I'll update with more questions or, if we're lucky, a solution.

User avatar
TaoistFruitbat
Newbie
Posts: 20
Joined: Wed Jun 29, 2016 12:46 am
Projects: Soulshine
Contact:

Re: Custom Keyboard Navigation for Choice Menu

#8 Post by TaoistFruitbat »

Okay, so I got the hotkeys working. Adding the hotkeys caused layout problems with grid and vpgrid because they see key str(b_num) action b_action as its own screen to be put in the grid, giving us twice as many screens in the grid as we want. There's no easy way to fix this so I made my own grid.

I'm still having trouble with the focus though. Is there any command to force a specific button to focus? That would solve most of my problems immediately. Actually, that command would allow me insane amounts of flexibility in controls and let me easily test out a bunch of ideas I have.

Code: Select all

# =======================
# Custom Choice Screen
# =======================
# This controls the menu that the player selects pretty
# much everything with. 
# This file contains button layout, animation, and menu
# logic code. Layout code is found in options.rpy


init 1:


    # --- Button layout code --- # -----------------------
    python:

        b_width = int(menu_width * 0.3)
        b_height = int(menu_height * 0.3)

        x_padding = (menu_width - 3*b_width) / 4 
        y_padding = (menu_height - 2*b_height) / 3
   
        config.narrator_menu = True

        # This returns the position the button should 
        # be, assuming a 3x2 grid.
        def b_position(b_num):
            # First Row
            if b_num <= 3:
                xpos = sidebar_width + b_width * (b_num-1) + (b_num * x_padding)
                ypos = textbox_height + y_padding
            # Second Row
            elif b_num > 3:
                xpos =  sidebar_width + b_width * (b_num-4) + (b_num - 3)*x_padding
                ypos = textbox_height + b_height + y_padding * 2
            # If > 6 or error, stick it in the corner
            else:
                xpos = 0
                ypos = 0
            pos = (int(xpos), int(ypos))
            return pos

    style menu_window is default

    style menu_choice is button_text:
        clear

    # Changes the size of the buttons
    style menu_choice_button is button:
        xminimum int(b_width)
        xmaximum int(b_width)
        yminimum int(b_height)
        ymaximum int(b_height)

    # The invisible focus button.
    style focus_button is button:
        xminimum int(b_width / 4)
        xmaximum int(b_width / 4)
        yminimum int(b_height / 2)
        ymaximum int(b_height / 2)


# --- CHOICE --- # ---------------------------------
# A custom grid to hold the choice menu.
# (vpgrid doesn't work with hotkeys because 'key' is
# considered a screen and is added to the grid. )

screen choice(items):

    window:
        style "menu_window"

        # --- Button Logic --- # -------------------------
        
        $ b_num = 0 # The counter

        for i in items:

            python:
                # Get the button text and action
                b_text = i[0]
                b_action = i[1]
                # See if button has action
                b_has_action = True # <- todo: if this is false we want the button grayed out
                                    # It will be false if it has the 'unselectable' action. 
                                    # (Or something.)
                # Increase b_num counter
                b_num += 1

            if b_has_action:
                button:
                    # Animates and positions the button
                    at fadeSide(b_num, b_position(b_num))
                    # The action the button runs when pressed
                    action b_action
                    # Style of the button
                    style "menu_choice_button"
                    # Text of the button
                    text "[b_num]. " + b_text style "menu_choice"

                # Allow selection via b_num
                key str(b_num) action b_action

            # If there is no action, grey out the button
            # todo.
            else:
                text caption style "menu_caption"

        # --- Focus Button --- # ------------------------------
        # This makes an invisible button on top of button 1.
        # The menu always defaults the focus to it.
        # It deletes itself when it looses focus. #(todo last parts.)


        # --- Default doesn't work in this format? Possible bug with renpy code??? --- #
        # button:    
        #     style "focus_button"
        #     pos(b_position(1)[0] - 40, b_position(1)[1] - 40) # At position of button 1 or something
        #     action do_nothing
        #     default True # THIS GIVES ERRORS

        # --- This format does work however --- #
        button pos(b_position(1)[0] - 40, b_position(1)[1] - 40) style "focus_button" action do_nothing default True
        # But it does not change the focus in the way I want. If I focus on button 1 and press enter, then
        # the next screen is still focused on button 1, not the focus button.


# ---------- ANIMATIONS --------- # -------------------------------------

transform fadeSide(b_num, b_pos):
    subpixel True
    alpha 0.0 # Starting Opacity
    pos (b_pos[0] + b_width * b_num/2, b_pos[1]) # Starting Position 
    parallel:
        easein_back 0.4 alpha 1.0 # Fade time and ending opacity
    parallel:
        easein_back 0.4 + .05 * b_num pos b_pos  # Slide Time and ending position
Layout code is hanging out in options.rpy.
(Side question: when I remove this code from options.rpy and place it in another file the values for sidebar and others are screwed up. It is almost as if config.screen_height gets a different value in this part of the code, and then goes to the correct value when playing the game. Any idea why?)

Code: Select all

init python:

    #--- UI Variables --- # -----------------------

    # Golden Ratio Constants.
    # (# We base the ui partitioning on the golding ratio so that
    # everybody thinks we are fancier than we really are.)
    phi = 1.618
    Phi = .618 # The conjugate

    sidebar_width = int((config.screen_height * Phi)/2)
    sidebar_height = config.screen_height


    textbox_height = int(config.screen_height * Phi)
    menu_width = int(config.screen_width - sidebar_width * 2)
    menu_height = config.screen_height - textbox_height

    # --- Textbox --- # --------------------------

    style.window.left_margin = 0
    style.window.right_margin = 0
    style.window.top_margin = 0
    style.window.bottom_margin = 0

    # Puts textbox at top of screen
    #style.window.ypos = 100 #config.screen_height 

    ## Padding is space inside the window, where the background is
    ## drawn.

    style.window.left_padding = 40
    style.window.right_padding = 40
    style.window.top_padding = 40
    style.window.bottom_padding = 40

    ## This is the minimum height of the window, including the margins
    ## and padding.

    style.window.yminimum = 0


init -1 python hide:

     # Rest of options code here...
I really appreciate the help here. I know menu control may be a small detail, but my players interact with the menu every few seconds. Good controls may make or break the game.

Post Reply

Who is online

Users browsing this forum: Google [Bot]