Implementing Choices Without RenPy's `menu` Syntax


So, everything begins with this:


At the time, the illustration was already finished, and we definitely didn't have time to redraw or redesign everything, so we had to go with what we had. Then I came up with an idea:


I went ahead with the idea, and as I was writing this post and revisiting the O2A2 2025 FAQ, I found this:

I must have checked the FAQ page a hundred times during the jam, and yet I still ended up missing some of the rules...😭

Based on that👆, it’s possible that my solution might be bending the rules a little. We’ll leave the final decision to the organisers, and hopefully the game won’t have to be removed from the jam!


In the script, I only used call/show screen pick and toggled certain variables to control the behaviour of the screen. Everything else — from presenting the choices to handling their effects — was done entirely within the screen pick and its related code. This approach worked purely because the game is extremely small in scale; it would probably quickly become messy and problematic the moment the content grows more complex.

I only came up with this solution because I realised that using Ren'Py’s native menu syntax would likely have caused me to break the jam rules, which I was actively trying to avoid. Under normal circumstances, of course, the menu system syntax be more than sufficient and efficient for most use cases.

Here's how I implemented my solution.

default pick_activated = False
default snacks_up = False
        
# Defining transformation for an animation in script
transform trans_snacks_up:
    on show:
        yoffset 0
        linear 2.0 yoffset (-845 if snacks_up else 0)
        
screen pick:
    default apple_sound = "audio/apple.mp3" if pick_activated else None
    default cake_sound = "audio/cake.mp3" if pick_activated else None
    default milk_sound = "audio/milk.mp3" if pick_activated else None
    default steak_sound = "audio/steak.mp3" if pick_activated else None
    default udon_sound = "audio/udon.mp3" if pick_activated else None
     
    fixed at trans_snacks_up:
        if not apple_chosen:
            imagebutton: # apple
                xysize (380, 377)
                pos (763, 860)
                idle "images/items/apple.png"
                hover "apple_hover"
                hover_sound apple_sound
                action Function(handle_apple)
        
# The other four choice buttons follow pretty much the same structure, so I'll omit them here.
        
        if pick_activated:
            imagebutton: # leave
                xysize (226, 205)
                pos (1230, 210)
                idle "images/items/leave.png"
                hover "leave_hover"
                hover_sound "audio/leave_hover.mp3"
                activate_sound "audio/fridge_close.mp3"
                action Function(handle_leave)
        
# Defining button animation
init:
    image apple_ani:
        "items/apple1.png"
        0.7
        "items/apple2.png"
        0.7
        repeat
        
    image apple_hover = ConditionSwitch(
            "pick_activated == True", "apple_ani",
            "True", "images/items/apple.png")
        
# Defining button behaviour
init python:
    def handle_apple():
        if not pick_activated:
            return
        store.pick_activated = False
        store.apple_chosen = True
        store.choose_count += 1
        renpy.jump("apple")
        
    def handle_leave():
        if choose_count == 0:
            store.pick_activated = False
            renpy.jump("end_leave")
        else: # 1 & 2
            store.pick_activated = False
            renpy.jump("end_snacks")

Let me explain this code.


In the imagebutton, the hover attribute normally displays a static image (just like the idle state). However, when pick_activated is set to True — indicating that the player is currently required to make a choice — the hover state will instead display a corresponding animation. To achieve this dynamic behavior, I use ConditionSwitch().

This allows the hover image to switch between a static image (when pick_activated is False) and an animated version (when pick_activated is True), providing a visual cue that the button is interactive only during the active choice phase.

I applied the same idea to hover_sound by defining apple_sound etc., so that during the active choice phase, hovering over the button plays a sound effect, while outside of that phase, there's no sound at all.


As for the action part, I wanted the button to be unresponsive during non-choice periods. Ideally, it should progress the dialogue — but I couldn’t find a suitable function for that. Ren'Py does provide RollForward(), but it doesn’t quite do what I needed.

When pick_activated is True — meaning the player is making a choice — clicking the button will trigger the following actions:

  • Set pick_activated back to False: store.pick_activated = False

  • Mark the item as chosen: store.apple_chosen = True

  • Increment the choice counter: store.choose_count += 1

  • Jump to the corresponding label: renpy.jump("apple")

Outside of the choice phase, since pick_activated is False, clicking the button effectively results in action None. This is also why, as noted in the game’s homepage, clicking to advance the dialogue sometimes doesn’t work — because the cursor happens to be over an imagebutton that does nothing when clicked.


Finally, the transformation. There’s an animation in the script where the background image moves. Naturally, the screen containing the items should move in sync with it. At first, I tried to simply do something like show screen pick at trans_snacks_up, but quickly found that it didn’t work as expected.

Then I attempted a very inefficient and silly workaround by writing something like this:

if not apple_chosen:
    show apple:
        pos (763, 860)
        linear 2.0 yoffset -845

…and repeating it five times for each item. But I soon realised this also violated the rules.

So I did some more research and ended up creating a separate variable called snacks_up to control the animation. When the background animation plays, I set snacks_up = True and then re-show the screen pick. Because the line yoffset (-845 if snacks_up else 0) in the transformation, the entire screen shifts accordingly, like it’s moving in sync with the background animation.


 In actual use, it looks like this:

label after_snacks:
    if choose_count == 1:
        call food1 from _call_food1
    
# Can be replaced with something like `jump choose_food` if using the `menu` syntax
        $ pick_activated = True
        call screen pick
    
    elif choose_count == 2:
        call food2 from _call_food2
        $ pick_activated = True
        call screen pick
    elif choose_count == 3:
        call food3 from _call_food3
        jump end_snacks
    
    return


One last detail, and a failed successful(!!!) attempt.

I originally wanted the screen pick to remain persistently on screen without needing to hide it, in hopes of staying within the rules as much as possible. For blackout effects, I could just  show/call screen pick  on the master layer, so that when the solid black background is shown, it will appear on top and cover screen pick.

However, when trying to animate both the screen and the background simultaneously, I realised I had to do something like this:

hide screen pick
$ snacks_up = True
show screen pick

Without hiding and re-showing the screen, it would remain at yoffset = 0 and wouldn’t move with the background. I tried several alternatives — including $ renpy.restart_interaction , but none of them worked.

During my experiments, I also realised that I could simplify the logic by removing the snacks_up variable altogether and just passing a parameter directly into the transform, like this:

screen pick(yoffset=0):
    fixed at trans_snacks_up(yoffset)
        
transform trans_snacks_up(y):
    on show:
        yoffset 0
        linear 2.0 yoffset y
    
label start:
    show screen pick(yoffset=-845)

However, I still couldn’t get it to work properly without hiding and re-showing the screen — so in the end, I just had to leave it as it is.

Then it hit me and I just tried this, keeping everything the same except for the transform:

transform trans_snacks_up(y):
    yoffset 0
    linear 2.0 yoffset y 

And it works!!!🥳🥳🥳 Now the screen stays persistently on display, exactly as I wanted!


Honestly, these restrictions really pushed me to think differently. If it weren’t for the jam rules, I probably wouldn’t have tried writing this kind of custom logic at all. But I’m glad I did, and came out of it knowing Ren'Py a little better!

Thanks for reading! I’ll wrap things up here very happily.🎉

Get To Put Your Childhood Sweetheart Into A Refrigerator

Download NowName your own price

Leave a comment

Log in with itch.io to leave a comment.