32blogby Studio Mitsu

Screen Language en Ren'Py: Por Qué Tu UI No Se Actualiza

Aprende screen language de Ren'Py 8.5 desde cero. Cubre el paradigma declarativo, mecánica de interacciones, show vs call screen y control de UI basado en Actions.

by omitsu11 min read
Contenido

Cuando intentas construir una pantalla de estado o un menú personalizado en Ren'Py, casi seguro pensarás: "Cambié la variable, pero la pantalla no se actualizó."

No es un bug. El screen language de Ren'Py está diseñado para ser declarativo. Si estás acostumbrado a la programación imperativa de Python, esto se siente contraintuitivo al principio.

Esta guía cubre los fundamentos del screen language en Ren'Py 8.5.2 y explica la mecánica detrás de "por qué no se actualiza."

Screen Language es declarativo

Python es imperativo. Escribes instrucciones paso a paso: "haz esto, luego haz aquello."

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

El screen language de Ren'Py es declarativo. Describes cómo debería verse la pantalla.

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

Este text "HP: [player_hp]" no es un comando que dice "muestra HP ahora." Es una declaración: "la pantalla debería mostrar el valor de HP." Ren'Py lee esta declaración en el momento apropiado y construye la pantalla.

Esta naturaleza "declarativa" es la clave para entender el comportamiento de actualización de pantallas.

Sintaxis básica de screens

Definiendo y mostrando una screen

renpy
# Definir una screen
screen greeting():
    frame:
        xalign 0.5
        yalign 0.5

        vbox:
            text "Hola"
            textbutton "Cerrar" action Hide("greeting")

label start:
    # Mostrar la screen
    show screen greeting

    "Una ventana es visible en la pantalla."

    # Ocultar la screen
    hide screen greeting

Define la estructura de UI con la declaración screen, luego muéstrala con show screen.

Displayables clave

Los bloques de construcción (displayables) que usas dentro de las screens.

DisplayablePropósitoEjemplo
textMostrar textotext "HP: 100"
addMostrar una imagenadd "icon.png"
textbuttonBotón de textotextbutton "OK" action Return()
imagebuttonBotón de imagenimagebutton idle "btn.png" action Return()
vboxContenedor verticalApila hijos verticalmente
hboxContenedor horizontalApila hijos horizontalmente
frameVentana enmarcadaÁrea de UI con fondo
barBarra/deslizadorbar value player_hp range 100

Para la lista completa, consulta Screens and Screen Language.

Condiciones y bucles

Puedes usar if y for dentro del screen language.

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

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

            if len(inventory_list) == 0:
                text "No hay objetos"

Estos se parecen al if/for de Python, pero se comportan diferente. Los if/for de screen se re-evalúan cada vez que la screen se re-evalúa. No son flujo de control imperativo de una sola vez.

Por qué tu screen no se actualiza

Este es el concepto central. Mira este código:

renpy
default player_hp = 100

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

label start:
    show screen hp_display

    "HP es [player_hp]."

    $ player_hp -= 30

    "HP bajó a [player_hp]."

Este código funciona bien. Cuando player_hp cambia, la screen se actualiza.

Entonces, ¿por qué la gente dice "mi screen no se actualiza"? Aquí es cuando realmente sucede.

El caso: Sin interacción

renpy
label start:
    show screen hp_display

    $ player_hp -= 30
    # ← La screen NO se ha actualizado aquí

    $ player_hp -= 20
    # ← Todavía NO se ha actualizado

    "HP es [player_hp]."
    # ← La screen finalmente se actualiza en esta declaración say (interacción)

Las screens de Ren'Py se re-evalúan cuando ocurre una interacción. Una interacción es cualquier punto donde el motor espera input del jugador.

Causa interacciónNO causa interacción
Declaraciones say (say)$ variable = value
Opciones (menu)show image
pausehide image
call screenshow screen
with transitionDeclaraciones Python (líneas $)

No importa cuántas variables cambies con líneas $, la screen no se redibujará hasta la siguiente interacción.

La solución: Agrega una interacción

Para actualizar la screen después de cambiar variables, dispara una interacción con una declaración say o pause.

renpy
label start:
    show screen hp_display

    $ player_hp -= 30
    pause 0.5
    # ← pause dispara una interacción, la screen se actualiza

    $ player_hp -= 20
    "HP bajó a [player_hp]."
    # ← la declaración say también es una interacción, la screen se actualiza aquí también

Cuando cambias variables a través de botones dentro de una screen, los Actions (cubiertos abajo) automáticamente reinician la interacción, así que no necesitas disparar una manualmente.

Cuidado con la predicción de screens

Ren'Py predice las screens antes de mostrarlas. Esta optimización permite transiciones suaves, pero significa que el código Python dentro de las screens puede ejecutarse en momentos inesperados.

renpy
screen dangerous():
    python:
        # ¡Esto se ejecuta durante la predicción también!
        store.gold -= 100

    text "Oro: [gold]"

El bloque python: dentro de una screen se ejecuta durante la predicción, antes de que la screen se muestre realmente. Nunca escribas efectos secundarios (cambios de variables, operaciones de archivo) en bloques python: de screens. Usa Actions para cambios de variables en su lugar.

Si tu screen recibe argumentos con efectos secundarios, usa la cláusula nopredict en show screen o call screen para desactivar la predicción:

renpy
# Evita que Ren'Py prediga (y pre-evalúe) esta screen
call screen my_screen(some_function()) nopredict

show screen vs call screen

Dos formas de mostrar una screen, con comportamiento muy diferente.

show screen: El label sigue ejecutándose

renpy
label start:
    show screen hp_display

    "La pantalla de HP es visible durante este diálogo."
    "Sigue visible aquí."

    hide screen hp_display

    "La pantalla de HP desapareció."

show screen muestra la screen pero no detiene la ejecución del label. Úsalo para HUDs siempre visibles (barras de estado, minimapas, etc.).

call screen: Espera input del jugador

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

        textbutton "Luchar" action Return("fight")
        textbutton "Huir" action Return("flee")

label start:
    call screen choice_menu

    # El resultado se almacena en _return
    if _return == "fight":
        "¡Elegiste luchar!"
    else:
        "¡Huiste!"

call screen muestra la screen y detiene la ejecución del label hasta que se llama a Return(). El valor de retorno va a _return. Úsalo para menús y diálogos temporales.

Controlando screens con Actions

Lo que pasas al action de un botón no es una llamada a función Python — es un objeto Action.

Un error común

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

screen heal_button():
    # Incorrecto — llama a do_heal() inmediatamente durante la evaluación de la screen
    # textbutton "Curar" action do_heal()

    # Correcto — lo envuelve en Function()
    textbutton "Curar" action Function(do_heal)

Escribir do_heal() llama a la función inmediatamente durante la evaluación de la screen, pasando su valor de retorno (None) a action. Escribir Function(do_heal) hace que se ejecute cuando se hace clic en el botón.

Actions incorporados clave

ActionComportamiento
SetVariable("name", value)Cambiar una variable global
SetScreenVariable("name", value)Cambiar una variable local de screen
ToggleVariable("name")Alternar una variable booleana
Function(callable)Ejecutar cualquier función
Show("screen_name")Mostrar otra screen
Hide("screen_name")Ocultar una screen
Return(value)Devolver un valor a call screen
NullAction()No hacer nada (hace que un botón sea clickeable)

Los Actions incorporados reinician automáticamente la interacción después de la ejecución. Cambia HP con SetVariable, y cualquier screen que muestre HP se actualiza automáticamente. Por eso raramente necesitas renpy.restart_interaction() directamente.

Para la lista completa, consulta Screen Actions.

Variables locales de screen

Las variables usadas solo dentro de una screen se declaran con default.

renpy
screen counter():
    default count = 0

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

default se inicializa una vez cuando la screen se muestra por primera vez. Asignar variables en un bloque python: dentro de una screen las reinicia en cada re-evaluación — un bug sutil que la documentación de Screens and Python señala específicamente.

Creando Actions personalizados

Cuando los Actions incorporados no son suficientes, puedes crear los tuyos subclasificando Action:

renpy
init python:
    class HealAction(Action):
        def __init__(self, amount):
            self.amount = amount

        def __call__(self):
            store.player_hp = min(store.player_hp + self.amount, store.max_hp)
            renpy.restart_interaction()
            return None

        def get_sensitive(self):
            return store.player_hp < store.max_hp

screen heal_button():
    textbutton "Curar +20" action HealAction(20)

get_sensitive controla si el botón es clickeable. Cuando player_hp ya está al máximo, el botón se desactiva automáticamente. Llamar a renpy.restart_interaction() dentro de tu Action personalizado fuerza la re-evaluación de las screens — los Actions incorporados hacen esto automáticamente, pero los personalizados necesitan llamarlo explícitamente.

FAQ

¿Cuál es la diferencia entre show screen y call screen?

show screen muestra la screen sin bloquear — el label sigue ejecutándose, ideal para HUDs y overlays persistentes. call screen pausa la ejecución del label hasta que se dispara un Action Return(), almacenando el resultado en _return. Usa call screen para menús y diálogos donde necesitas la elección del jugador antes de continuar.

¿Por qué mi variable de screen se reinicia cada vez que la screen se actualiza?

Probablemente estás asignando la variable en un bloque python: en vez de usar default. El código en bloques python: se ejecuta en cada re-evaluación de la screen, reiniciando el valor. Declara variables locales de screen con default count = 0 al inicio de tu screen — esto se inicializa solo una vez cuando la screen aparece por primera vez.

¿Cómo paso datos de una screen de vuelta a un label?

Usa call screen con un Action Return(value) en tus botones. El valor devuelto se almacena en la variable especial _return en tu código del label. Por ejemplo, textbutton "Sí" action Return(True) seguido de if _return: en el label.

¿Puedo usar funciones Python regulares en el action de un botón?

Sí, pero debes envolverlas con Function(). Escribir action my_func() llama a la función inmediatamente durante la evaluación de la screen y pasa None al action. Escribir action Function(my_func) posterga la ejecución hasta que se hace clic en el botón. Si tu función recibe argumentos, usa action Function(my_func, arg1, arg2).

¿Cómo hago que una screen se actualice continuamente (como un temporizador o animación)?

Para animaciones, usa transformaciones ATL dentro de tu screen — se ejecutan independientemente de las interacciones. Para temporizadores, usa la declaración timer: timer 1.0 action SetVariable("seconds", seconds + 1) repeat True. El timer dispara el Action en el intervalo especificado y reinicia la interacción automáticamente.

¿Cuál es la diferencia entre SetVariable y SetScreenVariable?

SetVariable modifica variables globales (las del store), visibles en todas las screens y labels. SetScreenVariable modifica variables declaradas con default dentro de esa screen específica, con alcance limitado a esa instancia. Usar SetVariable en una variable local de screen no funcionará, y viceversa.

¿Cómo creo una clase Action personalizada?

Subclasifica Action e implementa __call__ (qué sucede al hacer clic) y opcionalmente get_sensitive (si el botón es clickeable) y get_selected (si aparece como seleccionado). Llama a renpy.restart_interaction() al final de __call__ para refrescar las screens. Consulta la documentación de Screen Actions para la API completa.

Conclusión

Lo que cubrimos:

  • Paradigma declarativo: Las screens describen lo que debería mostrarse, no instrucciones paso a paso
  • Sintaxis básica: Define con screen, construye con displayables text/textbutton/vbox/frame
  • Interacciones: Las screens se re-evalúan en las interacciones. Las líneas $ solas no disparan actualizaciones de screen
  • show vs call: show screen es no bloqueante (para HUDs), call screen es bloqueante (para menús)
  • Actions: Pasa objetos SetVariable/Function/Return a los botones. Automáticamente disparan actualizaciones de screen

Para lo básico de Ren'Py, consulta la guía de inicio. Para visualización de imágenes, consulta la guía básica de imágenes.

Recursos oficiales:

Artículos relacionados: