Welcome to the sponsor-exclusive content for the Ren'Py Patreon. Sponsors like you ensure this page exists. Thank you.
A structure shared by many visual novels is a common path in which the player makes multiple choices that increase or decrease statistics associated with different characters. At a certain point, the visual novel compares the statistics, chooses the character with the highest number, and branches to that character’s ending.
When a project has a small number of characters, this is relatively easy to do. For example, if a game has 3 characters (let’s call them Alice, Bianca, and Claire), we can write:
if (alice_points >= bianca_points) and (alice_points >= claire_points):
jump alice_ending
elif (bianca_points >= claire_points):
jump bianca_ending
else:
jump claire_ending
But this is already a bit unruly, and it only gets worse as you add more characters. A fourth character adds three more tests, a fifth four more. If you were to have ten characters, you’d have 45 tests - which gets to be a lot to manage.
Python to the rescue! One of the reasons Ren’Py embeds Python is because it gives us access to a powerful programming language to make problems like these easier. In this article, I’ll show you how I use Python to simplify this structure in a way that makes it easy to add as many endings as you need. I’ll break down what I did step by step to make it more obvious.
Here’s my solution to this problem:
python:
endings = [
(alice_points, 3, "alice_ending"),
(bianca_points, 2, "bianca_ending"),
(claire_points, 1, "claire_ending"),
]
endings.sort()
jump expression endings[-1][2]
This solution has three steps: First, the right kind of data is assigned to the endings variable. Then we sort that data. Finally, we separate out only the data we need and use that to jump to an ending.
In the first statement in the Python block:
endings = [
(alice_points, 3, "alice_ending"),
(bianca_points, 2, "bianca_ending"),
(claire_points, 1, "claire_ending"),
]
the endings variable is set to a list of tuples.
A list, enclosed in square brackets, is a Python data type that contains multiple items in order. A list can be changed after it’s created - it can have items added, removed, or replaced. Relevantly here, a list can also have its items sorted.
Right now, there are three items in the list of endings. We call each of these a tuple. A tuple is another data type that can contain multiple items.
If you are having a hard time getting your head around this concept, picture our list as a filing cabinet. Each tuple is like an file folder inside the cabinet. Each item inside the tuple is like a piece of paper inside the file folder.
The items inside each tuple consist of:
Once we have the endings list, we sort it. That’s done by calling the sort method on a list using:
endings.sort()
The sort method places the tuples in order, from smallest to largest. It decides how to sort them by comparing each component in turn - first it sorts on points, and if two are equal, it sorts them by the tiebreaker. The sort method is in-place - it changes the endings list rather than making a copy.
Python’s square brackets operator performs indexing of a list or a tuple, picking one thing out of it. When the number is zero or greater, it picks out the component which is in that numeric position from the front. So 0 gets the first component, 1 gets the second, and so on. When the number is negative, it starts from the back. -1 gets the last component, -2 the second to last, and so on.
What we have here (endings[-1][2]) first grabs the last tuple from the list (that is, the one with the biggest number of points and tiebreaker). It then takes the third component of the tuple, which is the label of the girl’s ending.
Finally, we use the Ren’Py jump expression statement to jump to the
label. Unlike the usual jump statement, jump expression takes a
Python expression rather than a simple label name. Because of this, we can use the jump expression statement to jump to whatever label that our program chose based on its sorted position in the list.
This structure’s simplicity is revealed when we want to add more endings. Say, for example, we wanted to add in a rule that if the player does not achieve at least five points of favor with a character, they get a bad ending. In our original example, we’d have to add a large amount of complexity.
if (alice_points >= bianca_points) and (alice_points >= claire_points) and (alice_points >= 5):
jump alice_ending
elif (bianca_points >= claire_points) and (bianca_points >= 5):
jump bianca_ending
elif (claire_points >= 5):
jump claire_ending
else:
jump bad_ending
With our new version, we can just add a single short line:
python:
endings = [
(alice_points, 3, "alice_ending"),
(bianca_points, 2, "bianca_ending"),
(claire_points, 1, "claire_ending"),
(5, 0, "bad_ending"),
]
endings.sort()
jump expression endings[-1][2]
This example shows just some of the power even a little bit of Python brings to Ren’Py. Hopefully, it has introduced some concepts that can help you improve your own project. Thanks again for supporting Ren’Py, and I can’t wait to see what you create!