- Dec 19, 2018
- 93
- 546
Background
I'm a software developer who likes to play VNs with the source-code open in another window so I can be sure I'm not missing anything important. This means I see a lot of Ren'Py code. It's always bugged me that many developers track state haphazardly, so I decided to throw up a guide to how developers could (in my opinion) make life easier for themselves in the long run.
Introduction
It felt like this was always going to happen.
Six months into development, six episodes of your game published like clockwork and working hard on episode seven. But that one character. That one minor character who turned into a Patreon favorite. That one character you hadn't been tracking affection for because she was just the bank teller for fuck's sake. You grimace, go back through the code and add relationship-counter updates to every past interaction, and write those terrible words into the changelog.
Counters Bad. Event Logs Good.
Knowing that wendy_affection == 7 tells you that Wendy likes you, but it doesn't tell you why she likes you. The problem with using counters to track game state in a VN is that every time you react to some in-game event by adding to (or subtracting from) a counter, you're throwing away important information about where that number came from. Other ways this can bite you in the ass include:
Start with an empty list:
Now every time the player makes any non-trivial choice, record that they did it in the list. Don't try to second-guess yourself here. If it's in an interaction menu, it's probably worth recording.
Already, you'll be getting simple call-backs to previous occurrences for free.
A good example of a game that uses this technique to good effect is Jessica O'Neil's Hard News, which is constantly making short dialogue insertions and changes based on its log of every decision you've ever made. But for some reason JOHN hasn't taken the next obvious step, which is:
Replace your counters with functions.
Moving these calculations from variables to functions gives you the power to add new ones whenever you want, or to change your mind about the effect of player decisions in a way that is 100% compatible with every existing save-game. You also get handy central locations where you can see every choice that might impact any given statistic, rather than having to global-search through all your scripts to find every place a variable is touched.
"But isn't it more efficient to keep this in a variable rather than recalculate it every time?" Sure in theory, but finding an item in a reasonably-sized list should take microseconds on any computer worth sticking in your pocket. I promise Ren'Py is doing so many far, far more expensive things every time you throw a line of text on the screen that you're not going to notice the difference.
Example Code
This is just the beginning, obviously. I threw together a quick sample Ren'Py project with some examples of how this can work in practice, and some helper-functions to streamline common operations:
I'm a software developer who likes to play VNs with the source-code open in another window so I can be sure I'm not missing anything important. This means I see a lot of Ren'Py code. It's always bugged me that many developers track state haphazardly, so I decided to throw up a guide to how developers could (in my opinion) make life easier for themselves in the long run.
Introduction
It felt like this was always going to happen.
Six months into development, six episodes of your game published like clockwork and working hard on episode seven. But that one character. That one minor character who turned into a Patreon favorite. That one character you hadn't been tracking affection for because she was just the bank teller for fuck's sake. You grimace, go back through the code and add relationship-counter updates to every past interaction, and write those terrible words into the changelog.
But it didn't have to be that way. With a little bit of preparation your players could have boned (or not boned) Wendy without ever knowing it wasn't part of your master-plan all along.* To access the scenes with Wendy, you will need to start a new game.
Counters Bad. Event Logs Good.
Knowing that wendy_affection == 7 tells you that Wendy likes you, but it doesn't tell you why she likes you. The problem with using counters to track game state in a VN is that every time you react to some in-game event by adding to (or subtracting from) a counter, you're throwing away important information about where that number came from. Other ways this can bite you in the ass include:
- You've been scrupulously tracking how virtuous your MC has been behaving, but you realize too late that you also want to know how virtuous they've been behaving while other people were watching.
- You mistakenly wrote += instead of -= in that conversation branch where you told your best friend she looks like a pig, and didn't notice until after you released the game
- You want to make a callback to something that may have happened in a previous episode, but you didn't record whether it happened because it wasn't important at the time
- You want your game to be unpredictable, but anyone who wants to know what their decisions mean in future episodes can look at the script to see what variables were updated today.
Start with an empty list:
Python:
default choices = []
Python:
menu:
"Punch him in the face!":
choices.append("ep4bosspunched")
"You knock your boss out cold. This might look bad in your performance review."
"Slap him on the back":
choices.append("ep4bossslapped")
"Choking back bile, you pretend to laugh along with what [boss] just said about your mother."
Python:
mc "I don't know what you're getting so uptight about. Everybody does it."
if "ep2spiedlonger" in choices:
mc "And you still haven't explained to me what you were doing with that goat in the bathroom."
Replace your counters with functions.
Python:
init python:
def boss_friendliness():
bf = 0
if "ep4bosspunched" in choices:
bf -= 5
if "ep4bossslapped" in choices:
bf += 2
if "ep6reacharound" in choices:
bf += 10
return bf
"But isn't it more efficient to keep this in a variable rather than recalculate it every time?" Sure in theory, but finding an item in a reasonably-sized list should take microseconds on any computer worth sticking in your pocket. I promise Ren'Py is doing so many far, far more expensive things every time you throw a line of text on the screen that you're not going to notice the difference.
Example Code
This is just the beginning, obviously. I threw together a quick sample Ren'Py project with some examples of how this can work in practice, and some helper-functions to streamline common operations:
You must be registered to see the links
Last edited: