Build Reactive Ren'Py UIs with Screen Language
Use Ren'Py Screen Language to build menus that update automatically when game variables change.
10 Jan 2026, 09:28 UTC

The problem: static menus can't track game state
Ren'Py's menu statement works for branching dialogue, but it rebuilds choices from scratch every time it appears. If you want a settings screen, inventory panel, or difficulty selector that reflects the current value of a game variable—and updates immediately when that variable changes—a hardcoded menu is the wrong tool.
The useful takeaway: Ren'Py's Screen Language lets you declare a UI once. The screen reads live variables and redraws automatically, so clicking a button that changes difficulty updates the text on the same screen without a manual refresh.
Declare a screen once, let it react
A screen block defines a reusable interface. Widgets such as textbutton run actions like SetVariable() when clicked. Because the screen re-evaluates its contents on each UI pass, any text or image that references a game variable shows the current value.
Use show screen to keep an interface visible across label jumps, or call screen for a modal dialog that pauses the script until dismissed. The zorder property controls stacking order; setting it too high can make the screen block clicks on elements beneath it.
Worked example: a persistent difficulty selector
Assumes a recent Ren'Py release (8.x series). Place this in any .rpy file under your project's game/ directory—commonly game/screens.rpy. You need write access to that folder; no special shell permissions are required. Launch the project through the Ren'Py Launcher to test.
default difficulty = "normal"
screen difficulty_selector():
modal True
zorder 50
frame:
xalign 0.5
yalign 0.5
text "Current difficulty: [difficulty]"
vbox spacing 10:
textbutton "Easy" action SetVariable("difficulty", "easy")
textbutton "Normal" action SetVariable("difficulty", "normal")
textbutton "Hard" action SetVariable("difficulty", "hard")
textbutton "Close" action Return()
label start:
"The difficulty is currently [difficulty]."
call screen difficulty_selector
"It is now [difficulty]."
Where to run: the Ren'Py script editor, saved as game/screens.rpy.
Expected checks: clicking Easy, Normal, or Hard changes the displayed text instantly. After closing the screen, the dialogue line shows the new value, proving the variable persisted outside the screen.
Risk: if you set zorder above other interface layers, this screen can swallow clicks meant for buttons behind it. Lower zorder or hide the screen when it is not needed.
To verify persistence across labels, replace call screen with show screen difficulty_selector, jump to another label, then show the screen again. The variable and its displayed value should survive the jump.
Trade-off: keep Python out of the render loop
Screens are not a replacement for frame-by-frame animation. Use ATL (Animation and Transformation Language) for moving or fading elements. Likewise, avoid heavy Python expressions inside screen widgets; they run during the UI render loop and can make menus feel sluggish if they execute on every pass.
Before you ship it
- Open the project in the Ren'Py Launcher and click "Launch Project."
- Reach the
startlabel and open the difficulty selector. - Change the difficulty, close the screen, and confirm the dialogue text reflects your choice.
- Test with
show screenacross a label jump if you need the UI to persist.
Screen Language is best suited for state-driven menus and HUDs, not cutscene animation. Start with a single reactive screen, verify that variables update the display, then expand only if the render loop stays responsive.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.