¿Construyendo un sistema de estadísticas en Ren'Py? Te encontrarás con las mismas paredes que todos. "Mis datos de guardado se rompieron." "El rollback no revierte mis valores." "Los jugadores con guardados antiguos crashean después de mi actualización."
Esta guía te muestra cómo construir sistemas de estadísticas en Ren'Py 8.5.2 que sobreviven guardados, rollback y actualizaciones del juego. Asume que has leído la guía de inicio y conoces lo básico de define y default.
Cinco formas de romper tus datos de guardado
Antes de escribir cualquier código, necesitas entender dónde se rompe el sistema de guardado de Ren'Py. Estos cinco patrones aparecen repetidamente en la comunidad.
Patrón 1: Declarar estadísticas con define
# Esto se romperá
define player_hp = 100
define declara una constante. No se incluye en los datos de guardado. Cuando el juego se reinicia, cualquier cambio de HP se pierde y el valor se restablece a 100.
Patrón 2: Crear instancias dentro de init python
init python:
class PlayerStats:
def __init__(self):
self.hp = 100
self.mp = 50
# Esto se romperá
player = PlayerStats()
Los bloques init python se re-ejecutan cada vez que el juego inicia. Crear una instancia aquí significa que se sobrescribe con valores iniciales en cada carga.
Patrón 3: Cambios de atributos de clase no rastreados por rollback
default player = PlayerStats()
label battle:
$ player.hp -= 30
# ← Hacer rollback aquí NO restaura hp a su valor anterior
El rollback de Ren'Py rastrea si la referencia de una variable cambió. player.hp -= 30 no cambia a qué apunta player (el objeto en sí), así que el rollback no lo detecta.
Patrón 4: Declarar listas y diccionarios con define
# Esto se romperá
define inventory = []
label start:
$ inventory.append("potion")
# Guardar → Cargar y el inventario vuelve a ser una lista vacía
La misma razón que el Patrón 1. Las listas, diccionarios y cualquier objeto mutable que cambie durante el juego necesitan default.
Patrón 5: Cambiar la estructura de la clase rompe guardados antiguos
init python:
class PlayerStats:
def __init__(self):
self.hp = 100
self.mp = 50
# Agregado en v1.1
self.stamina = 80
Los datos de guardado de la v1.0 no tienen un atributo stamina. Acceder a player.stamina después de cargar ese guardado crashea el juego.
Los patrones 1 y 4 se arreglan usando default. El Patrón 3 requiere cambiar la clase padre. Los Patrones 2 y 5 tienen cada uno soluciones dedicadas. Vamos a verlos.
define vs default: Cuándo usar cuál
La guía de inicio introdujo esta distinción. Aquí está la imagen más profunda.
# Constantes — no se guardan
define MAX_HP = 100
define e = Character("Ellen", who_color="#c8ffc8")
# Variables — se guardan
default player_hp = 100
default inventory = []
default game_flags = {}
| Declaración | Se guarda | Rollback | Usar para |
|---|---|---|---|
define | No | No | Objetos Character, valores de config, constantes de máximo |
default | Sí | Sí | HP, ítems, flags, estado del juego |
La regla es simple. Si el valor cambia durante el juego, usa default. Si no, usa define.
La relación con init python
Las variables asignadas dentro de bloques init python tampoco se guardan.
init python:
# Las definiciones de clase van aquí (OK)
class PlayerStats:
def __init__(self):
self.hp = 100
# NO instancies aquí (INCORRECTO)
# player = PlayerStats()
Define clases en init python. Instancia con default. Esta es la regla.
# Definición de clase
init python:
class PlayerStats:
def __init__(self):
self.hp = 100
# Instanciación (esto se guarda)
default player = PlayerStats()
Diseño de clases seguro para rollback
Incluso con default, los cambios de atributos de clase no son rastreados por rollback (Patrón 3). La solución es cambiar la clase padre.
Heredar de renpy.store.object
El object en el espacio de nombres store de Ren'Py es en realidad RevertableObject. Heredar de él hace que los cambios de atributos sean rastreables por rollback.
init python:
class PlayerStats(renpy.store.object):
def __init__(self):
self.hp = 100
self.mp = 50
self.attack = 10
self.defense = 5
default player = PlayerStats()
label start:
"The hero's HP is [player.hp]."
$ player.hp -= 30
"Hit! HP dropped to [player.hp]."
# Hacer rollback aquí restaura correctamente player.hp a 100
Las clases que heredan de renpy.store.object tienen su __setattr__ enganchado, registrando todos los cambios de atributos. Esto permite que el rollback restaure los valores de atributos correctamente.
Las listas y diccionarios también son conscientes del rollback
De manera similar, las listas y diccionarios creados dentro de bloques init python se convierten automáticamente en versiones conscientes del rollback (RevertableList, RevertableDict). Lo mismo aplica para declaraciones default.
# Estos se convierten automáticamente en conscientes del rollback
default inventory = []
default game_flags = {}
Si necesitas explícitamente tipos built-in puros de Python (para datos que no deberían ser revertidos), usa python_list() o python_dict(). En la mayoría de los casos, las versiones conscientes del rollback por defecto son lo que quieres.
Construyendo un sistema de estadísticas
Es hora de juntar todo en un sistema de estadísticas funcional.
Definición de clase
init python:
class PlayerStats(renpy.store.object):
def __init__(self, name, hp=100, mp=50, attack=10, defense=5):
self.name = name
self.max_hp = hp
self.hp = hp
self.max_mp = mp
self.mp = mp
self.attack = attack
self.defense = defense
def take_damage(self, amount):
actual = max(0, amount - self.defense)
self.hp = max(0, self.hp - actual)
return actual
def heal(self, amount):
self.hp = min(self.max_hp, self.hp + amount)
def use_mp(self, cost):
if self.mp < cost:
return False
self.mp -= cost
return True
def is_alive(self):
return self.hp > 0
Los límites superior e inferior se aplican con max() / min(). Esto previene que HP sea negativo o exceda el máximo.
Instanciación y uso
default player = PlayerStats("Hero", hp=120, attack=15)
label start:
scene bg room
with fade
"The adventure begins."
"[player.name]'s HP: [player.hp]/[player.max_hp]"
menu:
"An enemy appears!"
"Attack with sword":
$ damage = player.take_damage(25)
"Counterattack! Took [damage] damage!"
jump check_status
"Cast a spell":
if player.use_mp(20):
"Defeated the enemy with magic!"
jump victory
else:
"Not enough MP!"
$ player.take_damage(30)
jump check_status
label check_status:
"[player.name] HP: [player.hp]/[player.max_hp] MP: [player.mp]/[player.max_mp]"
if not player.is_alive():
"You have fallen..."
return
"The adventure continues."
return
label victory:
"You won the battle!"
return
Screen de HUD de estadísticas
Para mostrar estadísticas en pantalla en todo momento, define una screen.
screen stats_hud():
frame:
xalign 1.0
yalign 0.0
xpadding 15
ypadding 10
vbox:
spacing 5
text "[player.name]" size 18
text "HP: [player.hp]/[player.max_hp]" size 14
text "MP: [player.mp]/[player.max_mp]" size 14
text "ATK: [player.attack] DEF: [player.defense]" size 14
label start:
show screen stats_hud
# Las estadísticas permanecen visibles en la esquina superior derecha desde aquí
Cambios de estadísticas desde opciones
El corazón de cualquier RPG o TRPG — opciones que moldean tu personaje.
default player = PlayerStats("Hero", hp=100)
label training:
menu:
"Which training do you choose?"
"Strength training":
$ player.attack += 3
"Attack power rose to [player.attack]!"
"Defense drills":
$ player.defense += 2
"Defense rose to [player.defense]!"
"Meditation":
$ player.max_mp += 10
$ player.mp = player.max_mp
"Max MP rose to [player.max_mp]!"
jump next_scene
Porque la clase hereda de renpy.store.object, todos los cambios de atributos están cubiertos por guardado y rollback.
Manteniendo guardados antiguos vivos después de actualizaciones
Después de lanzar tu juego, inevitablemente querrás agregar nuevos atributos de estadísticas. Pero los datos de guardado antiguos no tienen esos atributos, y acceder a ellos crashea el juego (Patrón 5).
default auto-llena variables simples
Las variables simples declaradas con default se inicializan automáticamente a su valor por defecto si no existen al momento de cargar.
# v1.0
default player_hp = 100
# Agregado en v1.1 — los guardados antiguos automáticamente obtienen 80
default player_stamina = 80
Esto solo funciona para variables de nivel superior declaradas con default. No aplica a atributos de clase.
Migra con label after_load
Para adiciones o cambios de atributos de clase, escribe lógica de migración en label after_load. Este label se ejecuta automáticamente cada vez que se cargan datos de guardado.
init python:
class PlayerStats(renpy.store.object):
def __init__(self):
self.hp = 100
self.mp = 50
self.attack = 10
self.defense = 5
# Agregado en v1.1
self.stamina = 80
self.max_stamina = 80
default player = PlayerStats()
label after_load:
# v1.1: Manejar guardados antiguos sin stamina
if not hasattr(player, "stamina"):
$ player.stamina = 80
$ player.max_stamina = 80
return
Usa hasattr() para verificar si el atributo existe antes de agregarlo. De esta forma, los datos de guardado de v1.0 se cargan correctamente con stamina correctamente inicializada.
Patrón de seguimiento de versión
Cuando las migraciones abarcan múltiples versiones, rastrear un número de versión mantiene el código organizado.
default save_version = 1
label after_load:
if save_version < 2:
# v1.0 → v1.1: Agregar stamina
if not hasattr(player, "stamina"):
$ player.stamina = 80
$ player.max_stamina = 80
$ save_version = 2
if save_version < 3:
# v1.1 → v1.2: Agregar luck
if not hasattr(player, "luck"):
$ player.luck = 5
$ save_version = 3
return
save_version se declara con default, así que el auto-llenado se activa. Los guardados antiguos no tendrán save_version, así que se inicializa a 1, y todas las migraciones se ejecutan en orden.
Conclusión
Esto es lo que cubrimos:
- Cinco patrones que rompen guardados: Declaraciones
define, instanciación eninit python, atributos ciegos al rollback,definemutable y atributos faltantes después de actualizaciones - define vs default: Cualquier cosa que cambie durante el juego usa
default; todo lo demás usadefine - Clases seguras para rollback: Hereda de
renpy.store.objecte instancia condefault - Implementación de sistema de estadísticas: Aplicación de límites, screen de HUD, cambios de estadísticas por opciones
- Migración compatible con guardados:
label after_load+hasattr()+ seguimiento de versión
Si eres nuevo en Ren'Py, comienza con la guía de inicio para configuración y lo básico.
Recursos oficiales:
- Python Statements — referencia oficial para
define/default/init python - Save, Load, and Rollback — cómo funcionan el sistema de guardado y rollback
- Store Variables — lista de variables en el espacio de nombres store
- r/RenPy — comunidad para preguntas y discusión