Ren'Py Need some help optimizing some bloated code for character Bio's

Turning Tricks

Rendering Fantasies
Game Developer
Apr 9, 2022
1,168
2,282
I'm hoping some of you guys with extensive python and/or ren'py experience can help me streamline some code I have.

So, I have this simple character page that you can call up at anytime by just clicking the "Characters" textbutton that is displayed during most of my VN. This is really the first real code I wrote in ren'py (almost two years ago) and it works okay, but I know there's likely a way to make it much more streamlined. It didn't matter that much though, until I just started adding features to it. What I need to do is give the player the ability to change the characters names at anytime, via this character Bio screen. I got some help from the other devs and I have a simple rename function added to each characters Bio page that works great. However, to do this new feature, I ended up having to add another label block and then duplicate that for each characters regular block. On top of that, because I am still new at this, I wasn't able to make some sort of dynamic variable function to reuse the same label block over and over again, just with different source variables.

The end result is to do this the way i want to and to be able to change the character's name, as well as their nicknames, would result in about ~600 lines of extra code. A year ago I would have said "Fuck it!" it works.. that's all that's important. But now i am trying to make my code better and it bothers me to see something I KNOW can most likely be reduced to a fraction of that if I could just figure out how to make the dynamic variables work.

Anyhow... to the code.

When a player clicks "Characters", it opens a screen built as a frame with the 16 Bio portraits (and their names below each portrait). That's straight forward and doesn't need to change.

Example...

Python:
screen bios_UI():
    style_prefix "bioUI"
    frame:
        modal True
        xalign 0.5
        yalign 0.5
        xpadding 20
        ypadding 20
        hbox:
            spacing 10
            vbox:
                imagebutton:
                    idle bio1_pic + "_idle"
                    hover bio1_pic + "_hover"
                    action [ToggleScreen( "bios_UI" ), ToggleScreen( "bio1_screen" )]
                text "[bio1_stat2]"
When you click one of the portraits above, it closes the character screen and opens the individual Bio screen of that particular character. Here is how that code looks now, with the new rename code already done for the character's name but not for the nickname. This works fine and it's tested. It's just duplicated 16 times with different values for the names. Also, the reason I have a separate style for button_text is because I have colored the highlighted text in each Bio to match the color that the characters text is displayed in, during the various Say states (Say, Thinking, Whispering, etc) Each character has their own custom color to help players differentiate the dialogue on-screen.

Python:
screen bio1_screen():
    style_prefix "BioStyle"
    frame:
        modal True
        right_padding 40
        top_padding 20
        xalign 0.5
        yalign 0.5
        hbox:
            vbox:
                image "bio_[bio1_stat1]"
            vbox:
                textbutton "[bio1_stat2]" style_prefix "Bio01" ysize 35 ypadding 10 xcenter 0.5 action [ToggleScreen( "bio1_screen" ), ToggleScreen( "Bio01_input" )]
                text "([bio1_stat4])\n" size 20 xalign 0.5
                text "Age: [bio1_stat3]" size 20 xalign 0.0
                text "Nickname: “{color=#ff0033}" + str(mc_nic) + "{/color}”\n" size 20 xalign 0.0
                text "[bio1_stat5]" size 20 xalign 0.0
                text "(click the name to change it)" size 20 xalign 0.5
                textbutton "{size=40}CLOSE{/size}" xcenter 0.5 ycenter 1.0 style_prefix "bioUI" action ToggleScreen( "bio1_screen" )

screen Bio01_input():
    frame:
        modal True
        padding (30, 20)
        xalign 0.5
        yalign 0.5
        hbox:
            vbox:
                text "Enter your new name and then click OK."
                input default "[mc_name]":
                    color "#ffff00"
                    length 32
                    value VariableInputValue("mc_name")
                key "K_RETURN" action NullAction()
                textbutton "OK" style_prefix "inputUI" action [ToggleScreen( "Bio01_input" ), Call( "set_bios" )]

style Bio02_button_text:
    size 40
    font "BibleScript-Regular.ttf"
    idle_color "#fa07e0"
    hover_color "#ffff00"

Anyhow... I can do dynamic variables in simple code - like the name scripts many ren'py games use. But doing it for multiple variables (character name, nickname and the colors for each character) combined with making it work for an input field that a player can click on right in the Bio... well that's beyond me ATM.

Any suggestions of how I can turn the above 38 lines of code, which are repeated 16 times, into something less bloated and clunky?
 

peterppp

Active Member
Mar 5, 2020
688
1,175
for stuff like this, i would use objects. make each character an object and then pass the object to a bio_screen() and use the values from the object.
 

Winterfire

Forum Fanatic
Respected User
Game Developer
Sep 27, 2018
5,296
7,695
You can create a class or dict for each character with each variable (common for all characters):

Python:
default characters = {
    "char1": {
        "name": "name 1",
        "age": 33,
        "image": "pic1",
        "description": "blabla"
    },
    "char2": {
        "name": "Name 2",
        "age": 33,
        "image": "pic2",
        "description": "blabla"
    },
    # Add all other characters here. Last character cannot have the "," at the end of the }
}
^ No need to have all of them as strings, the age is an integer for example. However, if you plan to only display them, you could turn the age into a string as well. However, you couldn't do things like "if age > 30 then..." unless you'd parse it back to int.


Now that you have a list of characters, you can create a SINGLE screen (honestly, such things should always be 1 and never a copy/paste because it ends up getting very messy, especially if you need to edit the code, then you need to do it 16x times, or more) and pass it as a parameter.

Python:
screen bio_screen(chara):
    $charInfo = characters[chara]
You can use the parameter like this:
Python:
text "[charInfo['description']]" size 20 xalign 0.0
In that single screen, you can integrate an input field to change the name OR create a input screen (where you'd pass charInfo as another parameter).

Your selection screen would then look like this:
Python:
screen bios_UI():
    style_prefix "bioUI"
    frame:
        modal True
        xalign 0.5
        yalign 0.5
        xpadding 20
        ypadding 20
        grid 2 2 spacing 10:
            for chara in characters:
                imagebutton:
                    idle characters[chara]["image"] + "_idle"
                    hover characters[chara]["image"] + "_hover"
                    action [ToggleScreen("bios_UI"), Show("bio_screen", chara=chara)]
This is a good tutorial for input screen (if you want it to be separate):
 

guest1492

Member
Apr 28, 2018
335
278
Python:
textbutton "[bio1_stat2]" style_prefix "Bio01" ysize 35 ypadding 10 xcenter 0.5 action [ToggleScreen( "bio1_screen" ), ToggleScreen( "Bio01_input" )]
Python:
input default "[mc_name]":
I am confused... The first seems to imply that bio1_stat2 holds the character's name but the second implies that mc_name holds the character's name.

If you're too far along to change the characters to dicts or class instances, you can use something like this:
Python:
screen bio_screen(num):
    style_prefix "BioStyle"
    frame:
        modal True
        right_padding 40
        top_padding 20
        xalign 0.5
        yalign 0.5
        hbox:
            vbox:
                image "bio_" + getattr(renpy.store, 'bio{}_stat1'.format(num))
            vbox:
                textbutton getattr(renpy.store, 'bio{}_stat2'.format(num)) style_prefix "Bio01" ysize 35 ypadding 10 xcenter 0.5 action [ToggleScreen( "bio_screen" ), ToggleScreen( "Bio_input" )]
                text "(" + getattr(renpy.store, 'bio{}_stat4'.format(num)) + ")\n" size 20 xalign 0.5
                # etc...
/CODE]
 

Turning Tricks

Rendering Fantasies
Game Developer
Apr 9, 2022
1,168
2,282
You can create a class or dict for each character with each variable (common for all characters):

Python:
default characters = {
    "char1": {
        "name": "name 1",
        "age": 33,
        "image": "pic1",
        "description": "blabla"
    },
    "char2": {
        "name": "Name 2",
        "age": 33,
        "image": "pic2",
        "description": "blabla"
    },
    # Add all other characters here. Last character cannot have the "," at the end of the }
}
^ No need to have all of them as strings, the age is an integer for example. However, if you plan to only display them, you could turn the age into a string as well. However, you couldn't do things like "if age > 30 then..." unless you'd parse it back to int.


Now that you have a list of characters, you can create a SINGLE screen (honestly, such things should always be 1 and never a copy/paste because it ends up getting very messy, especially if you need to edit the code, then you need to do it 16x times, or more) and pass it as a parameter.

Python:
screen bio_screen(chara):
    $charInfo = characters[chara]
You can use the parameter like this:
Python:
text "[charInfo['description']]" size 20 xalign 0.0
In that single screen, you can integrate an input field to change the name OR create a input screen (where you'd pass charInfo as another parameter).

Your selection screen would then look like this:
Python:
screen bios_UI():
    style_prefix "bioUI"
    frame:
        modal True
        xalign 0.5
        yalign 0.5
        xpadding 20
        ypadding 20
        grid 2 2 spacing 10:
            for chara in characters:
                imagebutton:
                    idle characters[chara]["image"] + "_idle"
                    hover characters[chara]["image"] + "_hover"
                    action [ToggleScreen("bios_UI"), Show("bio_screen", chara=chara)]
This is a good tutorial for input screen (if you want it to be separate):

Okay, I'm struggling with this...

I created the character dictionary like you suggested and the selection screen works okay...

Python:
default characters = {
    "char1": {
        "name": "Mark",
        "code": "mc_name",
        "surname": "Harris",
        "title": "Protagonist",
        "age": 46,
        "nicn": "Mick",
        "nics": "N/A",
        "image": "bio1",
        "description": "* Building Manager\n* Just got out of prison\n* Did over 5 years inside.\n\n",
        "color": "#ff0033"
    },

Python:
screen bios_UI():
    style_prefix "bioUI"
    frame:
        modal True
        xalign 0.5
        yalign 0.5
        xpadding 20
        ypadding 20
        grid 4 4 spacing 10:
            for chara in characters:
                imagebutton:
                    idle characters[chara]["image"] + "_idle"
                    hover characters[chara]["image"] + "_hover"
                    action [ToggleScreen("bios_UI"), Show("bio_screen", chara=chara)]

But when I try and open the screen bio_screen(chara) - I get this error

File "game/custom.rpy", line 274, in execute
image "[charInfo['image']]"
File "E:\Daz3d\Project Folder\Files\renpy-7.4.11-sdk\renpy\sl2\sldisplayables.py", line 451, in sl2add
d = renpy.easy.displayable(d, scope=scope)
File "E:\Daz3d\Project Folder\Files\renpy-7.4.11-sdk\renpy\easy.py", line 119, in displayable
return renpy.display.image.DynamicImage(d, scope=scope)
File "E:\Daz3d\Project Folder\Files\renpy-7.4.11-sdk\renpy\display\image.py", line 598, in __init__
self.find_target(scope)
File "E:\Daz3d\Project Folder\Files\renpy-7.4.11-sdk\renpy\display\image.py", line 669, in find_target
raise Exception("In DynamicImage %r: Could not find substitution '%s'." % (self.name, str(ke.args[0])))
Exception: In DynamicImage "[charInfo['image']]": Could not find substitution ''image''.

Here's the bio_screen...


Python:
screen bio_screen(chara):
    $ charInfo = characters[chara]
    style_prefix "BioStyle"
    frame:
        modal True
        right_padding 40
        top_padding 20
        xalign 0.5
        yalign 0.5
        hbox:
            vbox:
                image "[charInfo['image']]"
            vbox:
                textbutton "{color=[biocolor]}[charInfo['name']]{/color}" ysize 35 ypadding 10 xcenter 0.5 action [ToggleScreen( "bio_screen" ), ToggleScreen( "Bio_input" )]
                text "[charInfo['title']]\n" size 20 xalign 0.5
                if [charInfo['surname']]:
                    text "Surname: [charInfo['surname']]" size 20 xalign 0.0
                text "Age: [charInfo['age']]" size 20 xalign 0.0
                text "Nickname: {color=[charInfo['color']]}[charInfo['nicn']]{/color}”\n" size 20 xalign 0.0
                text "Lewd Name: {color=[charInfo['color']]}[charInfo['nics']]{/color}”\n" size 20 xalign 0.0
                text "[charInfo['description']]" size 20 xalign 0.0
                text "(click on the names to change them)" size 20 xalign 0.5
                textbutton "{size=40}CLOSE{/size}" xcenter 0.5 ycenter 1.0 style_prefix "bioUI" action ToggleScreen( "bio_screen" )

I'm swimming in the deep-end here and my arms are getting tired, lol :p
 

Turning Tricks

Rendering Fantasies
Game Developer
Apr 9, 2022
1,168
2,282
Change all [charInfo['XXX']] to [charInfo[XXX]].

Ren'py text interpolation is weird. You cannot nest interpolations either.
Ya, I figured that part out and removed those single quotes and it worked to the next step.

So, I can show the bio_screen okay, but now I am having issues making the text input work with those interpolations.
 

guest1492

Member
Apr 28, 2018
335
278
I haven't tried using before, but shouldn't it be like this?

Python:
screen Bio_input(chara):
    frame:
        modal True
        padding (30, 20)
        xalign 0.5
        yalign 0.5
        hbox:
            vbox:
                text "Enter your new name and then click OK."
                input:
                    color "#ffff00"
                    length 32
                    value DictInputValue(characters[chara], 'name')
                key "K_RETURN" action NullAction()
                textbutton "OK" style_prefix "inputUI" action [Hide( "Bio_input" ), Show("bio_screen", chara=chara)]
 

Turning Tricks

Rendering Fantasies
Game Developer
Apr 9, 2022
1,168
2,282
I haven't tried using before, but shouldn't it be like this?

Python:
screen Bio_input(chara):
    frame:
        modal True
        padding (30, 20)
        xalign 0.5
        yalign 0.5
        hbox:
            vbox:
                text "Enter your new name and then click OK."
                input:
                    color "#ffff00"
                    length 32
                    value DictInputValue(characters[chara], 'name')
                key "K_RETURN" action NullAction()
                textbutton "OK" style_prefix "inputUI" action [Hide( "Bio_input" ), Show("bio_screen", chara=chara)]

I had to revert my code back to original. I had it about 70% done and working, but I kept hitting issues with some progress booleans and variables I have that keeps track of who you have met and when.

I realized that this project is too far along to make a major code change like this. I have a system in place that tracks when you meet new people and then unlocks their profile picture and between all the various code for that and this Bios screen code, I just don't think it would be wise for me to make such a big change at this point. That's why I had those different variables for the same thing, that you noticed. One was the basic character name variable and the other bio stats for changed depending on certain conditions during the story.

A big part of the problem I'm having, is that I am getting pretty familiar with Ren'py but I still don't have the background in Python to easily debug python functions. While Ren'py syntax and structure is getting pretty easy for me to use and scan.

So instead of trying to cram a years worth of python training into a day, I'll just finish this old bloated code. I can easily make it do what I want and I am past the half-way point in my VN so I won't have any new characters to add. I'm okay dealing with ~600 lines of code for this feature and it's mostly just formatting that I have to tweak.

I was also worried about breaking saves if I go this route.

I'll save this work and look at it again when I start my next project. When I do start a new project, I'll do this whole character screen code properly, from the ground up. I'll also probably do the normal thing and have it all linked with the player's phone. I've already lost a day's production fucking around with this today and I need to get back to rendering.
 

gojira667

Member
Sep 9, 2019
287
273
I had to revert my code back to original. I had it about 70% done and working, but I kept hitting issues with some progress booleans and variables I have that keeps track of who you have met and when.
...
I'm okay dealing with ~600 lines of code for this feature and it's mostly just formatting that I have to tweak.
No need to duplicate the name change screen. Use your already written screens and pass in the appropriate variable name, as a string, to the name change screen.
Python:
screen Bio01_input(name_var):
    frame:
        modal True
        padding (30, 20)
        xalign 0.5
        yalign 0.5
        hbox:
            vbox:
                text "Enter your new name and then click OK."
                input default getattr(store, name_var):
                    color "#ffff00"
                    length 32
                    value VariableInputValue(name_var)
                key "K_RETURN" action NullAction()
                textbutton "OK" style_prefix "inputUI" action [ToggleScreen( "Bio01_input" ), Call( "set_bios" )]
 
  • Red Heart
Reactions: Turning Tricks

Turning Tricks

Rendering Fantasies
Game Developer
Apr 9, 2022
1,168
2,282
No need to duplicate the name change screen. Use your already written screens and pass in the appropriate variable name, as a string, to the name change screen.
Python:
screen Bio01_input(name_var):
    frame:
        modal True
        padding (30, 20)
        xalign 0.5
        yalign 0.5
        hbox:
            vbox:
                text "Enter your new name and then click OK."
                input default getattr(store, name_var):
                    color "#ffff00"
                    length 32
                    value VariableInputValue(name_var)
                key "K_RETURN" action NullAction()
                textbutton "OK" style_prefix "inputUI" action [ToggleScreen( "Bio01_input" ), Call( "set_bios" )]
Thx! This actually helped a lot. I still have to keep 16 separate bio pages, but I got rid of the rename scripts and just pass the variable to this script above. Of course, I am so clueless that it took me a few tries to find the right syntax to pass the variable to this screen using my textbuttons. But I got it in the end... I just use Show( "Bio01_input", name_var="mc_name" )
 
  • Like
Reactions: gojira667

Turning Tricks

Rendering Fantasies
Game Developer
Apr 9, 2022
1,168
2,282
One issue I am having, and this isn't a critical one but annoying nonetheless, is that whenever you close the textbutton, the dialogue on the main screen advances. I already added that key "K_RETURN" action NullAction() to prevent the player from advancing dialogue if they hit Return when entering the new name, but clicking the OK button and closing the name input screen still advances the dialogue.

Any ideas how to prevent this?
 

gojira667

Member
Sep 9, 2019
287
273
...but clicking the OK button and closing the name input screen still advances the dialogue.

Any ideas how to prevent this?
The issue the Call() action. Once you hit the return control is passed to the following statement from when the call started.

You can try: [ToggleScreen( "Bio01_input" ), Call( "set_bios", from_current=True )]

Keep in mind this actually re-runs the current statement. :unsure: Think I would look to have bios_UI be a user-defined menu screen, .
 
  • Thinking Face
Reactions: Turning Tricks

Turning Tricks

Rendering Fantasies
Game Developer
Apr 9, 2022
1,168
2,282
The issue the Call() action. Once you hit the return control is passed to the following statement from when the call started.

You can try: [ToggleScreen( "Bio01_input" ), Call( "set_bios", from_current=True )]

Keep in mind this actually re-runs the current statement. :unsure: Think I would look to have bios_UI be a user-defined menu screen, .

You're a genius!

I changed my game_info screen (the one that is up in the upper left corner most of the story) to ShowMenu("bios_UI") and then for the CLOSE textbuttons on the character bios, I changed their action from ToggleScreen to Return().

Now the screens open, you can interact and change the text fields and when you close them, it doesn't advance the dialogue. I also just added a gallery icon to take the player to the main gallery page as well and it works good.

Finally getting this shit nailed down, lol.

EDIT: One bonus to using this method is my screens now share the main menu transitions, so they transition in and out smoother than just showing a screen abruptly.
 

Turning Tricks

Rendering Fantasies
Game Developer
Apr 9, 2022
1,168
2,282
Okay, that's all done. A huge item off my "To Do" list!

I added the name change functionality to all 15 current characters, including Name, Nickname and Lewd Name (if applicable). I also added a gallery icon to each Bio page and I cleaned up the formatting and made sure all the names were colored to match the character's custom color.

Took me longer to test it all than it did to write the code, lol. Overall I added 289 new lines of code, so that's not that bad.

On my next project, I will be making sure I nail down the code for a good phone system that also ties in to character Bio's and the Gallery - before I even start making the content.
 
  • Like
Reactions: osanaiko