Turn Your Ren'Py Ad‑Hoc Menus Into Reusable Screens with Return()
Instead of hard‑coding confirm dialogs as labels and jumps, model them as screens that return a value. Learn how to declare a modal confirm dialog, call it from a label, and keep your story logic clean and reusable.
06 Aug 2025, 01:22 UTC

Why the old way is fragile
Most Ren'Py projects start with a simple label confirm_quit that jumps back to the main menu or exits the game. Over time the pattern repeats: label confirm_save, label confirm_load, label codex. Each of these labels contains its own menu block, a handful of jump statements, and a few boolean flags to remember the choice. The result is a tangled web of story labels that are hard to reuse, hard to test, and easy to break when you add a new platform or save‑load feature.
Ren'Py’s screen language was built exactly to solve this problem. Screens are declarative UI components that can be shown, hidden, and stacked without touching the story flow. When a screen finishes it can return a value to the calling label, turning the UI into a pure function of the game state.
1. Screens are declarative, not imperative
A screen is a block of UI code that describes what the player sees, not what the game does next. For example, a modal dialog that asks the player to confirm quitting looks like this:
screen confirm_quit():
modal True # Prevent interaction with the background
vbox:
xalign 0.5
yalign 0.5
background Frame("gui/frame.png", 30, 30)
text "Quit?"
hbox:
textbutton "Yes" action Return(True)
textbutton "No" action Return(False)
Notice the modal True flag, which blocks the rest of the screen stack. The layout uses a vbox to stack the title and buttons. Each button’s action ends the screen and yields a value.
2. Return() turns a screen into a function call
When you call screen confirm_quit from a label, Ren'Py pauses the label, displays the screen, and waits for a Return() action. The value passed to Return() becomes the result of the call screen expression. Here’s how you use it in a label:
label start:
scene bg classroom
show character happy at center
"Ready to leave?"
$ result = call screen confirm_quit
if result:
"Goodbye!"
return
else:
"All right, back to the game."
jump main_menu
The $ result = assignment captures the boolean. Because the screen is pure UI, the label can decide what to do with it—exit, continue, or branch elsewhere—without hard‑coding the menu logic.
3. Reuse and parameterize your dialogs
Screen arguments let you change the title or button labels without duplicating code. Here’s a generic confirmation screen:
screen confirm_dialog(title, yes_label="Yes", no_label="No"):
modal True
vbox:
xalign 0.5
yalign 0.5
background Frame("gui/frame.png", 30, 30)
text title
hbox:
textbutton yes_label action Return(True)
textbutton no_label action Return(False)
Now you can call it from any part of your game:
label save_prompt:
$ should_save = call screen confirm_dialog("Save game?", "Save", "Cancel")
if should_save:
$ renpy.save("autosave")
"Game saved."
else:
"Save cancelled."
Because the screen is stateless, you can safely show it from different labels, on different platforms, or even from a gamepad‑only build.
4. Trade‑offs and limitations
- Screen persistence across saves:
show screenkeeps a screen alive between saves, which can lead to stale UI. Always usecall screenfor dialogs that should not survive a save. - Direct variable mutation: If a button’s action writes to a game variable, the value may not be captured by
Return(). Keep the screen presentational; let the label handle state changes. - Platform quirks: Keyboard, mouse, and gamepad navigation can differ. Test each input method on your target builds.
5. Actionable next steps
- Audit one ad‑hoc label‑based menu in your project (e.g., a codex viewer).
- Convert it to a screen that takes arguments and returns a value.
- Replace all
jumpstatements withcall screenand branching logic based on the return value. - Run a quick test: show the screen, click both buttons, then save, load, and rollback. Verify the dialog does not reappear after a rollback.
- Commit the change, and repeat for any other dialog that currently uses labels.
By treating transient UI as pure screens that return a value, you keep your story logic clean, your UI reusable, and your save system reliable. Happy coding!
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.