How to store a bunch of different InputValues efficiently?

Discuss how to use the Ren'Py engine to create visual novels and story-based games. New releases are announced in this section.
Forum rules
This is the right place for Ren'Py help. Please ask one question per thread, use a descriptive subject like 'NotFound error in option.rpy' , and include all the relevant information - especially any relevant code and traceback messages. Use the code tag to format scripts.
Post Reply
Message
Author
GalArt
Newbie
Posts: 10
Joined: Tue Aug 11, 2020 4:02 pm
Contact:

How to store a bunch of different InputValues efficiently?

#1 Post by GalArt »

Hi!
I'm very new to python and renpy, but what I can find on this issue is either to basic or I don't know what to search for. Anyway, my problem is:

- I have a bunch of variables to store character information in.
- ...for multiple characters
- I would like to show those on a screen, where you can also modify these informations.

I got so far to show them on a screen and have the information as buttons with "input"-displayable like this:
screenshot0008.png
So you can select the person on the left and my programm takes all the information about this character out of the Characters[][][] list and fills the "form" with it.
Then you can click on those entries (buttons on the right side) to modify them. I have this input as a local variable "characters_0_1", but how do I get this input back into my list? Or maybe a field or something.

As far as I understand it, you can't use the SetVariable-action, because you can't access the individual list items with beeing forced to put the list in quotation marks.
And I have to use some kind of list or field (i don't know fields), because I don't know how else I could access a set of variables in a context sensitive manner (with "person x" beeing the context and "information 1 to y" being the set of variables.)

a cropped down version of my code, which shows only the necessary stuff:

Code: Select all

default Characters = [                                         # variable from another file
        [True, None, "", "Person", "1"], # Character 01
        [True, None, "", "Person", "2"], # Character 02
        and so on
]
default characters_0_1 = ""                                # variable for the character-form
default characters_0_2 = ""                                # variable for the character-form
default inp_val_1 = VariableInputValue('characters_0_1',default=False,returnable=False)  # InputValue for the character-form
default inp_val_2 = VariableInputValue('characters_0_2',default=False,returnable=False)  # InputValue for the character-form

screen characters(scroll=None, yinitial=0.0):
    tag menu
    frame:
           hbox:
                #(left side frame with list of characters)
                frame: 
                                for i in range(len(Characters[0])):
                                    if Characters[0][i][0]:                                               # shows characters already known
                                        textbutton [Characters[0][i][3]+" "+Characters[0][i][4]] action [ # charactername - button
                                            SetVariable("characters_0_1",Characters[0][i][3]),            # copy first name into form
                                            SelectedIf(SetVariable("characters_0_2",Characters[0][i][4])) # copy last name into form
                                            ]
                #(right side frame with character information)
                frame:

                            fixed:
                                order_reverse True           # This ensures the input will get the enter event before any of the buttons do.
                                
                                hbox:                                      
                                    xpos 60 ypos 235
                                    
                                    button:                                          # last name of character with input
                                        key_events True
                                        action inp_val_2.Toggle()
                                        input:
                                            value inp_val_2
                                            
                                    button:                                          # first name of character with input
                                        key_events True
                                        action inp_val_1.Toggle()
                                        input:
                                            value inp_val_1

User avatar
hell_oh_world
Miko-Class Veteran
Posts: 777
Joined: Fri Jul 12, 2019 5:21 am
Contact:

Re: How to store a bunch of different InputValues efficiently?

#2 Post by hell_oh_world »

For things like lists or dictionaries you can use DictInputValue, for objects FieldInputValue it is. I would suggest using objects for things like this because as soon as your set of characters grow it would be cumbersome to not manage them in an object oriented way.

Code: Select all

default dictInpVal = DictInputValue(characters[0], 0, default=False,returnable=False)
# accesses the first element in the first character in the characracter list.
Using objects would be...

Code: Select all

init python:
  store.character_profiles = [] # the list that would contain all the profiles, dont add here, they will be automatically added once you created the CharacterProfile() instance... Like below...
  
  class CharacterProfile(object):
    def __init__(self, **details):
      self.details = details.keys()
      self.focused = False
      self.__dict__.update(details)
      store.character_profiles.append(self)
      
    def Focus(self):
      def focus(self):
        self.focused = True
        for cp in store.character_profiles:
          if cp is not self:
            renpy.run(cp.Unfocus())
            
      return Function(focus, self)
      
    def Unfocus(self):
      def unfocus(self):
        self.focused = False
        
      return Function(unfocus, self)
      

default mc = CharacterProfile(first_name="John", last_name="Doe") # add more if wanted, separated by commas.
define character.mc = Character("mc.first_name", dynamic=True) # use the character namespace... Dynamic true makes the name dynamic based on the profile named mc and its attribute first_name that was set.

screen profile():
  vbox:
    for index, profile in enumerate(character_profiles):
      textbutton "Profile {}".format(index +1) action profile.Focus()
      
      if profile.focused: # show only the inputs if the character profile is selected...
        hbox:
          for detail in profile.details:
            $ fiv = FieldInputValue(profile, detail, default=True)
            
            button:
              action fiv.Toggle()
              selected_background "#151515"
              
              input:
                value fiv
            
    textbutton "RETURN" action Return()

label start:
  call screen profile
  mc "My name is [character.mc]. My last name is [mc.last_name]"
My example is kinda extendable so you can add as many properties for the character such as height etc... Then by looping you can save a lot of time since the process is kinda repetitive for each character.

GalArt
Newbie
Posts: 10
Joined: Tue Aug 11, 2020 4:02 pm
Contact:

Re: How to store a bunch of different InputValues efficiently?

#3 Post by GalArt »

Thanks a lot! I don't quite understand everything, what you did there, but I will figure it out.

GalArt
Newbie
Posts: 10
Joined: Tue Aug 11, 2020 4:02 pm
Contact:

Re: How to store a bunch of different InputValues efficiently?

#4 Post by GalArt »

Ok, so I familiarized myself with your code and I like it a lot!

I have just one problem:
The input buttons in your profile screen are somewhat unresponsive meaning: You can only activate the input for those buttons once per profile selected.
Once you selected one button/attribute, you can't select another attribut unless you unfocus and refocus this profile again.

As far as I can tell, this might be because the InputValue is defined locally, but I don't know enough about that to understand why (and if) that is the case. I just can tell that it seems to be this way, whenever I define the variable within the screen.
When I, instead, define a global InputValue and later just change its value with "SetVariable()", you can Toggle() the input as you like:

Code: Select all

default inp_val_1 = FieldInputValue(mc, "first_name", default=False)
default inp_val_2 = FieldInputValue(mc, "last_name", default=False)

screen profile():
  vbox:
    for index, profile in enumerate(character_profiles):
      textbutton "Profile {}".format(index +1) action [
        profile.Focus(),
        SetVariable("inp_val_1",FieldInputValue(profile, "first_name", default=False)),
        SetVariable("inp_val_2",FieldInputValue(profile, "last_name", default=False))
        ]
      if profile.focused: # show only the inputs if the character profile is selected...
          hbox:
            
            button:
              action inp_val_1.Toggle()
              selected_background "#151515"
              
              input:
                value inp_val_1

            button:
              action inp_val_2.Toggle()
              selected_background "#151515"
              
              input:
                value inp_val_2
            
    textbutton "RETURN" action Return()
Maybe related to that issue: The "selected_background "#151515"" button-porperty doesn't show in your version of the code. It does, tho, in the code above.

So is there anything wrong with my workaround solution or is there just a better way to fix this input.Toggle() issue?

User avatar
hell_oh_world
Miko-Class Veteran
Posts: 777
Joined: Fri Jul 12, 2019 5:21 am
Contact:

Re: How to store a bunch of different InputValues efficiently?

#5 Post by hell_oh_world »

Okay, so i tested the code and it doesn't work as it should, probably making the input values as local variables isn't good at all, so you need to change these parts..

Code: Select all

self.details = details.keys()
to

Code: Select all

self.details = {d: FieldInputValue(self, d) for d in details.keys()}
then just some magic in the screen...

Code: Select all

if profile.focused: # show only the inputs if the character profile is selected...
        hbox:
          for detail, input_value in profile.details.items(): # this one is changed...
            
            button:
              action input_value.Toggle()
              selected_background "#151515"
              
              input:
                value input_value
tested this and it works fine.

GalArt
Newbie
Posts: 10
Joined: Tue Aug 11, 2020 4:02 pm
Contact:

Re: How to store a bunch of different InputValues efficiently?

#6 Post by GalArt »

Again, thanks a ton! For all the time and effort and quick response time!

I have to admit, tho, I'm not sure that I will use your latest code, because it's over my head and I don't like to work with tools I don't understand.
Plus, I'm currently satisfied with my global InputValue-version, which works, too!
But if I run into any issues or decide to make every profile attribute customizable (instead of currently just 3 out of 20), then I will come back and give it a shot.

Anyway, without your help, this screen would not exist, so again. Thank you!
screenshot01.jpg
screenshot02.jpg
Disclaimer: a bunch of placeholders ;)

GalArt
Newbie
Posts: 10
Joined: Tue Aug 11, 2020 4:02 pm
Contact:

Re: How to store a bunch of different InputValues efficiently?

#7 Post by GalArt »

I would like to revive this thread, because I encountered a major issue, which I've failed to solve so far. After 20h of try and error and still no clue, I thought I ask again.

So @hell_oh_world left me with this brilliant code:
hell_oh_world wrote: Thu Aug 13, 2020 1:28 pm Okay, so i tested the code and it doesn't work as it should, probably making the input values as local variables isn't good at all, so you need to change these parts..
updated code:

Code: Select all

init python:
  store.character_profiles = [] # the list that would contain all the profiles, dont add here, they will be automatically added once you created the CharacterProfile() instance... Like below...
  
  class CharacterProfile(object):
    def __init__(self, **details):
      self.details = {d: FieldInputValue(self, d) for d in details.keys()}
      self.focused = False
      self.__dict__.update(details)
      store.character_profiles.append(self)
      
    def Focus(self):
      def focus(self):
        self.focused = True
        for cp in store.character_profiles:
          if cp is not self:
            renpy.run(cp.Unfocus())
            
      return Function(focus, self)
      
    def Unfocus(self):
      def unfocus(self):
        self.focused = False
        
      return Function(unfocus, self)
      

default mc = CharacterProfile(first_name="John", last_name="Doe") # add more if wanted, separated by commas.
define character.mc = Character("mc.first_name", dynamic=True) # use the character namespace... Dynamic true makes the name dynamic based on the profile named mc and its attribute first_name that was set.

screen profile():
  vbox:
    for index, profile in enumerate(character_profiles):
      textbutton "Profile {}".format(index +1) action profile.Focus()
      
      if profile.focused: # show only the inputs if the character profile is selected...
        hbox:
          for detail, input_value in profile.details.items(): # this one is changed...
            
            button:
              action input_value.Toggle()
              selected_background "#151515"
              
              input:
                value input_value
            
    textbutton "RETURN" action Return()

label start:
  call screen profile
  mc "My name is [character.mc]. My last name is [mc.last_name]"

tested this and it works fine.
and it really does work fine until you close the game and start it again. Because if you do, the "mc" CharacterProfile object still remembers the changed values, so the "mc.first_name" and "mc.last_name" should still have the modified values (modified through the profile screen),
but the profile screen itself does not remeber these values but shows the default values ("John Doe" in this case)

To make it clearer:
Let's say we start the game and play a bit and open the profile screen to change the player name from "John Doe" to "Bob Builder".
Then the profile screen will show "Bob Builder" and

Code: Select all

mc "My name is [character.mc]. My last name is [mc.last_name]"
will also be: Bob "My name is Bob. My last name is Builder"

But if we restart the game and load this save, the profile screen will show "John Doe" again while the textbox will still show: Bob "My name is Bob. My last name is Builder"

So any idea how to fix the profile screen and make it also persistent?

User avatar
emz911
Regular
Posts: 103
Joined: Fri Jun 23, 2017 2:23 pm
Contact:

Re: How to store a bunch of different InputValues efficiently?

#8 Post by emz911 »

GalArt wrote: Tue Jun 01, 2021 2:06 pm I would like to revive this thread, because I encountered a major issue, which I've failed to solve so far. After 20h of try and error and still no clue, I thought I ask again.

So @hell_oh_world left me with this brilliant code:
hell_oh_world wrote: Thu Aug 13, 2020 1:28 pm Okay, so i tested the code and it doesn't work as it should, probably making the input values as local variables isn't good at all, so you need to change these parts..
updated code:

Code: Select all

init python:
  store.character_profiles = [] # the list that would contain all the profiles, dont add here, they will be automatically added once you created the CharacterProfile() instance... Like below...
  
  class CharacterProfile(object):
    def __init__(self, **details):
      self.details = {d: FieldInputValue(self, d) for d in details.keys()}
      self.focused = False
      self.__dict__.update(details)
      store.character_profiles.append(self)
      
    def Focus(self):
      def focus(self):
        self.focused = True
        for cp in store.character_profiles:
          if cp is not self:
            renpy.run(cp.Unfocus())
            
      return Function(focus, self)
      
    def Unfocus(self):
      def unfocus(self):
        self.focused = False
        
      return Function(unfocus, self)
      

default mc = CharacterProfile(first_name="John", last_name="Doe") # add more if wanted, separated by commas.
define character.mc = Character("mc.first_name", dynamic=True) # use the character namespace... Dynamic true makes the name dynamic based on the profile named mc and its attribute first_name that was set.

screen profile():
  vbox:
    for index, profile in enumerate(character_profiles):
      textbutton "Profile {}".format(index +1) action profile.Focus()
      
      if profile.focused: # show only the inputs if the character profile is selected...
        hbox:
          for detail, input_value in profile.details.items(): # this one is changed...
            
            button:
              action input_value.Toggle()
              selected_background "#151515"
              
              input:
                value input_value
            
    textbutton "RETURN" action Return()

label start:
  call screen profile
  mc "My name is [character.mc]. My last name is [mc.last_name]"

tested this and it works fine.
and it really does work fine until you close the game and start it again. Because if you do, the "mc" CharacterProfile object still remembers the changed values, so the "mc.first_name" and "mc.last_name" should still have the modified values (modified through the profile screen),
but the profile screen itself does not remeber these values but shows the default values ("John Doe" in this case)

To make it clearer:
Let's say we start the game and play a bit and open the profile screen to change the player name from "John Doe" to "Bob Builder".
Then the profile screen will show "Bob Builder" and

Code: Select all

mc "My name is [character.mc]. My last name is [mc.last_name]"
will also be: Bob "My name is Bob. My last name is Builder"

But if we restart the game and load this save, the profile screen will show "John Doe" again while the textbox will still show: Bob "My name is Bob. My last name is Builder"

So any idea how to fix the profile screen and make it also persistent?
I believe you need to use retain after load for this:

Code: Select all

$ renpy.retain_after_load()
https://www.renpy.org/doc/html/save_loa ... after-load

GalArt
Newbie
Posts: 10
Joined: Tue Aug 11, 2020 4:02 pm
Contact:

Re: How to store a bunch of different InputValues efficiently?

#9 Post by GalArt »

Nice idea. Got my hopes up. Doesn't work , tho. :/

I also made the observation, that with

Code: Select all

default mc = CharacterProfile(first_name= "John", last_name= "Doe")
I can't change the name again after restarting the game. It seems to create a new "mc" object which ties to the profile screen variables, while calling the "mc.first_name" value (e.g. in dialog interpolation) still returns the old, modified name from before the restart.
Also, every other characterprofile object becomes duplicated, when I return to the main menu and load the game again.

In contrast, when I do the declaration in init python:

Code: Select all

init -1 python:
        mc = CharacterProfile(first_name= "John", last_name= "Doe")
I don't get dups and also the profile screen still works after reloading and restarting, so everything is fine, except that it still resets everything to default values after restarting the game. Even the "mc.first_name" value. So while playing and reloading (and as long as I don't close the game), it remembers any changes , but "forgets" them when I close the game.

GalArt
Newbie
Posts: 10
Joined: Tue Aug 11, 2020 4:02 pm
Contact:

Re: How to store a bunch of different InputValues efficiently?

#10 Post by GalArt »

Ok, so to recap the current state of the problem:
1. I was given a method how to manage a bunch of customizable variables by hell_oh_world, which I am very happy with
2. But those variables (objects) are not persistent and I can't figure out why that is and how to fix it.
2a. not persistent in the sense, that they reset to default when restarting the game application
2b. but they remain when restarting just the playthrough (by starting a new game or loading an old one) without closing the application

So my guess would be, that either the init declaration is the problem which overwrites all instances of these objects with the default values or the type of variable itself (FieldInputValue) is not suitable for persistent storage?

for the full code, have a look at earlier post, but I think the crucial part is this one:

Code: Select all

init -2 python:
    # the list that would contain all the profiles
    store.character_profiles = []

    class CharacterProfile(object):
        def __init__(self, **details):
            self.details = {d: FieldInputValue(self, d) for d in details.keys()}
            self.__dict__.update(details)
            store.character_profiles.append(self)

init -1 python:
    mc = CharacterProfile(first= "John", last= "Doe")
PS: It also makes no difference if I use a code variant, where certain character_profile.details are in no contact with FieldInputValue, so my guess would be, that upon closing the game application, it either deletes all objects or simply overwrites them during the init phase. If I check for the existence of these objects via...

Code: Select all

init -1 python:
    if 'mc' in globals(): # same for "in locals()" and "hasattr(mc, 'first')"
        mc = CharacterProfile(first= "John", last= "Doe")
    else:
        mc = CharacterProfile(first= "Bob", last= "Builder")
... it always returns False during init. So why does these objects become deleted and how do I prevent that?

edit: I tried some stuff and it becomes really weird. If I create the object after the init and within a label

Code: Select all

label char_init:
    $ renpy.retain_after_load()
    
    # False at this point
    if 'mc' in globals():
        n "mc exists in globals."
        mc "Hi, I am [mc.first] [mc.last]."
    else:
        n "mc does not exist in globals."
        
    $ mc = CharacterProfile(first= "John", last= "Doe")
    
    # True here
    if 'mc' in globals():
        n "mc exists in globals."
        mc "Hi, I am [mc.first] [mc.last]."
    else:
        n "mc does not exist in globals."
... I saved the game right after the object creation and restarted the application to load the save:
- the mc object still exists in global() and "mc.first" exists too
- the mc object is no longer in the "store.character_profiles" list (which it was as it should before the restart)

edit2: so a workaround seems to be to declare the "store.character_profiles = []" list also within a label and before the profiles. then everything seems to work as intended (so far)

User avatar
hell_oh_world
Miko-Class Veteran
Posts: 777
Joined: Fri Jul 12, 2019 5:21 am
Contact:

Re: How to store a bunch of different InputValues efficiently?

#11 Post by hell_oh_world »

GalArt wrote: Thu Jun 03, 2021 8:41 am But if we restart the game and load this save, the profile screen will show "John Doe" again while the textbox will still show: Bob "My name is Bob. My last name is Builder"

So any idea how to fix the profile screen and make it also persistent?
Sorry for the delayed reply. Just got back here, been busy with my thesis and all.
Hopefully, this would solve your issue...

Code: Select all

init python:
  store.character_profiles = []
Remove store.character_profiles = [] and kindly replace it with...

Code: Select all

init python:
  # your codes...
  pass
  
default character_profiles = []
And after applying these changes, start a new save using this code, to avoid any conflicts from your older saves with different code.
Good luck with your game :)

GalArt
Newbie
Posts: 10
Joined: Tue Aug 11, 2020 4:02 pm
Contact:

Re: How to store a bunch of different InputValues efficiently?

#12 Post by GalArt »

Either version works fine now. Again, thanks a lot! I consider this thread "solved".

henvu50
Veteran
Posts: 337
Joined: Wed Aug 22, 2018 1:22 am
Contact:

Re: How to store a bunch of different InputValues efficiently?

#13 Post by henvu50 »

What is that store object?

Is character_profiles an existing object of store, or did you make a new variable that is a child of the store object?

Why not just use character_profiles? Why did you use store? I'm curious about this store object, what it is, and what it does?

User avatar
zmook
Veteran
Posts: 421
Joined: Wed Aug 26, 2020 6:44 pm
Contact:

Re: How to store a bunch of different InputValues efficiently?

#14 Post by zmook »

henvu50 wrote: Thu Jun 10, 2021 10:26 pm What is that store object?
`store` is where renpy actually holds all variables you define, as part of the magic that allows rollback to work. So, for instance, the idiom to see if you've defined a variable yet is

Code: Select all

  if hasattr(store, "varname")
`character_profiles` and `store.character_profiles` are usually synonymous, unless you start making use of named stores or the `hide` modifier.

See https://www.renpy.org/doc/html/python.h ... -the-store
colin r
➔ if you're an artist and need a bit of help coding your game, feel free to send me a PM

Post Reply

Who is online

Users browsing this forum: DragoonHP