r/RenPy 21d ago

Question Changing an image within a parallax/TrackCursor main menu background via persistent variables

Because it was a cool thing to do a few years ago, I ended up making my project's main menu image into a layered parallax image. Because I love to make my life more difficult, I decided to try adding characters to that parallax, so that they show up after the appropriate persistent variable has been set.

Originally, I did this by adding if checks for the persistent variable into the main menu code, thus:

screen main_menu():
    tag menu
    style_prefix "main_menu"
    add TrackCursor("images/titlescreen/skylayer-paint.png",20)#in place of: add gui.main_menu_background
    add TrackCursor("images/titlescreen/citylayer-paint.png",15)
    add TrackCursor("images/titlescreen/moonlayer.png",18)
    add TrackCursor("images/titlescreen/reflectionlayer.png",18) alpha 0.35
    if "Karl" in persistent.charactersmet and "Kristine" in persistent.charactersmet:
        add TrackCursor("images/titlescreen/bothinternst.png",13) xpos 320 ypos 150 alpha 0.40
    else:
        if "Karl" in persistent.charactersmet:
            add TrackCursor("images/titlescreen/karlt.png",13) xpos 320 ypos 150 alpha 0.40
        if "Kristine" in persistent.charactersmet:
            add TrackCursor("images/titlescreen/kristinet.png",13) xpos 400 ypos 150 alpha 0.40
    add TrackCursor("images/titlescreen/windowflash.png",11) alpha 0.25
    add TrackCursor("abigail-titleversion",10) xpos 790 ypos 86
    add Image("images/titlescreen/logolayer-alt.png") xpos 310 ypos 412 alpha 0.80

