32blogby StudioMitsu
renpy7 min read

Ren'Py Screen Language: Why Your UI Won't Update

Learn Ren'Py 8.5 screen language from scratch. Covers the declarative paradigm, interaction mechanics, show vs call screen, and Action-based UI control.

renpyvisual-novelgame-devpython
On this page

When you try to build a status display or custom menu in Ren'Py, you'll almost certainly think: "I changed the variable, but the screen didn't update."

It's not a bug. Ren'Py's screen language is designed to be declarative. If you're used to imperative Python programming, this feels counterintuitive at first.

This guide covers the fundamentals of screen language in Ren'Py 8.5.2 and explains the mechanics behind "why it won't update."

Screen Language Is Declarative

Python is imperative. You write step-by-step instructions: "do this, then do that."

python
# Python (imperative)
hp = 100
hp -= 30
print(hp)  # 70

Ren'Py's screen language is declarative. You describe what the screen should look like.

renpy
screen stats_hud():
    frame:
        text "HP: [player_hp]"

This text "HP: [player_hp]" isn't a command saying "display HP now." It's a declaration: "the screen should show the HP value." Ren'Py reads this declaration at the appropriate time and builds the screen.

This "declarative" nature is the key to understanding screen update behavior.

Basic Screen Syntax

Defining and Showing a Screen

renpy
# Define a screen
screen greeting():
    frame:
        xalign 0.5
        yalign 0.5

        vbox:
            text "Hello"
            textbutton "Close" action Hide("greeting")

label start:
    # Show the screen
    show screen greeting

    "A window is visible on screen."

    # Hide the screen
    hide screen greeting

Define UI structure with the screen statement, then display it with show screen.

Key Displayables

The building blocks (displayables) you use inside screens.

DisplayablePurposeExample
textDisplay texttext "HP: 100"
addDisplay an imageadd "icon.png"
textbuttonText buttontextbutton "OK" action Return()
imagebuttonImage buttonimagebutton idle "btn.png" action Return()
vboxVertical containerStacks children vertically
hboxHorizontal containerStacks children horizontally
frameFramed windowUI area with background
barBar/sliderbar value player_hp range 100

For the full list, see Screens and Screen Language.

Conditions and Loops

You can use if and for inside screen language.

renpy
screen inventory():
    frame:
        vbox:
            text "Inventory"

            for item in inventory_list:
                textbutton "[item]" action NullAction()

            if len(inventory_list) == 0:
                text "No items"

These look like Python's if/for, but they behave differently. Screen if/for are re-evaluated every time the screen is re-evaluated. They're not one-time imperative control flow.

Why Your Screen Won't Update

This is the core concept. Look at this code:

renpy
default player_hp = 100

screen hp_display():
    text "HP: [player_hp]"

label start:
    show screen hp_display

    "HP is [player_hp]."

    $ player_hp -= 30

    "HP dropped to [player_hp]."

This code works fine. When player_hp changes, the screen updates.

So why do people say "my screen won't update"? Here's when it actually happens.

The Case: No Interaction

renpy
label start:
    show screen hp_display

    $ player_hp -= 30
    # ← Screen has NOT updated here

    $ player_hp -= 20
    # ← Still NOT updated

    "HP is [player_hp]."
    # ← Screen finally updates at this say statement (interaction)

Ren'Py screens are re-evaluated when an interaction occurs. An interaction is any point where the engine waits for player input.

Causes InteractionDoes NOT Cause Interaction
Say statements (say)$ variable = value
Choices (menu)show image
pausehide image
call screenshow screen
with transitionPython statements ($ lines)

No matter how many variables you change with $ lines, the screen won't redraw until the next interaction.

The Fix: Add an Interaction

To update the screen after changing variables, trigger an interaction with a say statement or pause.

renpy
label start:
    show screen hp_display

    $ player_hp -= 30
    pause 0.5
    # ← pause triggers an interaction, screen updates

    $ player_hp -= 20
    "HP dropped to [player_hp]."
    # ← say statement is also an interaction, screen updates here too

When changing variables through buttons inside a screen, Actions (covered below) automatically restart the interaction, so you don't need to manually trigger one.

Watch Out for Screen Prediction

Ren'Py predicts screens before displaying them. This optimization enables smooth transitions, but it means Python code inside screens can run at unexpected times.

renpy
screen dangerous():
    python:
        # This runs during prediction too!
        store.gold -= 100

    text "Gold: [gold]"

The python: block inside a screen runs during prediction, before the screen is actually shown. Never write side effects (variable changes, file operations) in screen python: blocks. Use Actions for variable changes instead.

show screen vs call screen

Two ways to display a screen, with very different behavior.

show screen: Label Keeps Running

renpy
label start:
    show screen hp_display

    "HP display is visible during this dialogue."
    "Still visible here."

    hide screen hp_display

    "HP display is gone."

show screen displays the screen but doesn't stop label execution. Use it for always-visible HUDs (status bars, minimaps, etc.).

call screen: Waits for Player Input

renpy
screen choice_menu():
    vbox:
        xalign 0.5
        yalign 0.5

        textbutton "Fight" action Return("fight")
        textbutton "Flee" action Return("flee")

label start:
    call screen choice_menu

    # The result is stored in _return
    if _return == "fight":
        "You chose to fight!"
    else:
        "You ran away!"

call screen displays the screen and halts label execution until Return() is called. The return value goes into _return. Use it for temporary menus and dialogs.

Controlling Screens with Actions

What you pass to a button's action isn't a Python function call — it's an Action object.

A Common Mistake

renpy
init python:
    def do_heal():
        store.player_hp += 20

screen heal_button():
    # Wrong — calls do_heal() immediately during screen evaluation
    # textbutton "Heal" action do_heal()

    # Right — wraps it in Function()
    textbutton "Heal" action Function(do_heal)

Writing do_heal() calls the function immediately during screen evaluation, passing its return value (None) to action. Writing Function(do_heal) makes it execute when the button is clicked.

Key Built-in Actions

ActionBehavior
SetVariable("name", value)Change a global variable
SetScreenVariable("name", value)Change a screen-local variable
ToggleVariable("name")Toggle a bool variable
Function(callable)Execute any function
Show("screen_name")Show another screen
Hide("screen_name")Hide a screen
Return(value)Return a value to call screen
NullAction()Do nothing (makes a button clickable)

Built-in Actions automatically restart the interaction after execution. Change HP with SetVariable, and any screen displaying HP updates automatically. That's why you rarely need renpy.restart_interaction() directly.

For the full list, see Screen Actions.

Screen-Local Variables

Variables used only within a screen are declared with default.

renpy
screen counter():
    default count = 0

    vbox:
        text "Count: [count]"
        textbutton "+1" action SetScreenVariable("count", count + 1)
        textbutton "Reset" action SetScreenVariable("count", 0)

default initializes once when the screen is first shown. Assigning variables in a python: block inside a screen resets them on every re-evaluation.

Wrapping Up

What we covered:

  • Declarative paradigm: Screens describe what should be shown, not step-by-step instructions
  • Basic syntax: Define with screen, build with text/textbutton/vbox/frame displayables
  • Interactions: Screens re-evaluate on interaction. $ lines alone don't trigger screen updates
  • show vs call: show screen is non-blocking (for HUDs), call screen is blocking (for menus)
  • Actions: Pass SetVariable/Function/Return objects to buttons. They automatically trigger screen updates

For Ren'Py basics, see the getting started guide. For image display, see the image basics guide.

Official resources: