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 (imperativo)
hp = 100
hp -= 30
print(hp) # 70
El screen language de Ren'Py es declarativo. Describes cómo debería verse la pantalla.
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
# 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.
| Displayable | Propósito | Ejemplo |
|---|---|---|
text | Mostrar texto | text "HP: 100" |
add | Mostrar una imagen | add "icon.png" |
textbutton | Botón de texto | textbutton "OK" action Return() |
imagebutton | Botón de imagen | imagebutton idle "btn.png" action Return() |
vbox | Contenedor vertical | Apila hijos verticalmente |
hbox | Contenedor horizontal | Apila hijos horizontalmente |
frame | Ventana enmarcada | Área de UI con fondo |
bar | Barra/deslizador | bar 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.
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:
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
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ón | NO causa interacción |
|---|---|
Declaraciones say (say) | $ variable = value |
Opciones (menu) | show image |
pause | hide image |
call screen | show screen |
with transition | Declaraciones 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.
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.
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:
# 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
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
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
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
| Action | Comportamiento |
|---|---|
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.
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:
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 displayablestext/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 screenes no bloqueante (para HUDs),call screenes bloqueante (para menús) - Actions: Pasa objetos
SetVariable/Function/Returna 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:
- Screens and Screen Language — referencia de screen language
- Screens and Python — clases Action, BarValue y
renpy.restart_interaction() - Screen Actions — lista completa de Actions incorporados
- Screen Language Optimization — detalles de predicción y optimización
- ATL (Animation and Transformation Language) — animaciones en screens
- Lemma Soft Forums — foro oficial de la comunidad Ren'Py
- r/RenPy — comunidad en Reddit
Artículos relacionados:
- Gestión de Estadísticas en Ren'Py: Patrones que No Rompen los Guardados
- Básicos de Imágenes en Ren'Py: show, scene y Transiciones
- Primeros Pasos con Ren'Py 8.5: De la Instalación a tu Primera Compilación
- Opciones y Ramificaciones en Ren'Py: De Menús a Múltiples Finales
- Audio en Ren'Py: BGM, Efectos de Sonido y Voces