With something as large as the Controller Support Expansion for Ren’Py, it can be difficult to find the right tool to do what you need in your game. This page is designed to help you find the right resources to solve whatever problem you have. Pick up the tool from itch.io if you haven’t already:
Sometimes Ren’Py will not co-operate when you want to focus a particular button. Maybe it’s skipping over a button in a vbox, or it’s hard to see which button is currently focused.
If you’re having issues with focus skipping over buttons in a grid or an hbox/vbox, some of the following solutions may work:
spacing
First and most basic: negative spacing usually has focus issues. If it’s possible to set the spacing of your box or grid to a positive number, often focus skipping issues will go away.
keyboard_focus_insets
A property found in v8.3+ of Ren’Py. Documentation here. This can be used like:
vbox:
textbutton _("Start") action Start():
keyboard_focus_insets (0, 10, 0, 10)
textbutton _("Load") action ShowMenu("load"):
keyboard_focus_insets (0, 10, 0, 10)
It will reduce the “hitbox” of the button so they aren’t overlapping in Ren’Py’s internal focus calculations (overlapping buttons tend to be skipped when it comes to focusing another button). In the above example, it shrinks the hitbox by 10 pixels at the top and 10 pixels at the bottom (it goes (left, top, right, bottom)
in clockwise order, like Frame
or padding
).
penalty
property ofcontroller_viewport
If you’re using a Controller Viewport, you can make the penalty
property smaller to reduce how much items to the left/right of items in a column, or above/below items in a row, are penalized when Ren’Py is figuring out what to focus next. The default penalty value is 1024
, so try something smaller like 128
.
- Turn cache_reverse off
If your layout is not symmetrical, you may be having focus issues because of the cache_reverse
property of Controller Viewport. Set cache_reverse False
to turn this property off.
If you’re having trouble with specific button relationships, you can use the KeyController class (and its screen language version, focused_on
) to tell Ren’Py exactly which button should be focused when certain directions are pressed.
Depending on what’s in your screen, a virtual cursor may work best for keyboard and controller users. This is especially helpful for unusual layouts like flow charts or point-and-click investigation.
Issue: Directions focusing the wrong thing
Another common issue is something like “if I hit left
while focused on the “Schedule” button, it should select the “Inventory” button”. If these two buttons aren’t exactly aligned in something like a grid or hbox, Ren’Py may have trouble figuring out the relationship between them.
In this case, you can use the focused_on
screen element to declare these relationships (see KeyController and focused_on). Otherwise, the Virtual Cursor may also be helpful. See the previous section on skipping over buttons if your buttons are in a container like a grid or vbox/hbox.
Issue: Focusing something I don't want focused
If you’ve got a button or bar that the player shouldn’t be able to navigate to with the keyboard or controller at all, use keyboard_focus False
on that element (see the docs here). This is often the case for scrollbars, and sometimes for buttons that can be focused with the mouse but which have a controller or keyboard shortcut.
If you’re using the Controller Viewport, you might need the trap_focus property to prevent focus from leaving the viewport, or the aborb_events property to make sure focus events aren’t passed to the rest of the UI.
You can also use focused_on
to specify focus relationships between buttons, or make sure that focus doesn’t leave a button in a particular direction. See KeyController and focused_on. Note that you can also use focused_on
to “absorb” an action e.g.
focused_on "desk" key "focus_right" action NullAction()
This means that when right
is pressed while focused on the button with the ID “desk”, nothing will happen (NullAction).
Issue: Viewport not scrolling
First, make sure you’re using the Controller Viewport. Regular viewports don’t scroll when focusing the children inside them, or scroll with the controller sticks.
Second, if your viewport has children inside them that you’re trying to focus so that the viewport scrolls, make sure the vscroll_style
and/or hscroll_style
are set to something besides None
. None
is the default, which means the viewport will not scroll to reposition itself if a child is focused. vscroll_style "center"
and hscroll_style "center"
are usually a good value to start with.
Otherwise, if your viewport doesn’t have anything in it but you want to use the controller sticks to scroll it, make sure to set which_stick
to one of “left”, “right”, or “both”. Otherwise the sticks will not move it! You can also set arrowkeys
to "not sticks"
or "keyboard"
to make it scroll with the keyboard + dpad or just the keyboard, respectively. This is often helpful in combination with which_stick
so keyboard users can scroll as well.
Viewports
How do I make a viewport scroll with the controller sticks/How do I make a viewport scroll when I focus things inside it?
To make a viewport scroll with the controller sticks, you’ll use the Controller Viewport. If the viewport has buttons and other content in it that you can focus, you’ll need to set the vscroll_style
and/or hscroll_style
properties to something besides None so the viewport will scroll while you navigate it. If the viewport doesn’t have focusable content inside it (e.g. it’s like the History Log viewport), then set which_stick
to “left”, “right”, or “both” so that stick can scroll the viewport. You may also want to set focus_scroll False
.
User Interface (UI)
How do I change the UI for controllers?
By default, the pad_config.INPUT_TYPE_CALLBACKS
(see Configuration Variables) callbacks include a function which will refresh the screen when the input type changes to/from a controller. This means you can wrap parts of your screen with a check for pad_config.is_using_controller() e.g.
screen quick_menu():
## Ensure this appears on top of other screens.
zorder 100
if pad_config.is_using_controller():
if quick_menu:
use quick_menu_controller()
elif quick_menu:
hbox:
style_prefix "quick"
textbutton _("Back") action Rollback()
textbutton _("History") action ShowMenu('history')
textbutton _("Skip") action Skip() alternate Skip(fast=True, confirm=True)
textbutton _("Auto") action Preference("auto-forward", "toggle")
textbutton _("Save") action ShowMenu('save')
textbutton _("Prefs") action ShowMenu('pref_display')
Here, you can see that there’s a check for pad_config.is_using_controller()
, which then uses a different screen for the quick menu.
You might also consider taking advantage of Ren’Py’s variant
system – see Screen Variants. Of particular note for controller layouts is the "steam_deck"
variant.
You can also read up on Controller and Keyboard Icons, which are set up to respond to the currently used input type as well.
How do I show controller icons?
To show controller icons, you’ll usually use the controller_icon
displayable, which you can read more about here: Controller and Keyboard Icons. You may also use the functions pad_config.get_icons or pad_config.get_inline_icons to get the icons associated with a particular event.
How do I change controller icon sets?
By default, all of the icons used for controllers will automatically change when the controller layout changes, so you don’t have to write any kind of if/else statement for it. To change which layout is used, however, you’ll typically use the CycleControllerLayout or SetControllerLayout actions.
Virtual Cursor
How do I use a controller/the arrow keys to control a cursor?
To move around a virtual cursor with the controller sticks or keyboard arrow keys, you’ll use the Virtual Cursor.
How do I make the virtual cursor move to a particular location?
You’ll use the MoveVirtualCursor action. If you want the physical cursor to move somewhere too, you may want to use this in a list with MouseMove.
Other
How do pause the game when a controller is disconnected?
Some platforms like Steam want your game to pause when a controller is disconnected. To do this, you’ll use the pad_config.CONTROLLER_CONNECT_CALLBACKS
and pad_config.CONTROLLER_DISCONNECT_CALLBACKS
(see Callbacks on the Configuration Variables page). In the default template, these are set to functions which will temporarily pause the game with a message that the controller has been disconnected, and dismiss said message if the controller is reconnected.