Ren'Py Adding Flavor Text To Make Your Dialogue Feel More Responsive in RenPy - Tutorial + Code Examples

Palos69

Member
Game Developer
Feb 8, 2021
149
671
Hello All,

I've been rewriting an old Renpy game to learn the framework. I've decided to share some of the technical details of my work, so hopefully it'll be useful to someone. It was hard for me to find tutorials for the things I wanted to do. And if someone has an easier way to do the same thing, I would love to learn about it. This is meant more for amateur programmers, so more advanced developers probably won't find this interesting.

I'll start by saying that one of the most important aspects of development (in my opinion) is organization. Code should be easy to read and easy to navigate to reduce future development time fixing bugs and adding features. If you've poked around some of the codebases of some of the larger abandoned games, you'll realize how large and unwieldy some of the files are. One of the main reasons a game may be abandoned is due to the accumulation of what the software industry calls "technical debt". The game becomes too difficult or time consuming to change because of how bloated, hard to understand, or intertwined the current code is. It doesn't have to perfect, but spending extra time to organize your codebase by separating out features in smaller, digestible pieces will only help you in the long run if you want to add features or new routes down the line.

So back to the game development. Here's the structure of how the original game dealt with minor responsive dialogue in their game.

You don't have permission to view the spoiler content. Log in or register now.

There's nothing wrong with the above code, and it's even the right solution for many scenarios, especially if the dialogue is key information. But it's annoying to do this over and over again for minor dialogue that has no real impact on the story, which is why most games don't bother.

Below is my scenario:

I have a system in the game called "status". The value of status can be either "Beta", "Alpha", or "Neutral". The status can change throughout the game based on the choices of the player.

For example, I want the mother of the player character to respond differently to in-game dialogue based on this player determined status. So I created a python function that will retrieve different text depending on that status.

You don't have permission to view the spoiler content. Log in or register now.
The above example text is very specific to my project but can be easily modified to reflect another.

This function constructs a string that can then be used to retrieve a line of text stored in a variable. In the game, time progresses by day, so I have a different variable file for each day to keep it organized. "DayNr" is an int variable that increases whenever a day passes. "strId" is a string that will be passed in the function to identify which block of variables should be pulled from. And "status" is the same variable that was discussed before.

For this example, the line of text the mother is responding to is simply:

mc "I love you, Mom."

You don't have permission to view the spoiler content. Log in or register now.
Again, the above example text is very specific to my project but can be easily modified to reflect another. It is kept in a separate file from the main script code so it's easily scannable.

In the above variables, the "5" comes from "DayNr". The "status" in the variable name shouldn't need to be explained again at this point. And the "mombrstresponse" is the "strId" being passed in the function. To more easily manage this system, I keep my script code and my flavor text variables in separate files. If modifying your flavor text variables, it is quite easy to split the screen in your IDE to show your script code next to a variable list.

So in the script code, this section of dialogue would appear like this:

You don't have permission to view the spoiler content. Log in or register now.

But wait? I put the function as an inline expression in the dialogue? That is correct. A developer named Feniks created a script that allows this. It can be downloaded for free, but it's such a useful tool that I encourage you to donate a few dollars. It can be found . The above line of code will read and execute the function between the "<>" first and then the generated string will be used to retrieve a variable.

With the addition of other variable flags or conditions, you could make even more complex dialogue trees. The retrieve_response function could also be modified to accept additional parameters or have more internal logic. This will add varying degrees of complexity to your code, though, so it might be better to just separate out the script with if statements and labels in that case.

I have been able to use the same labels for multiple routes by carefully using this function once or twice in a section, though.

You don't have permission to view the spoiler content. Log in or register now.

CAVEATS:
The above code is really only useful for more nuanced changes, or flavor text. I would never use this method to do anything major like determining path routing or delivering key story dialogue. It is NOT intended to completely replace "if" statements in the codebase. It has a very specific use case.

Again, it also relies on organization. I have my flavor text variables separated out into different files. If you try to put script code, functions, variables all together in one file, I could see this getting confusing real quick.

I would also not use this method for recurring dialogue. For ease of use, it should be used for one time scripted events and it would probably be best to never reuse the same "id" just to makes things easier for yourself. In my example, this wouldn't be a problem, because I have "DayNr" as a parameter that would make the variable names different, but I'm still using an "id" only once.

Despite the limitations, I still think it's a useful tool for the developer toolbox. Like in the above example, a character responding with "an eye roll" to an "I love you" gives a completely different vibe to a scene than if she reacted with a "smile and a touch on the arm". A lot of games don't feature a lot of minor responsive changes and I do think it's a value add to a project if used correctly.

-------

Okay, that's it. If anything above doesn't make sense or needs to be expanded, let me know. I'll try to address any questions.

And if you have a different solution for the same kind of feature, please let me know. I want to know what other people are doing.
 
Last edited:

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,978
16,236
Interesting when it come to rewrite a game, yet with a lot of reserves (see below). But impracticable when it come to developing the game.
A part of the development process is to adjust the dialog lines because, even if you already have everything wrote, the reality will never be exactly what you pictured in your mind. Once put in context, especially with the visuals, many dialog lines will have to be adjusted one last time. And with your system, the author will need to constantly jump from a point to another to do this. It's not practicable, and it's also a big obstacle when it come to feel the dialog vibe.


