Handling Variables In Ren’Py

I’m currently working on a Ren’Py game with a couple of other people and we’re not quite ready to post about it publicly yet, but I wanted to share some notes on handling variables between states and sessions as its caught me up a little bit!

Supporting Rollback and Save States

I’ve got a couple of classes that handle things like information about the main character and your current relationship with romancable characters. I was setting these up in the init, but it turns out that init variables do not participate in save states or rollback as they are often used for things like the ui.

The solution is to do variable initialisation in a callable label and then run this at start and load time. The hasattr function is used to check if the variable exists, with renpy.store containing every declared variable in the project.

label _init_variables:
         if not hasattr(renpy.store, 'mc') : mc = MainCharacter()

The call in new context function creates a new context where rollback is disabled and save/load still happens in the top level context.

label start:
    $ renpy.call_in_new_context("_init_variables")

label after_load:
    $ renpy.call_in_new_context("_init_variables")

Persistent Data

We wanted to have a list of achievements and ending so the player knows how many endings they’ve found. While ren’py does have a built in achievements system, this seems mostly for passing backend data to services like stream.

Instead, I used persistent variables. Adding ‘persistent.’ to a variable name saves it in persistent data, which remains the same between sessions.

$if persistent.achievements_list is None:persistent.achievements_list = []

I can then just add to this list when a player gets an achievement.

$ persistent.achievements_list.append("Started The Game")


Forcing Updates During Runtime

Variables are only checked when changed within the correct scope, which means that a class taking in a variable will only get the value at the point the class instance is created.
This feels hacky, but the accepted solution by the community seems to be to put all the variables that need to be constantly passed around into a screen, as it is forced to updated every frame.

 show screen update_vars()
screen update_vars():
    $ naal.good_value = naal.good_value
    $ naal.other_value = naal.other_value
    $ naal.bad_value = naal.bad_value
    $ sal.good_value = sal.good_value
    $ sal.other_value = sal.other_value
    $ sal.bad_value = sal.bad_value