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: 286 Replies: 21
Viewing posts 1 to 7
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! 

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 ✨