But, as implied, there's also the problem of the benefit, that is way lower that you believe.

To take your own example the code would pass from:
Python:
label whatever:

    if characterEmotion == "Happy":
        mc "Insert happy dialogue here"
    elif characterEmotion == "Sad":
        mc "Insert sad dialogue here"
    else:
        mc "Insert other dialogue here"
to something like:
Python:
define dayx_HAPPY_stringToken = "Insert happy dialogue here."
define dayx_SAD_stringToken = "Insert sad dialogue here."
define dayx_NEUTRAL_stringToken = "Insert neutral dialogue here."

[...]

label whatever:

    mc "[[<retrieve_response('stringToken')>]"
side note: it's define that is needed here. There's really no need to pollute the save files by adding into them all those constant strings.

There's more to write in the second case than in the first one.
Plus...

If the "stringToken" part is easy to remember, because it will be used right when created, it's not necessarily the same for the changing element. Especially since you'll have to use it over years of development ; a development made during your free time.
"Is it 'HAPPY', 'SAD', 'NEUTRAL', or is the last one 'OTHER' ? Ah shit, let's looks what I used before."

And, of course, there's rarely only one possible criteria. Sometimes the dialog line will depend on the mood, other time it will depend on the love level or on the arousal.
Therefore, either you'll need to have a response_to_mood(), response_to_love() and response_to_arousal() function, or you'll have to add a parameter to your function, in order to make it use the right criteria retrieve_response( "stringToken", "criteria" ).
In both it add a bit of complexity to the development process. The developer will have to remember what is the name of the function, or the generic name it gave to the criteria, this still over the years of development during his free time. And, of course, he'll have more to write, while already having by default more to type than with a basic if structure.

