Skip to main content

Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
TagsGame Engines

Controller Support Expansion for Ren'Py

Comprehensive set of features to improve controller support in Ren'Py. · By Feniks

Need Support? Post here! Sticky

A topic by Feniks created Feb 09, 2025 Views: 964 Replies: 32
Viewing posts 1 to 11
Developer (1 edit)

If you're having trouble or run into any bugs with the code, post a comment here! I will try to get back to you within a few days. Be sure to check the Controller Support Expansion section on my website too for documentation.

So I finally got around to testing this out! Unfortunately, I immediately ran into a crash:

I'm sorry, but an uncaught exception occurred.
While running game code:
  File "game/script/system/plugins/controller_support/controller_config.rpy", line 313, in script
    init 999 python in pad_config:
  File "game/script/system/plugins/controller_support/controller_config.rpy", line 313, in script
    init 999 python in pad_config:
  File "game/script/system/plugins/controller_support/controller_config.rpy", line 316, in <module>
    FOCUS_MANAGERS = [FocusManager(x) for x in RESTORE_FOCUS_SCREENS]
  File "game/script/system/plugins/controller_support/controller_config.rpy", line 316, in <lambda>
    FOCUS_MANAGERS = [FocusManager(x) for x in RESTORE_FOCUS_SCREENS]
  File "game/script/system/plugins/controller_support/controller_config.rpy", line 316, in <listcomp>
    FOCUS_MANAGERS = [FocusManager(x) for x in RESTORE_FOCUS_SCREENS]
  File "game/script/system/plugins/controller_support/controller_functions.rpy", line 339, in __init__
    self.is_showing = renpy.get_screen(self.screen)
Exception: Unknown layer 'game_menu'.

I'm using a custom layer for my in-game menus (including the main menu), and that seems to be causing a problem.

Developer

Ooh I see! Yes I had not accounted for that. For now, can you see if this change to the FocusManager class in controller_functions.rpy will fix it?

def __init__(self, screen, layer=None):
    if isinstance(screen, tuple):
        self.screen = screen[0]
        self.layer = screen[1]
    else:
        self.screen = screen
        self.layer = layer
    self.is_showing = renpy.get_screen(self.screen)
    self.displayable_lookup = dict()
    self.refresh_triggered = False
    super(FocusManager, self).__init__()

And then adjust `RESTORE_FOCUS_SCREENS` to be a list of pairs like ("game_menu", "MY_GAME_MENU_LAYER") e.g.

define pad_config.RESTORE_FOCUS_SCREENS = [ ("main_menu", "my_menu_layer"), ("game_menu", "my_menu_layer"), "controller_remap" ]

I'll run some more tests and such, but try that for now. That, or if you're not using a game_menu screen/standalone like the template does, just remove "game_menu" from RESTORE_FOCUS_SCREENS altogether.

(1 edit)

Sadly, that didn't change anything. It still throws the same error (just with different line numbers, due to the few that were added).

I can only get the game to boot up by removing the "main_menu" AND "game_menu" part from RESTORE_FOCUS_SCREENS.

It also works if I remove [layer "game_menu"] from my "main_menu" screen  and do:

define pad_config.RESTORE_FOCUS_SCREENS = [ ("main_menu", "game_menu"), "controller_remap" ]

So it seems like it doesn't really care what I put in the tuple?

Developer

If your layer is called `game_menu`, renpy.get_screen is interpreted first as a tag and then as a screen, so regardless it wouldn't work with a screen *and* a layer called game_menu. So, I recommend either changing the tag name or the screen name so they're different if you want to use the save-and-restore focus features for the in-game menu screen. Otherwise, it's fine to just not have that screen in the RESTORE_FOCUS_SCREENS.

Hmm, the crash still occurs even after renaming my "game_menu" layer to "menus". But I'll keep testing with just 

pad_config.RESTORE_FOCUS_SCREENS = [ "controller_remap" ] 

for now. Thank you!

Hi, was looking at the demo template. Turning on self voicing when any of the custom preference screens open gave this if it detects mouse currently being used. Didn't happen when using keyboard/roller though.

I'm sorry, but an uncaught exception occurred.
While running game code:
TypeError: 'dict_values' object is not subscriptable
Developer(+1)

Huh, I had not noticed this! You are correct. The solution is to update the visit method in 01_controller_cdsl.rpy:

def visit(self):
    return list(self.displayables.values())

to wrap the return value in a list. I'll add this to the next fix release. Thanks!

Hi, is there any way I can disable the reset-to-middle aspect of the virtual cursor?

Developer

What kind of functionality are you looking to replace it with? Are you referring to how the cursor begins in the center of the screen, or the snap-to-center feature?

I thought it'd be nice to have the cursor available during regular dialogue (to click buttons in the quick menu with the cursor), but every time you advance the dialogue, the cursor resets back to the center of the screen.

Not a huge deal at all, just something I'm curious about.

Developer

How have you declared the cursor/added it to the screen?

Hi! First, I'd like to say your expansion is incredible! It's exactly what I needed, so thank you :-) Second, I had a quick question about the virtual cursor.  I may be overlooking something obvious (if so, my bad!!!), but I'd like the cursor to 'remember' its position if that makes sense. Rather than return to its predetermined position after pressing a button, I'd like it to stay put - basically updating its position to wherever the player pressed. I played around with the code for a little while but I can't seem to figure it out! Any suggestions? Thanks in advance for the help!

Developer

Hey there! The cursor should already stay put by default - do you have an example of code where you're seeing this behaviour?

Sorry for the slow reply! The code you've created is completely untouched - I reverted all the changes after messing around (even replaced the 01_virtual_cursor.rpy file with an untouched version just to make extra sure). The place I'm seeing this behavior in-game is during an investigation segment which uses image buttons inside a screen. This code is in screens.rpy and it looks like this:

I'll then call the screen in-game and use a series of labels to jump to each event like this...which I'm just now realizing is exactly my problem!!! When the event jumps back to the broom_nav label, the screen is called again, resetting the position of the cursor. 

Clearly there's a better way to code this that I didn't come up with the first time around (I'm self taught okay...) so I'll look into that! If you have any pointers you'd like to share, I'd really appreciate it. Otherwise, no pressure! You've helped me enough by making the perfect expansion :-)

Developer

No worries! I will look into adding a built-in feature so that the screen language version can save its position between being shown - there is already a property to indicate the starting position, so the only addition would be to save the current position somewhere so that it can be re-applied when the screen is shown again. In the meantime however, you can declare the cursor in Python with default, which will save it! e.g.

default vc = VirtualCursor(cursor=(Transform("gui/window_icon.png", xysize=(50, 50)), 0, 0))
screen investigation():
    vbox:
        spacing 100 align (0.5, 0.9)
        textbutton _("Hello") action Notify("world")
        textbutton _("Jump") action Jump("bedroom")
    add vc

The `add vc` bit adds the cursor to the screen, just like the `virtual_cursor` line. You can add whichever properties you like in the vc declaration e.g. `VirtualCursor(cursor=<...>, snap_to_center=True, which_stick="left")`

Hope that helps!

Wow, this works perfectly! Thank you for taking the time to look into this - I really do appreciate it! 

(+1)

Hi, thanks so much for creating such a great expansion. 

Unfortunatly I ran into a few issues. When using the ESC key on keyboard, or the little menu button on my ps5 controller a bug appears that it hadn't before.  "An exception has occured. While running game code: File "renpy/common/00gamemenu.rpy" line 174 in script $ui.interact() TypeError: missing a required argument 'title'" 

This immediatly pops up if one of those buttons is pressed, and while I didn't check the controller without the expansion, I remember the ESC worked normally before . 

Then, whenever I mess up code (which is a lot lol), instead of renpy showing me where I messed up, everything is closed down and this is shown in my code editor: "I'm sorry, an uncaught exception has occured. After loading the script. File "game/backend/backend/controller_support/controller_override.rpy", line 196, in my_controller_event    NameError: name 'CONTROLLERDEVICEADDED' is not defined" Not sure if that's normal? 

And then I have a question regarding putting a controller_viewport   in the game menu screen. It seems like when I replace the normal viewport with the controller one there *is* a functioning scrollbar in all the game menus,... however you can't visually see it. I narrowed down the deciding component being "side_yfill True", without it even the normal viewport won't show the scrollbar, however controller viewport doesn't seem to support that property, and normal yfill True does nothing. 

Oops, I realize this was a whole wall of text, but I hope you can still help me out. Thank you so much in advance.

Developer

No worries! This is something I'm looking to update the files to make it a bit easier to understand - by default, the controller pack sets the game_menu screen to be opened when you hit the game_menu shortcut button (e.g. Start/ESC/right click). In the template, this is made possible by setting `title` as a keyword argument rather than a required argument, hence the error complaining about requiring the title argument.

You can remove or adjust this line `_game_menu_screen = "game_menu"` and/or adjust `screen game_menu` to have `title=""` instead of just `title` as an argument.

As for the error, you can put this into the my_controller_event function:

try:
    cda = CONTROLLERDEVICEADDED
except NameError:
    ## Probably reporting some other error
    return rv

put it just above the `if ev.type == CONTROLLERDEVICEADDED:`

To be fair this may not solve all problems with error reporting - the main issue is just that Ren'Py tries to make an error message and skips over various other steps like declarations, which means it then gets caught on otherwise normal parts of the code. So that check is an attempt to ignore that particular problem if it arises, but it's possible it'll try throwing an error somewhere else instead.

And for the viewports, `controller_viewport` does not handle `side_` properties or automatically adding scrollbars unfortunately, so you'll need to explicitly set that up/add it instead. Notably, the following are equivalent (aside from the controller_viewport bit being a controller-compatible viewport obviously):

viewport:
    scrollbars "vertical"
    side_yfill True
side 'c r':
    yfill True
    controller_viewport:
        id 'whatever'
    vbar value YScrollValue("whatever")
Hope that helps!
(+1)

Thank you so much  for the help! I really appreciate it.

Just as a side (ha) note in case ppl in the future read this: side 'c r' gave me real trouble until I substituted the quotes with double quotes like this : side "c r".  The rest is correct and was an awesome help. My issues are pretty much gone now, thx again. :D 

Developer

Hmm, are you certain that was the cause? Double and single quotes are interchangeable in Ren'Py in most cases for denoting strings, and I pasted the code into script and didn't have any problems running it. If you got an error, feel free to post it!

Happy to hear your other problems were resolved as well ✨

might be THE most oddly specific thing to ask in the world BUT. is it at all possible to automatically center a button inside the viewport when its clicked? i have a scrolling hbox for a menu within a controller_viewport and it has a series of imagebuttons that each open a screen that's centered in the screen, giving the illusion that its simply the initial button changing appearance when clicked. however i forgot to account for mouse options, and that means that when you click one of the imagebuttons when theyre just off in the corner with a mouse, the screen that appears is overlaid over the incorrect imagebutton. was wondering if its possible to have the viewport automatically scroll to the imagebutton when its clicked if its just off to the side, basically.

and another question. i wanted to have some imagebuttons that could be used to scroll to the next item in the list, but im unable to figure out that too. sorry for the long ramble

Developer

The first one is really out of the scope of what I can help with for this tool. You can potentially look into nearrect for positioning the new screen appropriately, or style your buttons instead of opening a new screen.

For your second question, you can use buttons that have the Scroll action, if I understood you. Generally if they're using the mouse though, it won't make sense to have buttons which set focus elsewhere. You might be better served having a variable keep track of what button is "selected" and style it based off of selectedness rather than hover state. Then you can execute the appropriate button action based on which button is selected.

ah thank you so much!

(1 edit)

Hello! I've been trying out the controller system, and I'm currently developing some minigames for my VN, currently the buttons for my minigames were pygame-mapped keys. I tried to use the add_custom_event in order to map my game to new custom events but I ran into some issues. Is there a way to isolate events for a specific screen? 
Supposedly, my events should be categorized as in-game. The problem is that anything I map to it will remove it from another in-game action (let's say my walk action or hit action will delete confirm or advance dialogue default button mapping). And if I manage to get them to work by using the "always" category instead of the "in-game" for my custom event, when I run my game screen, it will do both the in-game action and skip over the always action. 
For this specific screen, I dont really need any of the scroll forward, auto, or any of the in-game default events, as my screen is used for the minigame only (we dont save, reload, auto fordward or do any other Ren'Py action that is not a custom action for the minigame.) Once my game is over, my screen disappears and returns to the normal dialogue screen.

Developer

If anything, this sounds like it would be a "situational"-category event, given that it's only active during minigames. You're also welcome to add your own categories and define their compatibilities when you add new events. Or, if you don't need your minigame keys to be remappable, it's fine to just hardcode it with pygame events.

Apologies for another odd question, but this one has really stumped me.

I have an imagebutton, or rather a series of them, within a horizontal controller_viewport. I wanted to make them show/hide using a transition (an ATL one, but after further testing, this applies to preset transitions as well), but the transition doesn't seem to play. On being clicked, the button either immediately appears or immediately disappears, as if the animation is being skipped.

I also tested the imagebuttons outside of the viewport as seperate screens, and the animation plays just fine then, which leads me to believe this issue is symptomatic of the button being within the aforementioned viewport. This may be a general issue with viewports, and not exclusive to controller_viewports specifically, but I thought it was worth mentioning in case you, or anyone else, may be aware of a workaround.

Thanks!

Developer

Have you tried this with a general viewport? Ren'Py doesn't pass ATL events to displayables inside containers, so that may be what you're seeing. Otherwise, if it works with a regular viewport and not the controller viewport, if you can make me a short reproducible code section I'll look into debugging it.

(1 edit)

after just testing with a non-controller viewport, the atl transitions actually DID work to my surprise! so this might actually be an issue with controller support expansion! I've made a Google Drive folder with both the classic viewport and controller viewport versions of my full chapterselect script. figured that a more in-depth look might help better for debugging than a short extract. there's also a folder containing all of the necessary image assets, so that should help as well.

SMALL UPDATE ON THIS! 

I've found that this bug ALSO applies to tooltips. For example, if you have a button in a controller viewport, and that button uses both an ATL transform and a tooltip, the tooltip will override the ATL for some reason, and cause it to (again, the same with the previous problem) immediately complete the animation. It seems to skip over the ease/linear arguments specifically. 

I also made sure to test this without the tooltip, in which it worked fine, and with the tooltip in a regular viewport, and that, too, works fine. Not sure if this will be helpful to your debugging process or not, but I thought it to be worth mentioning.

I have a question regarding the ongoing port of Renpy to SDL3 (See Renpy Dev update for February 2026). With this expansion being based on SDL2, do you think it's likely that it won't work on Renpy 8.6 and beyond? Even with Renpy 8.5 pygame_sdl2 was removed. Is that of relevance for this project? Can you recommend a version of Renpy that will definitely work with it? Thanks you in advance!

Developer(+1)

I'm not really concerned about this, no. Ren'Py's developers value backwards compatibility, so it's unlikely we're going to lose functionality. If some parts need tweaking following the change, I'll simply make an update to the tool, and you would be able to continue using this initial version on any earlier Ren'Py engine releases. In general I try to avoid modifying engine internals wherever possible, and if I do (often to fix bugs) I always ensure that if I need to change how the tool accesses that part, your "front-end" code using my tool won't have to change. So, I wouldn't be worried about it.