Ren'Py How to add new saveable and changeable variables [SOLVED]

aceofspade

Newbie
Game Developer
Oct 3, 2018
84
232
Just wanna know how can I create a saveable variable after start() that I can access it from python and change its value. Example, I create a variable called "variable = False" after start(). Then, I want to change its value to True but in another file, calling in init python.

I know there is "default", I use it, but I can't change it's value inside python blocks, only in renpy. I searched a lot, but couldn't find a solution. Tried it in many ways. Is that simple, just want to add a new variable and don't have to start a new game, but in a way that I can change it's value in renpy or in python.
 

OscarSix

Active Member
Modder
Donor
Jul 27, 2019
829
6,647
When you say saveable do you mean a variable that saves its value even after quitting or starting a new game?

You can define variables after your start label by using RenPy's 1 line python statement, $ variable1 = value1
I would strongly recommend using a RenPy file, (.rpy) and using an init python: instead of a separate python file as you would need to import the python file for it to work.
 

OscarSix

Active Member
Modder
Donor
Jul 27, 2019
829
6,647
When you say saveable do you mean a variable that saves its value even after quitting or starting a new game?
You have normal variables variable1 = value1 that will be restored to their default or defined value when you start a new game.
And persistent variables persistent.variable1 = value1 that will be saved to a persistent file and retain its value even if you start a new game.
 

the66

beware, the germans are cumming
Modder
Donor
Respected User
Jan 27, 2017
7,667
23,783
code in an init block runs before declaring variables via the default statement.
declare your variables at proper init level and you can use them in init blocks.
they take part in saving when they got changed in the script once.
 
  • Like
Reactions: OscarSix

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,355
15,269
[Side note: Why can't I just reply to your message ? :/]
"I know there is "default", I use it, but I can't change it's value inside python blocks, only in renpy. "

What do you mean by "Python block", because you can totally change the value of a variable inside them.

Code:
init python:
    def increase( step ):
        store.myVar += step

default myVar = 0

label start:
    "myVar value is [myVar]"
    $ increase( 10 )
    "myVar value now is [myVar]"
    python:
        myVar += 100    
    "and finally myVar value is [myVar]"
    "End"
    return
 

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,581
2,219
Just wanna know how can I create a saveable variable after start() that I can access it from python and change its value. Example, I create a variable called "variable = False" after start(). Then, I want to change its value to True but in another file, calling in init python.

I know there is "default", I use it, but I can't change it's value inside python blocks, only in renpy. I searched a lot, but couldn't find a solution. Tried it in many ways. Is that simple, just want to add a new variable and don't have to start a new game, but in a way that I can change it's value in renpy or in python.

There's a lot going on within your post that doesn't seem clear to me, so let me go through a couple of things that "may be" the source of some confusion to you.

init: and init python: blocks are run as the game is starting up. It really doesn't matter which source code .rpy file they exist in... RenPy searches all source code for stuff like these and processes them well before anything else happens.

So when you say you want to create a variable AFTER label start: - that doesn't really tie into the whole use of init: or init python:.

From a little testing I just did now, it seems that variables created/set using default are processed after both init: or init python:. Again, RenPy doesn't care where the default statements are located within your source code - they are all processed BEFORE the game really gets started (but seemingly later than init blocks).
So if you had code that looked like this:

Python:
init:
    $ myVar = 3

init python:
    myVar = 6

label start:

    "myVar value is [myVar]"
    return

default myVar = 99

What you would see is "myVar value is 99".

So if you want to override the value of a variable during initialization, don't use default - because you're effectively duplicating the variable creation/initialization process in two places and then misunderstanding why the value isn't what you wanted.

However, let's assume it's not a coding issue you are discussing... and instead a practical one.

Perhaps you are talking about how to add a variable AFTER the game has already been released and played by the community. Perhaps you wrote version 0.1, released it and are now working on version 0.2.

The simple answer is that is exactly why default exists. Because RenPy processes all default statements during game startup... if you add a default myVar2 = False during development of version 0.2 - then it will simply become available for use by your code. No need to do anything else, it's just "there" to be used. It doesn't matter that the variable didn't exist while someone was playing version 0.1 or even if they load a version 0.1 save file. The fact there is a default statement somewhere is enough.

By way of explanation, because default statements are processed before save files could be loaded - the value would normally be set to its default value. Then if you load a savegame from 0.1 (where the new variable doesn't exist as part of the save file), it just keeps using its original value. However, if you load a savegame from 0.2 (or later presumably), the value within the save file overwrites the initial value set by the default statement.

Which brings me to my last alternative interpretation of your post.

If you really do what want do something clever to the value of your variable AFTER the player has already started playing... there is label after_load:.

label after_load: is an optional bit of RenPy code that is invoked every time the player loads a saved game. Use it with care... that run "every time" can really trip up inexperienced programmers (experienced ones too) - since you might put something in there to fix/repair something during 0.2, but you have to keep in mind that code will be run each and every time, even when loading version 0.8 save files.

You might do something like this:
Python:
default myVar3 = 0

label after_load:

    if myVar3 == 0:
        if renpy.seen_image("v3_bathroom7"):
            $ myVar3 = 6
        else:
            $ myVar3 = 3

    return

label start:

    # blah, blah, blah....

Skip this bit if you like... it's just highlighting how even simple solutions may not quite work for all circumstances...

The flaw of course is that you'll need to add a $ myVar = 0 just before the first use of the myVar3 variable. You'd think that because there is a default myVar3 = 0 you wouldn't need it. The reason you might is that if you release this code, then a brand new player comes along, starts the game, maybe puts in their name and immediately saves and quits because, well "reasons". They come back later, load their saved game and this code runs, finds the current value is 0 and immediately sets it to either 3 or 6. It's not the "normal player doing normal things" that trips you up - it's the stuff like this that come back to bite you in the ass, because you wrote it to correct things for "most" players, without considering that future new players could do something you weren't worried about. It will of course depend on how you use label after_load:, my point is merely that it isn't always as straight forward as it first appears.​


Finally, keep in mind that any statement after a dollar sign ($) in RenPy IS python code. So the idea of writing something in python after the the label start: is kinda moot - since it's all happening in python anyway.

However, as anne O'nymous has pointed out with his example, there are certain levels of python code that require you to qualify where the variable is stored by adding store. before the variable name (maybe even renpy.store.?) in order to correctly address the variable in the deep dark depths. There are of course other potential containers other than store., but honestly - it you are that deep into the dark arts... then you probably wouldn't be posting beginner level questions here in this forum.

I hope some of that helps.
If we have ultimately misunderstood your question... it may help if you posted the source code you are having problems with.
 
Last edited:

aceofspade

Newbie
Game Developer
Oct 3, 2018
84
232
Thanks for the explanations, I couldn't do what I wanted but the information was useful anyway. If I wasnt clear, ill try to do my best now. Its really a simple thing, nothing so deep.
Yesterday I was a little confused after seeing a lot of things, much headache, whatever... I forgot to mention some important information. I'm trying to change the value of a variable that I think is global (tested creating as default and in renpy after label start either) from a method of an object. If I print the variable, it shows the new value, but looking through the renpy console, the value hasn't changed, which leads me to believe it's changing the value of a local variable, not the global which is the one I think the console shows. Well, that I think is global (created as default, or after start with $ var)... Because I tried to put in my method

Code:
    def method():
        global var
        var = True
renpy.notify(var)
It shows True in screen, but still False in console.
I was going to put only the part that matters, but I preferred to put everything since I'm new at renpy and it's an adaptation of a script I found on the internet.
Well, my code:

script.rpy:
This
Code:
...

label after_load:
    if 'usingPill' not in globals():
        $ usingPill = False
    return


label start:
    $ usingPill = False
    $ inventory = Inventory()  #im sure I initialized inventory and pill, thats not the point
    $ pill = Item("Pill", image="gui/inv_chocolate.png", cost=5)
   
...
Or just this to declare the variable, without after_load

Code:
...

default usingPill = False

label start:
...
inventory.rpy:
Code:
init python:

    inv_page = 0 
    item = None
    class Item(store.object):
        def __init__(self, name, image="", cost=0):
            self.name = name
            self.image=image 
            self.cost=cost 

        def use(self): 
            if self.name == "Pill":
                global usingPill
                usingPill = True
                renpy.notify(usingPill)  #prints True
                inventory.drop(self)

    class Inventory(store.object):

        def __init__(self, money=0):
            self.money = money
            self.items = []

        def add(self, item): 
            self.items.append(item)

        def drop(self, item):
            self.items.remove(item)

        def buy(self, item):
            if self.money >= item.cost:
                self.items.append(item)
                self.money -= item.cost

    def item_use():
        item.use()





    style.tips_top = Style(style.default)

    style.tips_top.size=14

    style.tips_top.color="fff"

    style.tips_top.outlines=[(3, "6b7eef", 0,0)]

    style.tips_top.kerning = 5



    style.tips_bottom = Style(style.tips_top)

    style.tips_top.size=20

    style.tips_bottom.outlines=[(0, "6b7eef", 1, 1), (0, "6b7eef", 2, 2)]

    style.tips_bottom.kerning = 2



    style.button.background=Frame("gui/inv_frame.png",25,25)

    style.button.yminimum=52

    style.button.xminimum=52

    style.button_text.color="000"





screen inventory_button:

    imagebutton idle "gui/inv_button.png" hover im.MatrixColor("gui/inv_button.png", im.matrix.brightness(+0.2)) action [ Show("inventory_screen"), Hide("inventory_button")] align (.01,.01)



screen inventory_screen:

    text "Money: $[inventory.money]" xpos 0.36 ypos 0.77 style "tips_top"

    add "gui/inventory.png" xalign 1.0 yalign 0.0 

    modal True 

    hbox align (.01,.01) spacing 20:

        imagebutton idle  "gui/inv_button.png" hover im.MatrixColor("gui/inv_button.png", im.matrix.brightness(+0.2)) action [ Hide("inventory_screen"),SetVariable("inv_page", 0), Show("inventory_button")]

    $ x = 0 

    $ y = 205

    $ i = 0

    $ next_inv_page = inv_page + 1

    if next_inv_page > int(len(inventory.items)/9):

        $ next_inv_page = 0

    for item in inventory.items:

        if i+1 <= (inv_page+1)*9 and i+1>inv_page*9:

            $ x += 190

            if i%3==0:

                $ y += 170

                $ x = 767

            $ pic = item.image

            $ my_tooltip = "tooltip_inventory_" + pic.replace("gui/inv_", "").replace(".png", "")

            imagebutton idle pic hover im.MatrixColor(pic, im.matrix.brightness(+0.2))  xpos x ypos y action [Hide("gui_tooltip"), Show("inventory_button"), SetVariable("item", item), SetVariable("inv_page", 0),Hide("inventory_screen"), item_use] hovered [Show("gui_tooltip", my_picture=my_tooltip, my_tt_ypos=693) ] unhovered [Hide("gui_tooltip")] at inv_eff

        $ i += 1

        if len(inventory.items)>9:

            textbutton _("Next Page") action [SetVariable('inv_page', next_inv_page), Show("inventory_screen")] xpos .555 ypos .76  text_style "next_button"


screen gui_tooltip (my_picture="", my_tt_xpos=58, my_tt_ypos=687):

    add my_picture xpos my_tt_xpos ypos my_tt_ypos


init :

    transform inv_eff: 

        zoom 0.5 xanchor 0.5 yanchor 0.5

        on idle:

            linear 0.2 alpha 1.0

        on hover:

            linear 0.2 alpha 2.5



    image information = Text("INFORMATION", style="tips_top")


    image tooltip_inventory_chocolate=LiveComposite((665, 73), (3,0), ImageReference("information"), (3,30), Text("Generic chocolate to heal\n40 points of health.", style="tips_bottom"))

    image tooltip_inventory_banana=LiveComposite((665, 73), (3,0), ImageReference("information"), (3,30), Text("A healthy banana full of potassium! You can also use it as ammo for your guns! O.O Recharges 20 bullets.", style="tips_bottom"))

    image tooltip_inventory_gun=LiveComposite((665, 73), (3,0), ImageReference("information"), (3,30), Text("An gun that looks like something a cop would\ncarry around. Most effective on humans.", style="tips_bottom"))

    image tooltip_inventory_laser=LiveComposite((665, 73), (3,0), ImageReference("information"), (3,30), Text("An energy gun that shoots photon beams.\nMost effective on aliens.", style="tips_bottom"))



    image sidewalk = "Sidewalk.jpg"
Checking in renpy's console, usingPill is always False, even it shows True in screen
Trying
def use(self):
if self.name == "Pill":
store.usingPill = True
inventory.drop(self)

or
def use(self):
if self.name == "Pill":
usingPill = True
inventory.drop(self)

combined with the declarations, default usingPill = False before label start or $ usingPill = False after it. Same results either, couldn't change my usingPill value these ways. I hope it is clearer now, but any questions, just ask, please.
 

scrumbles

Engaged Member
Jan 12, 2019
2,328
2,418
It's just a wild guess, but maybe the issue is that the "use" method is invoked in a screen (through the function "item_use").
Try replacing item_use (in "imagebutton idle pic" [...]) with Function(item_use)
 

aceofspade

Newbie
Game Developer
Oct 3, 2018
84
232
It's just a wild guess, but maybe the issue is that the "use" method is invoked in a screen (through the function "item_use").
Try replacing item_use (in "imagebutton idle pic" [...]) with Function(item_use)
Same... Didn't change anything =\
 

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,581
2,219
Okay... I'm so far into the land of "I have no fucking clue what I'm doing", I barely remember what reality used to look like.

I'm generally clueless about classes and such... but...

this works for me...
Edit: Changed some variables from default to define, since they are effectively constants. (Thanks Anne).
... also means prices can be changed without affecting player's save files in the future.


Python:
default inv = Inventory()

define gown = Item("Gown", "", 10)
define painkiller = Item("Buffered Analgesic", "", 5)
define fluff = Item("Fluff", "", 1)
define toothbrush = Item("Toothbrush", "", 5)
define screwdriver = Item("Screwdriver", "", 10)
define mail = Item("Mail", "", 0)
define towel = Item("Towel", "", 0)
define pill = Item("Pill", "", 0)

default usingPill = False

init python:

    class Item():
        def __init__(self, name, image="", cost=0):
            self.name = name
            self.image = image
            self.cost = cost

        def use(self):
            if self.name == "Pill":
                store.usingPill = True
                renpy.notify(store.usingPill)
                inv.drop(self)

    class Inventory():

        def __init__(self, money=0):
            self.money = money
            self.items = []

        def add(self, item):
            self.items.append(item)

        def drop(self, item):
            self.items.remove(item)

        def buy(self, item):
            if self.money >= item.cost:
                self.items.append(item)
                self.money -= item.cost

label start:

    scene black with fade
    "Start:.... "

    $ inv.money = 100

    "Step 1... inventory cash = $[inv.money]"

    $ inv.buy(gown)
    $ inv.buy(screwdriver)
    $ inv.buy(painkiller)
    $ inv.buy(fluff)
    $ inv.buy(pill)

    "Step 2... pill use = [usingPill], cash= = $[inv.money]"

    $ pill.use()

    "Step 3... pill use = [usingPill]"

    "*** THE END ***"

    return
Now for the longest time, I couldn't get it to recognize my usage of the pill.

I had:
Python:
    $ pill.use

When I changed that to:
Python:
    $ pill.use()

It started working how I intended.
I'm sure someone who is much more familiar with python would know why those parenthesis matter.

I later tweaked that to be more like...
Python:
    def use_item(item):
        item.use()

# blah. blah... code.
# sometime later...

    $ use_item(pill)

Which "feels" neater to me, but as I say... classes... urgghhhh!!!.

I couldn't say which changes I've made have actually made the difference... All I can say is that an hour or so of "one monkey using one typewriter" resulted in something that seems to do what you wanted.

One obvious thing is that none of this code stops you trying to use an item you don't already have. It all needs tightening up a bit. But that's someone else's job.
 
Last edited:
  • Like
Reactions: aceofspade

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,355
15,269
I'm trying to change the value of a variable that I think is global
Unless defined in a python function or in a Python file (.py), all variables are global.
Python:
init python:
   myVar1 = "something"   # global

   def myFnct():
      myVar2 = "something"  # local

   class MyClass():
        myVar3 = "something" # local
        def myMethod():
            myVar4 = "something" # local

define myVar5 = "something"  # global
default myVar6 = "something" # global

label whatever:
    $ myVar7 = "something"  # global

If I print the variable, it shows the new value, but looking through the renpy console, the value hasn't changed,
Ren'py optimize the code by grouping some statements. Therefore, it effectively happen, time to time, that a value seen from the console isn't the same than the value seen from the game. But it only affect the console.
If you want to track the value of a variable, you've to use the watch console command. Open the console and type : watch nameOfTheVariable


Code:
    def method():
        global var
        var = True
You don't need global var. Like I said, variables declared in Ren'py are global. You can address them directly, and just need to prefix them with store when there's a possible ambiguity :
Python:
init python:

    class MyClass():
        value = 10

    def myFnct():
        # myVar2 isn't prefixed, it will be a local variable
        # myVar1 don't exist locally, but exist globally, Python will use
        # the global one.
        myVar2 = myVar1
        # Ambiguity, if it wasn't prefixed, Python would assume
        # that you are creating a local variable.
        store.myVar1 += 1
        # The first /myVar2/ is prefixed, so it will be the global variable.
        # The second isn't prefixed, and exist both locally and globally,
        # local variables have the priority, it's what Python will use.
        store.myVar2 = myVar2
        # It's an attribute from an object. /myObj/ don't exist locally,
        # but it exist globally, Python will use this global value.
        myObj.value *= 2

default myVar1 = 5
default myVar2 = 0
default myObj = MyClass()

label start:
    "before the function [myVar1]/[myVar2]/myObj.value]"
    $ myFnct()
    "after the function [myVar1]/[myVar2]/[myObj.value]"

Code:
label after_load:
    if 'usingPill' not in globals():
        $ usingPill = False
    return

label start:
    $ usingPill = False
default usingPill = False do exactly the same thing than all those lines.
It create the variable, give it False as default value, and the value will be updated if the variable exist in the loaded save file.

Edit: There's also something totally wrong with your code, "usingPill" will never be in globals(). Ren'py store its variables in an object named store ; globals() only contain the internal variables used by the core.
If really you need to test if a variable exist for Ren'py, you need to use this condition : hasattr( store, "name of the variable" ) is True.

$ pill = Item("Pill", image="gui/inv_chocolate.png", cost=5)
define pill = Item("Pill", image="gui/inv_chocolate.png", cost=5) is better here ; "pill" is a constant and, for this reason, have no reason to be included in a save. The variable will be created during the game initialization, and stay present even after the load of a save file.
This way, you can easily patch your game. If after some time you find that a cost of 10 is better, you just have to change the value in the define line, and that's all. By opposition of the way you actually do it, that would need something like :
Code:
label after_load:
    if hasattr( store, "pill" ) and pill.cost == 5:
        $ pill.cost = 10
    [...]

Code:
        def use(self):
            if self.name == "Pill":
                global usingPill
                usingPill = True
                renpy.notify(usingPill)  #prints True
                inventory.drop(self)
Your method to control is wrong.
Code:
init python:
    def myFnct():
        myVar = 1
        store.myVar = 10
        renpy.notify(str(myVar))

default myVar = 0

label start:
    $ myFnct()
    "pause"
Will display "1", not "10".

Change your code to this :
Code:
        def use(self):
            if self.name == "Pill":
                store.usingPill = True
                inventory.drop(self)
And it will works.

For some reason, Python failed to find the global variable "usingPill", and therefore fallback to the creation of a local variable with this name.


Python:
default gown = Item("Gown", "", 10)
Same advice above for you, prefer to use define for your constants. It's way easier to fix an error this way.
 
Last edited:

aceofspade

Newbie
Game Developer
Oct 3, 2018
84
232
I did it! And figured out what was the real problem. Well, I followed the suggestions and really, "define" makes more sense for certain variables, as explained. The usingPill I left as default and accessed them inside the method using store.variable. Thanks a lot, Anne, for the watch tip and the explanations, I didn't know that detail about the store, which now seems so obvious (A problem for those who don't study so much).
I thought very strange what was going on, but I found out what the real problem was: When I did ctrl + O, I don't know why but the game did a rollback and that made the variables come back. Since I was testing on screens that didn't show me any visual difference, it was hard to notice, the "watch" helped a lot with that as well.
Thanks a lot to all of you, I hope I can publish my first renpy game soon and post it here on the forum. Cya!
 
  • Like
Reactions: anne O'nymous