But the variable used for the criteria isn't the sole factor that change. There isn't just one character that will see its dialog line change depending on [whatever].
So, the developer have once again to split the functions (response_to_mood_mother, response_to_mood_sister) or to add yet another parameter (retrieve_response( "stringToken", "criteria", "character" ). What just increase the complexity and add once again too many letters to type.

And all this is without taking count of the fact that the application range will not be constant.
Sometimes it will be:
Python:
if love_girl < 5:
   "Hi !"
elif love_girl < 10:
   "I'm happy to see you."
else:
   "I missed you."
But other times it will be:
Python:
if love_girl < 10:
   "Where were you ?"
elif love_girl < 15:
   "Please, don't stay away too long."
else:
   "Next time, take me with you."
Something that can only be handled by an extra parameter, and, like before, add in complexity, open the gate to many bugs, and force the developer to write way more.

And of course, all this still do no cover the special cases like:
Code:
if gift == "flower":
    "Wait an instant, I'll go get a vase."
elif gift == "necklace":
    "Can you help me to wear it, please ?"
Nor does it cover combined criteria like:
Python:
if love < 5 or lust < 5:
   "..."
elif love < 10 or lust < 15:
   "..."
else:
   "..."

In the end, if people use an if structure when they have to make a dialog line vary accordingly to a condition, it's because it really is the easiest way to do it.

Relying on a function to get the answer isn't necessarily a bad idea, but to add variation in repetitive scenes, not in replacement of criteria. In this last case, it will always imply more works, lead to more bugs (that will be less easy to catch) and it will also limits the possibilities, because the developer will think in terms of what can easily be done with the code he defined, discarding too complex criteria that would have been more fitting in the context.
 

Quintillian

Member
Apr 15, 2019
124
239
I think my biggest concern from using this approach would be how compatible it is with existing visualization tools. While I do agree that code readability is important, if you are making a branching VN, being able to get a highlevel overview of the project is more important than worrying about repeated if-statements. For a programmer this may be neat and clever, but for a writer that is trying to get a scene just right during a revision phase, this seems troublesome and tedious to use.
 
  • Like
Reactions: hiya02 and Palos69

Palos69

Member
Game Developer
Feb 8, 2021
149
671
But, as implied, there's also the problem of the benefit, that is way lower that you believe.

To take your own example the code would pass from:
Python:
label whatever:

    if characterEmotion == "Happy":
        mc "Insert happy dialogue here"
    elif characterEmotion == "Sad":
        mc "Insert sad dialogue here"
    else:
        mc "Insert other dialogue here"
to something like:
Python:
define dayx_HAPPY_stringToken = "Insert happy dialogue here."
define dayx_SAD_stringToken = "Insert sad dialogue here."
define dayx_NEUTRAL_stringToken = "Insert neutral dialogue here."

[...]

label whatever:

    mc "[[<retrieve_response('stringToken')>]"
But the variable used for the criteria isn't the sole factor that change. There isn't just one character that will see its dialog line change depending on [whatever].
So, the developer have once again to split the functions (response_to_mood_mother, response_to_mood_sister) or to add yet another parameter (retrieve_response( "stringToken", "criteria", "character" ). What just increase the complexity and add once again too many letters to type.

And all this is without taking count of the fact that the application range will not be constant.
Sometimes it will be:
Python:
if love_girl < 5:
   "Hi !"
elif love_girl < 10:
   "I'm happy to see you."
else:
   "I missed you."
But other times it will be:
Python:
if love_girl < 10:
   "Where were you ?"
elif love_girl < 15:
   "Please, don't stay away too long."
else:
   "Next time, take me with you."
Something that can only be handled by an extra parameter, and, like before, add in complexity, open the gate to many bugs, and force the developer to write way more.

And of course, all this still do no cover the special cases like:
Code:
if gift == "flower":
    "Wait an instant, I'll go get a vase."
elif gift == "necklace":
    "Can you help me to wear it, please ?"
Nor does it cover combined criteria like:
Python:
if love < 5 or lust < 5:
   "..."
elif love < 10 or lust < 15:
   "..."
else:
   "..."
In the end, if people use an if structure when they have to make a dialog line vary accordingly to a condition, it's because it really is the easiest way to do it.

Relying on a function to get the answer isn't necessarily a bad idea, but to add variation in repetitive scenes, not in replacement of criteria. In this last case, it will always imply more works, lead to more bugs (that will be less easy to catch) and it will also limits the possibilities, because the developer will think in terms of what can easily be done with the code he defined, discarding too complex criteria that would have been more fitting in the context.
Thanks for the thorough response. And I agree with you for the most part. I would never use this method to do anything major like determining path routing or delivering main story dialogue. It is NOT intended to completely replace "if" statements in the codebase. It has a very specific use case. This function is used for one time dialogue for a specific point in the story, not as recurring dialogue. Therefore, an "id" should only be used one time and in one spot. I think my title made the intent of the function seem broader than what it really is, which is on me.

I'm about a quarter of the way done with my project but haven't run into any major bugs. Probably because I keep the system very compartmentalized. Since it's only used to change the flavor of dialogue text, the worst you could get is a line might sound off once in a while. And if someone has trouble debugging that because they can't use the search function present in every IDE, then it can't be helped. I only have three states to track in my project, but if someone is trying to do this with say more than five, I would probably look into a different solution or reducing the scope of the project.

And thanks for the tip on define vs default. I'm new to Python and I have a Java background.

I'm not sure I completely understand all of your critique. Why would you need to remember the function name? Most IDE's would populate a function after implementation with just a few typed characters. And to see the "id" you would just look in the script?

(I have changed the name of my thread title to reflect the lower scope of the function and made changes to my original post based on this feedback. )

I think my biggest concern from using this approach would be how compatible it is with existing visualization tools. While I do agree that code readability is important, if you are making a branching VN, being able to get a highlevel overview of the project is more important than worrying about repeated if-statements. For a programmer this may be neat and clever, but for a writer that is trying to get a scene just right during a revision phase, this seems troublesome and tedious to use.
I can see your point. It's never been an issue for me because I split up my script code by day and then have a separate variable file specifically for this kind of text per day. I rarely have more than five such implementations in a file. And it's easy to split my IDE screens to have the variables on one side while writing.

That being said, I've cracked open quite a few games to see how other people are doing it and I've noticed a lot of them put their entire script in one file. One had over 35k lines of code. I would never use this method for a game organized like that. Like most design choices, it's only valid in certain situations, not a blanket solution.

What kind of visualization tools are you using? I've been looking for a framework that maps out renpy dialogue trees/label jumps but haven't been able to find one that doesn't require a great deal of manual work.
 
Last edited:

Quintillian

Member
Apr 15, 2019
124
239
I can see your point. It's never been an issue for me because I split up my script code by day and then have a separate variable file specifically for this kind of text per day. I rarely have more than five such implementations in a file. And it's easy to split my IDE screens to have the variables on one side while writing.
Hey, if it works for you... I was just trying to play the devil's advocate for the writer's side. More seriously though, if you are solo a dev wearing multiple hats, you got to do what it takes to get the project finished. At the end of the day, very few players care if the code is poorly written or optimized, they just want to experience a game. On another note though, if you want to dig deeper into dynamic dialogue dispatch systems, I recommend checking out this that your post reminded me about.
 
  • Like
Reactions: Palos69

Palos69

Member
Game Developer
Feb 8, 2021
149
671
Hey, if it works for you... I was just trying to play the devil's advocate for the writer's side. More seriously though, if you are solo a dev wearing multiple hats, you got to do what it takes to get the project finished. At the end of the day, very few players care if the code is poorly written or optimized, they just want to experience a game. On another note though, if you want to dig deeper into dynamic dialogue dispatch systems, I recommend checking out this that your post reminded about.
That's neat. I will definitely watch that.
 
  • Like
Reactions: Quintillian

osanaiko

Engaged Member
Modder
Jul 4, 2017
2,553
4,638
My response to the concept would be:

+ interesting technical solution

- adding complexity for very little benefit
- harder to review and edit when you or someone else comes back to it later
- probably would make translation more difficult (lines in the translation file would be out of context, and that's if the auto-string extractor even worked correctly)

Thanks for sharing the concept though. Good luck with your renpy/erogame journey.
 
  • Like
Reactions: Palos69