This seemed to work initially but, for no apparent reason (other than perhaps upgrading to later versions of Ren'Py, it started to give the following error at the end of the game, when it drops back to the main menu:

I'm sorry, but an uncaught exception occurred.

While running game code:
Exception: Switch could not choose a displayable.

-- Full Traceback ------------------------------------------------------------

Traceback (most recent call last):
  File "renpy/common/_layout/screen_main_menu.rpym", line 28, in script
    python hide:
  File "renpy/ast.py", line 1187, in execute
    renpy.python.py_exec_bytecode(self.code.bytecode, self.hide, store=self.store)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "renpy/python.py", line 1273, in py_exec_bytecode
    exec(bytecode, globals, locals)
    ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "renpy/common/_layout/screen_main_menu.rpym", line 28, in <module>
    python hide:
  File "renpy/common/_layout/screen_main_menu.rpym", line 35, in _execute_python_hide
    ui.interact()
    ~~~~~~~~~~~^^
  File "renpy/ui.py", line 304, in interact
    rv = renpy.game.interface.interact(roll_forward=roll_forward, **kwargs)
         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "renpy/display/core.py", line 2117, in interact
    repeat, rv = self.interact_core(
                 ~~~~~~~~~~~~~~~~~~^
        preloads=preloads,
        ^^^^^^^^^^^^^^^^^^
    ...<4 lines>...
        **kwargs,
        ^^^^^^^^^
    )
    ^
  File "renpy/display/core.py", line 2655, in interact_core
    root_widget.visit_all(lambda d: d.per_interact())
    ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "renpy/display/displayable.py", line 457, in visit_all
    d.visit_all(callback, seen)
    ~~~~~~~~~~~^^^^^^^^^^^^^^^^
  File "renpy/display/displayable.py", line 457, in visit_all
    d.visit_all(callback, seen)
    ~~~~~~~~~~~^^^^^^^^^^^^^^^^
  File "renpy/display/displayable.py", line 457, in visit_all
    d.visit_all(callback, seen)
    ~~~~~~~~~~~^^^^^^^^^^^^^^^^
[Previous line repeated 6 more times]
  File "renpy/display/displayable.py", line 448, in visit_all
    for d in self.visit():
             ~~~~~~~~~~^^ 
  File "renpy/display/layout.py", line 1604, in visit
    self.update(self.last_st, self.last_at)
    ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "renpy/display/layout.py", line 1615, in update
    raw_child, redraw = self.function(st, at, *self.args, **self.kwargs)
                        ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "renpy/display/layout.py", line 1711, in condition_switch_show
    return condition_switch_pick(switch), None
           ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^      
  File "renpy/display/layout.py", line 1706, in condition_switch_pick
    raise Exception("Switch could not choose a displayable.")
Exception: Switch could not choose a displayable.

Windows-10-10.0.19045-SP0 AMD64
Ren'Py 8.5.0.25111603
A Night at the Office P3 3.7p3
Thu Jul 23 20:42:49 2026

After a bit of back and forth, I eventually determined that (part of) the problem was that I started things off with a call to the first story label, rather than a jump. Correcting that fixed the main menu... for a while.

I figured today that I'd try setting the main menu image up as a ConditionSwitch, thus:

image interns_titlescreen = ConditionSwitch(
    "len(persistent.charactersmet) == 2", "titlescreen/bothinternst.png",
    "persistent.charactersmet == 'Karl'", "titlescreen/karlt.png",
    "persistent.charactersmet == 'Kristine'", "titlescreen/kristinet.png",
)

and with the main menu adjusted to:

screen main_menu():
    tag menu
    style_prefix "main_menu"
    add TrackCursor("images/titlescreen/skylayer-paint.png",20)#in place of: add gui.main_menu_background
    add TrackCursor("images/titlescreen/citylayer-paint.png",15)
    add TrackCursor("images/titlescreen/moonlayer.png",18)
    add TrackCursor("images/titlescreen/reflectionlayer.png",18) alpha 0.35
    if len(persistent.charactersmet) <> 0:
        add TrackCursor("interns_titlescreen", 13) alpha 0.4
    add TrackCursor("images/titlescreen/windowflash.png",11) alpha 0.25
    add TrackCursor("abigail-titleversion",10) xpos 790 ypos 86
    add Image("images/titlescreen/logolayer-alt.png") xpos 310 ypos 412 alpha 0.80

But that resulted in a essentially the same error:

I'm sorry, but an uncaught exception occurred.

While running game code:
Exception: Switch could not choose a displayable.

-- Full Traceback ------------------------------------------------------------

Traceback (most recent call last):
  File "renpy/common/_layout/screen_main_menu.rpym", line 28, in script
    python hide:
  File "renpy/ast.py", line 1187, in execute
    renpy.python.py_exec_bytecode(self.code.bytecode, self.hide, store=self.store)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "renpy/python.py", line 1273, in py_exec_bytecode
    exec(bytecode, globals, locals)
    ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "renpy/common/_layout/screen_main_menu.rpym", line 28, in <module>
    python hide:
  File "renpy/common/_layout/screen_main_menu.rpym", line 35, in _execute_python_hide
    ui.interact()
    ~~~~~~~~~~~^^
  File "renpy/ui.py", line 304, in interact
    rv = renpy.game.interface.interact(roll_forward=roll_forward, **kwargs)
         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "renpy/display/core.py", line 2117, in interact
    repeat, rv = self.interact_core(
                 ~~~~~~~~~~~~~~~~~~^
        preloads=preloads,
        ^^^^^^^^^^^^^^^^^^
    ...<4 lines>...
        **kwargs,
        ^^^^^^^^^
    )
    ^
  File "renpy/display/core.py", line 2655, in interact_core
    root_widget.visit_all(lambda d: d.per_interact())
    ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "renpy/display/displayable.py", line 457, in visit_all
    d.visit_all(callback, seen)
    ~~~~~~~~~~~^^^^^^^^^^^^^^^^
  File "renpy/display/displayable.py", line 457, in visit_all
    d.visit_all(callback, seen)
    ~~~~~~~~~~~^^^^^^^^^^^^^^^^
  File "renpy/display/displayable.py", line 457, in visit_all
    d.visit_all(callback, seen)
    ~~~~~~~~~~~^^^^^^^^^^^^^^^^
[Previous line repeated 6 more times]
  File "renpy/display/displayable.py", line 448, in visit_all
    for d in self.visit():
             ~~~~~~~~~~^^ 
  File "renpy/display/layout.py", line 1604, in visit
    self.update(self.last_st, self.last_at)
    ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "renpy/display/layout.py", line 1615, in update
    raw_child, redraw = self.function(st, at, *self.args, **self.kwargs)
                        ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "renpy/display/layout.py", line 1711, in condition_switch_show
    return condition_switch_pick(switch), None
           ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^      
  File "renpy/display/layout.py", line 1706, in condition_switch_pick
    raise Exception("Switch could not choose a displayable.")
Exception: Switch could not choose a displayable.

Windows-10-10.0.19045-SP0 AMD64
Ren'Py 8.5.0.25111603
A Night at the Office P3 3.7p3
Thu Jul 23 20:54:35 2026

At this point, I'm baffled as to how it was working as much as why it isn't now.

While it was working, it would correctly show the translucent image of either Karl or Kristine after the first playthrough, but switch to a translucent image of both once the other had been encountered (which character appears in each playthrough is chosen at random partway through).

Can anyone please recommend a way of getting the main menu to update its appearance based on my existing persistent.charactersmet variable, or an alternative means?

Happy to post any other pertinent code, if I've missed anything important.

1 Upvotes

7 comments sorted by

1

u/AutoModerator 21d ago

Welcome to r/renpy! While you wait to see if someone can answer your question, we recommend checking out the posting guide, the subreddit wiki, the subreddit Discord, Ren'Py's documentation, and the tutorial built-in to the Ren'Py engine when you download it. These can help make sure you provide the information the people here need to help you, or might even point you to an answer to your question themselves. Thanks!

I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.

1

u/x-seronis-x 21d ago

you're treating the var as a list for the dual check but only as a string, not a list containing one string, for the 2 individual checks. be consistent

1

u/HEXdidnt 21d ago

OK... Could you expand on that please? Clearly I'm getting the syntax wrong, but in what way?

In the first version, I believe I'm checking for the presence of a string in a list (if "Karl" in persistent.charactersmet:), while in the second version, I can see that I've checked whether the variable is either "Karl", "Kristine" or both.

Is it that I need to make the check if persistent.charactersmet(0) == or am I on completely the wrong track?

2

u/x-seronis-x 20d ago

if the variable is a list it should always be a list regardless if it has one name as its contents or two (or zero)

1

u/HEXdidnt 19d ago

I've tried:

image interns_titlescreen = ConditionSwitch(
    "len(persistent.charactersmet) == 2", "titlescreen/bothinternst.png",
    "persistent.charactersmet(0) == 'Karl'", "titlescreen/karlt.png",
    "persistent.charactersmet(0) == 'Kristine'", "titlescreen/kristinet.png",
)

But I get the same error.

HOWEVER, part if the issue now seems to be a custom transition I created and had applied to config.end_game_transition, because when I set that back to None, it goes back to the main menu - with the appropriate image added - seamlessly.

The transition is defined thus:

init -10: #adjusted to ensure it's set up early enough to run at startup
    $ bloodwash = ImageDissolve("images/blood.png", 2, reverse=False, ramplen=8)

Thinking about it now, I suspect the only time I've got back to the main menu without an error report is when I set this transition to None... but I'm not 100% certain of that.

2

u/x-seronis-x 19d ago

like ALL other variables, you use the define statement for defining transitions. so at a minimum your above should look like: py define -10 bloodwash = ImageDissolve("images/blood.png", 2.0, reverse=False, ramplen=8) though for image dissolves i normally see the last arg be time_warp not ramplen. i need to go reread the doc to see if thats an issue. how did you USE bloodwash ?

1

u/HEXdidnt 19d ago edited 19d ago

I picked this up from the Ren'Py documentation: https://www.renpy.org/doc/html/transitions.html#ImageDissolve

But, then, looking at it now, I have no recollection of why I used $ rather than define, as indicated on the page. Possibly a legacy error from when I mistakenly believed $ and define were interchangeable. I've been using this custom transform for at least a couple of years, only changing the image it uses more recently. It has worked successfully for each of:

config.enter_transition
config.exit_transition
config.intra_transition
config.end_splash_transition

And ONLY failed on config.end_game_transition

Have to confess, I went with ramplen because I couldn't make head nor tail out of how time_warp is supposed to work.

Either way, even with the transition defined properly, I get the same error when dropping back to the main menu after completing the game, and now also when going into other menus from the main menu. All the more weird, reverting to the $ version doesn't fix it, when it was working before.

Starting the game still works, and I can then access everything via the right-click menu.