Skip to content

Instantly share code, notes, and snippets.

@shawna-p
Last active July 5, 2021 21:51
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shawna-p/09a689c7a962d2882b30d58196711900 to your computer and use it in GitHub Desktop.
Save shawna-p/09a689c7a962d2882b30d58196711900 to your computer and use it in GitHub Desktop.

Complete Guide to Ren'Py Variables

This guide is intended to take you through the most common ways to use variables in visual novel games made with Ren'Py, starting with the fundamentals. A quiz at the end will test your knowledge on the variable types introduced here.

It's recommended that you have a passing familiarity with conditional statements (if/elif/else) and choice menus (menu:) in Ren'Py to get the most out of the examples used below. These sections in the Ren'Py quickstart should be enough to get you up to speed:

Python and If Statements

Menus, Labels, and Jumps

What are Variables?

Variables are one of the basic building blocks of programming. They keep track of values you'd like to use during your program. In coding, a value is a term for any kind of information you'd like to store and use during a program. Values fall into one of two main categories: constants and variables.

A constant, as the name suggests, should have a constant value. It shouldn't change over the course of a game. In Ren'Py, you use define to declare (set up) a constant e.g. define dark_brown = "#604430". Other built-in features in Ren'Py are expected to be constants too, with some exceptions. For example, the GUI[1] styles which are used to customize the appearance of the game UI, once defined, aren't expected to change. Ren'Py does not keep track of constants in save files, since they're not expected to change from their initial value.

A variable, on the other hand, is a value that can be updated over the course of a game. You use default to declare a variable in Ren'Py e.g. default xia_love_points = 0. Most things you use in the script will be variables, so we'll cover those first.

[1] GUI stands for Graphical User Interface. It refers to things like the buttons and menus you see on-screen when interacting with a program like a phone app or a game. Similarly, UI stands for User Interface and is a slightly more broad term that encompasses any systems the user uses to interact with a program. For something like a Ren'Py game, these terms are mostly interchangeable.

Types of Variables

There are lots of values that you can assign to variables. The most common ones are listed below.

Simple Variable Types:

  • Booleans - A boolean (often called a bool for short) is one of either True or False.
  • Integers - Numbers without decimal places like 1, -72, 0, and 516487. May be called int for short.
  • Floats - Numbers with decimal places, like 0.0, 3.14159, -500043.2 and 0.00004. Float is short for Floating Point Number, named for the fact that the decimal point can "float" anywhere in the number, since there isn't a fixed number of digits before or after the decimal point e.g. 100.05 has the decimal point after the third number but 4.9543 has it after the first number, even though both have 5 digits.
  • Strings - Words you put inside quotes. Python and Ren'Py allow you to declare strings with either apostrophes ('like this') or with quotations ("like this"). Anything inside quotes is a string. So "1" is a string, not an integer, because it's in quotes. Other example strings are "This is a string", 'Pi is approximately equal to 3.14159', and "!!!!".
  • None - This is actually a very special kind of value, the None value. In other programming languages, None is approximately equivalent to NULL. For the most part, you can think of None as a bit like a special False value.

Complex Variable Types:

These won't be covered in this initial tutorial, but are included here for reference since they're very useful.

  • Lists - This is basically what it says; a list made up of other variable types. All you need to do to make a list is put it inside square brackets []. Each item in the list is separated by a comma. Lists don't need to contain all the same variable types, either e.g. ["This", 0, "is a valid list"] or [True, 3.14, 15, False].
    • Lists can even contain other variables - that is, you could have something like default name = "MC" and then a list like [name, 24, "student"] where the first item in the list, name is the variable name with the initial value "MC".
    • Note that declaring lists with [] is different from using [] inside dialogue, where "Hello, [name]!" will display as "Hello, MC!" in-game. In dialogue, this tells Ren'Py you'd like to substitute the variable's value inside the dialogue, so it doesn't literally print "Hello, [name]!" and instead shows the value of name.
  • Dictionaries - Dictionaries are made up of key-value pairs. You look up a dictionary entry with its key and get the value back, just like how you look up a word (the key) in a real-life dictionary and get its definition (the value).
    • Dictionary keys can't be variables (since it wouldn't make sense for the word in a dictionary to spontaneously change while still having the same definition), but dictionary values can be variables (like how real-world dictionaries may update the definition of a word after it acquires new meaning).

And then there are some other variable types we won't be covering here, but are listed here in case you'd like to look up more about them:

  • Classes
  • Sets
  • Tuples

Using Variables in your Script

Before we cover each variable type and when to use it in the next section, you first need to know how to set them up in your game. The most important thing to know about setting up variables is that you should always be using default to declare them (set them up) before the game starts. In previous Ren'Py versions, you'd declare the variable with a line of Python (using $ var = True-style statements), usually in the start label. However, recent versions use default to declare the variables even earlier, before the game starts.

default is essential for save compatibility and reducing errors. If Ren'Py has never seen a variable before, default gives it a default value so you won't get any errors that a variable is undefined. Consider the following code:

# Version 1
label start:
    menu:
        "Go to the store":
            $ went_to_store = True
            "You went to the store."
        "Go to the park":
            $ went_to_park = True
            "You went to the park."
    "You had a nice trip."
    if went_to_store:
        "You didn't buy anything, though."
    elif went_to_park:
        "You sat on a bench by the pond."
    "All in all, it was an alright day."
    return

If the player goes to the store, went_to_store is given the value True. When we get to the line if went_to_store, since went_to_store is True, we see the line "You didn't buy anything, though."

On the other hand, if the player goes to the park, went_to_park is given the value True. When we get to the line if went_to_store, Ren'Py has not seen the variable went_to_store yet, so it causes an error.

A slightly improved solution might look like:

# Version 2
label start:
    $ went_to_store = False
    $ went_to_park = False
    menu:
        "Go to the store":
            $ went_to_store = True
            "You went to the store."
        "Go to the park":
            $ went_to_park = True
            "You went to the park."
    "You had a nice trip."
    if went_to_store:
        "You didn't buy anything, though."
    elif went_to_park:
        "You sat on a bench by the pond."
    "All in all, it was an alright day."
    return

which would solve the issue earlier where went_to_store wasn't a known variable for players who went to the park.

However, let's say that you released a demo of your game which had the first code (Version 1). A player went through your game and went to the park. They then made a save file while at the line "You had a nice trip." You inform the player that there was an error with the code and you've updated it to Version 2. The player updates to the new version, then loads their save game and clicks to continue. NameError: name 'went_to_store' is not defined. What happened?

Even though the player updated their code, in their save game, they've already passed the point where the new line $ went_to_store = False was added, so it doesn't run and the variable still isn't assigned a value. They run into the same issue as Version 1, but at least in this version they can begin a new game from the start and won't get the error message.

Luckily, there's a solution to both of these problems, and that's default:

# Version 3
default went_to_store = False
default went_to_park = False
label start:
    menu:
        "Go to the store":
            $ went_to_store = True
            "You went to the store."
        "Go to the park":
            $ went_to_park = True
            "You went to the park."
    "You had a nice trip."
    if went_to_store:
        "You didn't buy anything, though."
    elif went_to_park:
        "You sat on a bench by the pond."
    "All in all, it was an alright day."
    return

Now when our player loads up their save file at the line "You had a nice trip", Ren'Py sees that this save file doesn't have a value for went_to_store yet, so it gives it its default value of False. It does have a value for went_to_park, so it leaves that variable alone. Now when the player clicks to continue, they will skip the line "You didn't buy anything, though." since went_to_store is False and move on to the line elif went_to_park without throwing any errors. went_to_park is True, so it shows the line "You sat on a bench by the pond." Success!

In summary, you will use default to set up the initial value of your variable before the game starts, and use $ to change that value during gameplay. Note that default and define go outside of labels; that is, they should occur during initialization[2] and not inside of a label. So the following two are not the same:

# Correct! This is run before the game starts.
default watched_tv = False
label start:
    menu:
        "Watch TV":
            $ watched_tv = True
        "Go to sleep":
            $ watched_tv = False
    return
label start:
    # Incorrect! If you want this to be set up
    # before the game starts, default must be
    # outside of any labels.
    default watched_tv = False
    menu:
        "Watch TV":
            $ watched_tv = True
        "Go to sleep":
            $ watched_tv = False
    return

[2] Initialization refers to the period of time before the game starts when the game is getting set up. When you hit Launch Game on the Ren'Py launcher, it takes it a few seconds before your game actually opens. The main tasks performed during that time are initializing values like variables and images, loading script files and menu screens into memory to display them, etc. To initialize a variable means to give it some value during this time, as opposed to giving it a value after the game has already started.

Matching variables to use cases

Now we'll go over what situations you might want to use each variable type for, and then have a quick quiz to see what you've learned.

Booleans

Booleans are usually the first variable type most people find out about, and are probably the most common across Ren'Py games. As mentioned, they are one of two values: True or False.

In a default Ren'Py game, you'll see booleans already in use in several places. For example, in screens.rpy there is a screen called screen navigation, and a line inside there that says if main_menu:. main_menu is a boolean variable that Ren'Py sets to be True if the player is on the main menu and False if they're in-game. In this case, if the player is on the main menu, they should be shown a Start button to start the game. If they're already in-game though, there's no need to show them a start button.

Places where you SHOULD use Booleans:

  • Tracking player actions e.g. did they give Ashwin a gift? True/False
    • gave_ashwin_gift = True
  • Keeping track of binary choices e.g. did the player lie about knowing who Xia is?
    • lied_about_xia = False
  • Toggling preferences e.g. do they have animations toggled on or off?
    • persistent.animations_on = True

Places where you should NOT use Booleans:

  • When you have 2+ values that are mutually exclusive[3] e.g. player_height_tall = True, player_height_average = False, player_height_short = False
    • In this case, what happens if player_height_tall AND player_height_short are somehow both True? What happens if all three are False? Because they are separate variables, there's a lot of room for error. It's better to use one of the other variable types mentioned below.
  • When tracking points e.g. player_zoran_love1 = True, player_zoran_love2 = True
    • It's better to use something like an integer to handle this and sum point totals during the game

Example boolean use:

default player_ate_apple = False
label start():
    "One day, Snow White was visited by a strange old lady selling apples."
    menu:
        "Eat the apple":
            $ player_ate_apple = True
            "She ate the apple. It was lovely and red and juicy."
        "Don't eat the apple.":
            $ player_ate_apple = False
            "She shook her head at the old woman and decided not to take the apple."
    "After the woman left, Snow White returned to her cleaning."
    if player_ate_apple:
        "She began to feel quite unwell and fainted in the kitchen."
        jump ate_apple
    else:
        jump end_day_1

This is a good use for boolean values as there are only two possibilities - either the player ate the apple (True) or they didn't (False). This doesn't conflict with anything else they could have done. You may also note that the $ player_ate_apple = False line under the "Don't eat the apple." choice is optional because player_ate_apple begins with a value of False. So, unless it is otherwise changed, it will still be False whenever you go to check it. However, it's good to add it for clarity and to prevent accidental errors in case player_ate_apple was somehow set to True before reaching this choice menu.

You may also notice that there is a conditional statement there - the thing that says if player_ate_apple. This is a useful feature of booleans is that you can just check if boolean_variable to see if boolean_variable is True.

The else statement basically says "if the condition(s) before this statement were NOT true, then do this". So in the case of

if player_ate_apple:
    jump ate_apple
else:
    jump end_day_1

when player_ate_apple is False, the line if player_ate_apple isn't True (the player didn't eat the apple). So the program moves on to the next condition, which is the else line and jumps to the end_day_1 label instead.

[3] Mutually exclusive means that of two or more related situations, if one of them is True then the other(s) must be False.

In everyday language, something like the kitchen light being on or off is mutually exclusive: if the light is on it cannot also be off, and if it is off it cannot also be on.

Click for more explanation on mutually exclusive values

More than one "situation" may be mutually exclusive. For example, in a game, perhaps you might have 1, 2, or 3 cookies in your inventory. If you have 1 cookie you can't also have 2 cookies or 3 cookies and so on. Either you have one cookie, or you have two cookies, or you have three cookies. Or perhaps the player can choose a suit of cards to be trump in a card game - choosing hearts as trump means clubs, diamonds, and spades cannot be trump.

Unless there are only two possibilities, or the options can be simplified to two possibilities (such as "either you have cookies or you don't" and the number of cookies doesn't matter, or "either hearts is trump or it isn't" and the specific non-heart suit that is trump doesn't matter), then you should use a different variable type like integers (for the cookies) or strings (for the card suits) to prevent situations where you end up with something like

default has_one_cookie = True
default has_two_cookies = True
default has_three_cookies = True

What does the above code even mean? How many cookies does the player really have? One? Three? Six? As you'll see in the example for integers, something like

default num_cookies = 1

is better because there's no way for a player to simultaneously have 1 and 2 cookies.

Integers

Integers are excellent for tracking any sort of numerical values in your game, such as love point totals for characters or the number of times the player has gone to the park. Remember that an integer is any non-decimal number e.g. -5, 20, 0, or 124083

In a default Ren'Py game, integers are used for some preferences, such as the number of characters displayed per-second (also known as CPS characters per second), and for lots of defined GUI values like the text size of in-game dialogue or the width (in pixels) of the dialogue box.

Places where you SHOULD use Integers:

  • When keeping track of collected points e.g. how many affection points do they have with Xia?
    • xia_love_points = 0 and xia_love_points += 1
  • When counting how many times a certain action has been done e.g. how many times has the player visited the park?
    • player_park_visits = 1
  • Tracking the number of actions the player has left before time runs out e.g. there's only enough time to dance with two people at the ball. Once you've danced with two people, the ball is over.
    • num_dance_partners = 2
  • Tracking how many items a player is holding or has acquired e.g. How many life jackets did the player manage to get?
    • num_life_jackets = 3

Places where you should NOT use Integers:

  • Variables where there are only two outcomes e.g. either they have enough life jackets or they don't, and there's no way to acquire/lose just one or two (instead of all the life jackets)
    • In this case, a boolean would be more appropriate (see the note on mutual exclusivity above[3])
  • Keeping track of multiple distinct but diverse options e.g. the player can be either tall or short. You use a variable like player_height = 0 for short and 1 for tall
    • This is more subjective; it's not a bad choice per se, but in the case of player heights, it can make your code harder to read by using numbers for non-numerical concepts. Additionally, what would happen if you wanted to allow players to be "average" height? Do you use 0.5? 2? In this case, a better choice would be to use strings like player_height = "short", player_height = "average" etc
    • However, if you wanted to represent height as actual numerical values like 150cm, then an integer would be a good choice, especially if you're doing comparisons like if player_height < 150 -> "You couldn't reach the top shelf."

Example integer use:

default zoran_love_points = 0
label start():
    "Zoran shuffles awkwardly, looking down at his feet."
    menu:
        "Make a joke.":
            $ zoran_love_points += 1
            "He smiles when you finish telling the joke."
        "Tell him to get over himself.":
            $ zoran_love_points -= 1
            "He looks almost angry at you."
            "Then he gets up and leaves."

Ren'Py has a couple of useful shortcuts for setting integer values. After you've set up your variable with a statement like default zoran_love_points = 0 you can increase it in several ways:

$ zoran_love_points = zoran_love_points + 2
$ zoran_love_points = 2
$ zoran_love_points += 2

The first statement adds two points to whatever the existing value of zoran_love_points is. If it starts at 0, then after $ zoran_love_points = zoran_love_points + 2 runs, zoran_love_points will be equal to 2.

The second statement directly sets zoran_love_points to the value 2. In the case of love points, this usually isn't what you want because you want to tally the total points the player has earned with Zoran, not overwrite the value. If a player was able to get 1 point with Zoran before reaching this statement, they should get two additional points for a total of 3. However, this can be useful in other situations, like when resetting the value of a variable.

The third statement is actually identical to the first, just in a shorter form. += tells Ren'Py you want to take the original value of the variable named on the left and add the number on the right to it. So, if zoran_love_points started as 3, then zoran_love_points += 2 will cause zoran_love_points to be equal to 5.

This also applies to negative numbers, which use -= instead of +=. To remove 1 point from zoran_love_points, you can do $ zoran_love_points -= 1.

Floats

Floats are very similar to integers, but are used for numbers with decimal places like 3.1415, -19.0, and 0.2

In a default Ren'Py game, floats are mostly used for alignment properties of GUI elements like the choice buttons. The alignment property, align for short, positions things like buttons on the screen. It uses floats to represent a percentage of the screen rather than a set number of pixels. So, a value like 0.5 indicates the alignment should be 50% of the way down/across the screen (aka centered).

Places where you SHOULD use Floats:

  • Tracking numbers which have decimal places, such as how much money the player has (including cents).
    • money = 100.00
  • Using decimal places as percentages
    • grade = 30.0/40.0 or grade = 0.9

Places where you should NOT use Floats:

  • Anywhere that you need distinct values or need to compare exact numbers.
    • Floats can't be exact due to the infinite nature of decimal points. When it comes to numbers with a lot of decimal places like pi, the computer has to limit how many digits it will use so it can do calculations, which means that there will be some rounding errors.
    • So if you find yourself comparing two floats like if grade == 0.9, you may want to rethink if you should use integers OR check for a range instead e.g. if grade >= 0.85 and grade <= 0.95
  • Same rules where integers would be bad also generally apply to floats

Example float use:

default cash = 10.00
define flower_price = 4.99
define necklace_price = 8.99
define postcard_price = 2.49
label start():
    "You go to the market to make some purchases."
    menu market:
        "What will you buy?"
        "Flower ($[flower_price])" if cash >= flower_price:
            $ cash -= flower_price
        "Necklace ($[necklace_price])" if cash >= necklace_price:
            $ cash -= necklace_price
        "Postcard ($[postcard_price])" if cash >= postcard_price:
            $ cash -= postcard_price
        "Don't purchase anything else":
            jump leave_market
    jump market
label leave_market():
    "You finished buying everything you needed. You now have $[cash] left."

Before the start label, we define some set prices for items at the market. These values don't change during gameplay, but they're useful to compare against instead of writing out the prices each time, and if we decide later that a flower should cost $5.50 instead of $4.99, we only have to update the initial definition to change it everywhere that price is used.

Then, at the market, each choice checks if the player has at least enough cash to cover the purchase of a given item (if cash >= flower_price). If so, the player can purchase that item and the funds are deducted from the total amount of cash that they have ($ cash -= flower_price). The [var] format inside of the choice text means that Ren'Py will substitute[4] in the value of the given variable instead of literally displaying [var]. So the player will initially see a menu which looks like:

Flower ($4.99)
Necklace ($8.99)
Postcard ($2.49)

This part works very much like integers, but for floats you don't want to do direct comparisons like if cash == 4.99. Instead you can do if cash > 4.98 and cash < 5.0 if you need to do a tight comparison, but most cases when you're doing float comparisons should be something more like if cash >= 4.99. This is because you can't guarantee that a float will be the precise number you give it due to rounding and limits in representing infinite decimal places.

[4] The formal coding term for this sort of variable substitution is interpolation. So, for something like:

$ name = "MC"
"Hello there, [name]."

the variable name is interpolated in the sentence "Hello there, (...)." so that the player sees the line "Hello there, MC."

Strings

Strings are one of the most flexible variable types. They'll contain just about anything so long as you put it in quotes. As a result, they're useful for holding a variety of information.

In a default Ren'Py game, strings are used for things like defining colour codes to use for text (e.g. "#ff0000") and for the name of your game and what the current version is. They're also used to specify file paths for things like images, sound effects, and font files.

Places where you SHOULD use Strings:

  • When you have a lot of possible values for a variable that correspond to words e.g. keeping track of what colour hair the player has
    • hair_color = "black"
  • When you want to use a variable in dialogue during the script e.g. player_height = "short" and then p "Yeah, I'm pretty [player_height].". This would display to the player like "Yeah, I'm pretty short."
  • When a variable corresponds to an image or filename you want to use e.g. xia_hair = "long" and then image xia happy = "Characters/xia/[xia_hair].png". Like variable substitution in dialogue, this will search for an image at images/Characters/xia/long.png. If you later change Xia's hair to be short ($ xia_hair = "short"), then the game would instead look for the file images/Characters/xia/short.png.
  • If you want to have branching storylines reconvene at some common event, then branch off again depending on past choices.
    • For example, the player can be on Xia, Zoran, or Ashwin's romance routes. There is a common event to all three routes where the player spends the day at work. After they finish their job, the next scene depends on who they are romancing. Before you jump to the common event label, you can set up a variable like $ next_scene = "zoran_scene_4" at the end of each specific route, and then at the end of the common event you can write jump expression next_scene to jump to the label saved in the next_scene variable (so for Zoran it would jump to a label named zoran_scene_4)
      • jump expression is a special variation of the jump statement that lets you use a string to get the label name to jump to.

Places where you should NOT use Strings:

  • When you're keeping track of counters (numbers that increase/decrease during the game, like affection points or counting how many times the player has said a swear word) or other numerical values
    • Usually integers are better for this. Sometimes floats are also helpful.
  • If the variable only has two possible values in a yes/no-style pattern
    • An exception to this is if you use the variable to determine which image to use for a character e.g. the character has two different outfits, their work outfit and their normal outfit. You have a variable ashwin_outfit = "work" or ashwin_outfit = "normal". Even though Ashwin is either wearing/not wearing their work uniform, you might use this variable inside an image definition like image ash = "Characters/Ashwin/[ashwin_outfit].png" so it makes sense to keep it as a string so you can take advantage of Ren'Py's variable substitution. With the above format, Ren'Py will either look for the image images/Characters/Ashwin/work.png or images/Characters/Ashwin/normal.png depending on the value of ashwin_outfit.

Example string use:

default gift = None
label start():
    "At the mall, you decided you wanted to buy Ashwin something."
    menu:
        "Buy them a bath bomb.":
            $ gift = "bath bomb"
        "Buy them a self-help book.":
            $ gift = "self-help book"
        "Buy them a digital camera.":
            $ gift = "digital camera"
    "Later that day, you were able to give them the [gift] you bought earlier."
    "They liked it a lot."

The player can only get one gift for Ashwin, so it makes sense to store this variable as a string. Even more useful is that we can have Ren'Py substitute it in the sentence after the menu so that if the player bought Ashwin a bath bomb the line reads "Later that day, you were able to give them the bath bomb you bought earlier." This saves us having to write out three separate sentences for each gift with only the name of the gift changed, but you can also customize later text by checking for conditions like if gift == "bath bomb" (conditionals are covered in more detail in a later section).

Strings vs Booleans

It's very common for people to use booleans (True/False) instead of strings (words in quotes) in their script when strings are a better choice to reduce errors while still remembering multiple options. Consider the following situation:

During your game, a round of rock-paper-scissors is played which determines who gets to eat the last cookie. The player gets to choose which of rock, paper, or scissors they would like to play, and if they win, they earn the cookie. You're keeping track of the results like so:

default npc_rock = False
default npc_paper = False
default npc_scissors = False
default player_wins = False
label start:
    # Round 1
    # You could do some logic here, like using
    # renpy.random to randomly select which of
    # rock/paper/scissors the non-player character
    # (NPC) should use, but we'll just set it directly.

    $ npc_paper = True

label rock_paper_scissors:
    # Show which move the NPC chose
    show paper_img
    menu:
        "Rock":
            if npc_rock:
                "It's a tie! Go again."
                jump rock_paper_scissors
            if npc_paper:
                $ player_wins = False
            if npc_scissors:
                $ player_wins = True
        "Paper":
            if npc_rock:
                $ player_wins = True
            if npc_paper:
                "It's a tie! Go again."
                jump rock_paper_scissors
            if npc_scissors:
                $ player_wins = False
        "Scissors":
            if npc_rock:
                $ player_wins = False
            if npc_paper:
                $ player_wins = True
            if npc_scissors:
                "It's a tie! Go again."
                jump rock_paper_scissors

    if player_wins:
        "You won!"
        jump later_on
    else:
        "You lost!"
        jump no_cookie_for_you

There's a lot happening here, but most of it is just repeat code for all the options. For example, let's look at the code under the "Rock" choice in the menu:

"Rock":
    if npc_rock:
        "It's a tie! Go again."
        jump rock_paper_scissors
    if npc_paper:
        $ player_wins = False
    if npc_scissors
        $ player_wins = True

First, we check if npc_rock aka did the NPC play "Rock"? If so, and the player chose rock, then it's a tie and we'll jump to the rock_paper_scissors label so the player can try again, no penalty.

Next, we check if npc_paper (did the NPC play "Paper"?). If they did, and our player chose rock, then the player loses, so we set player_wins to False.

Finally, we check if npc_scissors (the NPC played scissors). The player played rock, so they win. We set player_wins to True and continue on.

After the player has played the game, the game continues as usual, either jumping to the label later_on if the player won, or the label no_cookie_for_you if the player lost.

All seems pretty normal so far. Later in your script, the characters decide to play another game of rock-paper-scissors, this time for the last piece of pizza. You decide to use the same format for this new round:

label later_on:
    # Set up the NPC's move
    $ npc_rock = True

label rock_paper_scissors2:
    show rock_img
    # This menu is the same as the original
    menu:
        "Rock":
            if npc_rock:
                "It's a tie! Go again."
                jump rock_paper_scissors2
            if npc_paper:
                $ player_wins = False
            if npc_scissors:
                $ player_wins = True
        "Paper":
            if npc_rock:
                $ player_wins = True
            if npc_paper:
                "It's a tie! Go again."
                jump rock_paper_scissors2
            if npc_scissors:
                $ player_wins = False
        "Scissors":
            if npc_rock:
                $ player_wins = False
            if npc_paper:
                $ player_wins = True
            if npc_scissors:
                "It's a tie! Go again."
                jump rock_paper_scissors2

    if player_wins:
        "You won!"
        jump eat_the_pizza
    else:
        "You lost!"
        jump no_pizza_for_you

Can you spot what the issue is here?

We forgot to reset the NPC's original choice! So by the time we get to the rock_paper_scissors2 label, both npc_paper and npc_rock are True!

What does that even mean? You can't play both rock and paper at the same time. Now let's look at what happens the second time when the player chooses Scissors:

"Scissors":
    if npc_rock:
        # This is True!
        $ player_wins = False
    if npc_paper:
        # This is also True!
        $ player_wins = True
    if npc_scissors:
        "It's a tie! Go again."
        jump rock_paper_scissors2

Since both npc_rock and npc_paper are True, player_wins is first set to False, and then set to True! Even though the NPC was supposed to play rock and we showed rock_img to the player, the player now inexplicably wins the round even though they played scissors when the NPC played rock (and rock beats scissors).

One way to try to fix this is to make sure you reset each of the rock/paper/scissors variables before starting a new game e.g.

label later_on:
    # Set up the NPC's move
    $ npc_rock = True
    $ npc_paper = False
    $ npc_scissors = False

But this still leaves us vulnerable to accidental configurations like more than one value being True or all three being False if we're not careful, and it's already a lot of extra typing even though we only have three options. This is where strings come in!

Here's a rewrite of the rock-paper-scissors game, using strings:

default npc_move = "rock" # New!
default player_wins = False
label start:
    # Round 1
    # You could do some logic here, like using
    # renpy.random to randomly select which of
    # rock/paper/scissors the non-player character
    # (NPC) should use, but we'll just set it directly.

    $ npc_move = "paper" # New!

label rock_paper_scissors:
    # Show which move the NPC chose
    show paper_img
    menu:
        "Rock":
            if npc_move == "rock":
                "It's a tie! Go again."
                jump rock_paper_scissors
            if npc_move == "paper":
                $ player_wins = False
            if npc_move == "scissors":
                $ player_wins = True
        "Paper":
            if npc_move == "rock":
                $ player_wins = True
            if npc_move == "paper":
                "It's a tie! Go again."
                jump rock_paper_scissors
            if npc_move == "scissors":
                $ player_wins = False
        "Scissors":
            if npc_move == "rock":
                $ player_wins = False
            if npc_move == "paper":
                $ player_wins = True
            if npc_move == "scissors":
                "It's a tie! Go again."
                jump rock_paper_scissors

    if player_wins:
        "You won!"
        jump later_on
    else:
        "You lost!"
        jump no_cookie_for_you

Now if we set npc_move to "rock" later on for the second round, it's impossible to have conflicts with the previous game of rock-paper-scissors, since either npc_move is equal to "rock" or it's equal to "paper" or "scissors", and it's impossible for it to be equal to more than one. Note that == compares two values directly to see if they're equal/the same, so in the menu a line like if npc_move == "rock" is True if npc_move has the value "rock" (since "rock" == "rock"). if npc_move = "rock" with only one = is incorrect programming syntax and will cause an error, because only one = is used to set the value of a variable, not to compare with other values.

Quiz time

Amazing, you got this far! Now we have a few questions to see if you understand when to use each variable type.

Question 1

You're keeping track of whether the player has a part-time job. If the player does have a part-time job, sometimes the script will mention that they're not working today, or that they just finished a shift.

Which variable type should be used?

  1. Boolean (True/False)
  2. Integer (non-decimal numbers)
  3. Float (numbers with decimals)
  4. String (numbers/symbols/letters inside quotes)
Click for answer

The best answer is 1. Boolean. In this case, we don't care what job the player has, just whether they have a job or not. The variable would look something like default has_job = False and can be updated with lines like $ has_job = True.

Integers and floats aren't suitable here because we aren't tracking any kind of numerical value.

A string would be the best choice if we wanted to know what kind of job the player had. For example, if dialogue changed based on whether the player had a job at the mall vs. at a book store, then it would make sense to have something like player_job where player_job could be one of None, "mall", or "book store".

Question 2

The player can choose pronouns at the start of the game (he/him, she/her, or they/them). Sometimes other characters will use these pronouns when talking about the player. You want to write a sentence like "Did she say she knew about it?" where "she" uses the player's pronouns.

Which variable type should be used?

  1. Boolean (True/False)
  2. Integer (non-decimal numbers)
  3. Float (numbers with decimals)
  4. String (numbers/symbols/letters inside quotes)
Click for answer

The best answer is 4. String. There are three possible sets of pronouns which are mutually exclusive and you want to use them in dialogue, so a string is the way to go (and in this case, likely several strings). One possible way to handle this dialogue, for example, might be:

default they = "they"
default them = "them"
label start:
    menu:
        "She/her pronouns":
            $ they = "she"
            $ them = "her"
        "He/him pronouns":
            $ they = "he"
            $ them = "him"
        "They/them pronouns":
            $ they = "they"
            $ them = "them"
    "Xia" "Did [they] say [they] knew about it?"

This will correctly display as "Did she say she knew about it?" for a player with she/her pronouns (note that the variable name itself is they, which is sometimes set to the string "they" but may be set to another value like "he"). Further variables may need to be defined, particularly if a sentence requires verb conjugations (e.g. "she is going" vs "they are going"). Using strings will allow the pronoun variables to be used directly in sentences and also prevents accidental mixing-and-matching of pronouns.

Question 3

In your game, the player can make both good and bad choices. You want to check if they've made more good choices than bad choices to see which ending they get.

Which variable type should be used?

  1. Boolean (True/False)
  2. Integer (non-decimal numbers)
  3. Float (numbers with decimals)
  4. String (numbers/symbols/letters inside quotes)
Click for answer

The best answer is 2. Integer. Depending on how your game is set up, you can either have one variable like default ending_points = 0 which you then adjust via

menu:
    "A bad end choice.":
        $ ending_points -= 1
    "A good end choice.":
        $ ending_points += 1

And at the end, you can check if the player has a positive number of points (indicating they made more good end choices than bad) or a negative number (indicating they made more bad end choices than good).

Alternatively, if you have several ranges of endings based on how many good/bad end choices the player made, you might have two variables like default good_end_points = 0 and default bad_end_points = 0. You'd then adjust them like

menu:
    "A bad end choice.":
        $ bad_end_points += 1
    "A good end choice.":
        $ good_end_points += 1

It's fine to have two variables in this case since the two are not mutually exclusive. While you may only get one of either good or bad end points for any given choice, getting more bad end points doesn't affect the number of good end points you can earn.

For your ending you could check something like

if bad_end_points > 10:
    # The player made more than 10 bad choices
    jump super_bad_end
elif good_end_points > 10:
    # The player made more than 10 good choices
    jump excellent_end
elif bad_end_points > 5:
    # The player made 6-10 bad choices
    jump bad_end
elif good_end_points > 5:
    # The player made 6-10 good choices
    jump good_end
else:
    # The player didn't get more than 5 good or bad points
    jump normal_end

Question 4

You're trying to keep track of how the player reacted to Xia at the beginning of the game. There are three possible reactions - anger, surprise, and excitement. The player can only react with one of those emotions (no combinations). If you reacted with anger, Xia will bring that up later in the story.

Which variable type should be used?

  1. Boolean (True/False)
  2. Integer (non-decimal numbers)
  3. Float (numbers with decimals)
  4. String (numbers/symbols/letters inside quotes)
Click for answer

The best answer is 1. Boolean. While there are three possible reactions, only one of them matters to bring up in the story later - the anger reaction. For this reason, it makes sense to have a variable like xia_reaction_angry = False which gets set to True if the player reacts angrily.

However, if you wanted Xia to bring up the player's reaction and comment on it depending on the reaction, then you would use strings. There are more than two values and it's not a yes/no-style variable, so in that case it wouldn't make sense to use booleans. The player can't be both angry and surprised at seeing Xia, so you should have one variable keep track of which reaction the player had. In this case, the variable would look more like default xia_reaction = "surprised" and then you'd set it to something like $ xia_reaction = "angry" or $ xia_reaction = "surprised" during the game. Notably, the variable begins as "surprised" so that there isn't a situation in which the player didn't react to Xia.

What you don't want in either case is something like

default xia_reaction_angry = False
default xia_reaction_surprised = False
default xia_reaction_excited = False

This is because the reactions are mutually exclusive, so the player can only react with one emotion out of the three. This kind of setup is vulnerable to unintentional configurations like both xia_reaction_angry and xia_reaction_excited being True, or all three being False when you go to check which reaction the player had. If strings are used instead, it's impossible for the player to have had more than one type of reaction.

Question 5

The player has the option while shopping to buy Zoran a book: Pride & Prejudice or Frankenstein. If they bought Zoran a book, he will later tell the player what he thought of it and comment on the plot.

Which variable type should be used?

  1. Boolean (True/False)
  2. Integer (non-decimal numbers)
  3. Float (numbers with decimals)
  4. String (numbers/symbols/letters inside quotes)
Click for answer

The best answer is 4. String. There are two possible books, Pride & Prejudice and Frankenstein, which might mean that a boolean seems like a good choice, but the player also has the option to not buy Zoran a book at all, so there is a third option, not buying a book. Since these options are all mutually exclusive - either the player didn't buy Zoran a book, or they bought him one of Pride & Prejudice or Frankenstein - a string offers the best way to keep track of all three possibilities.

We'd need at least two booleans to keep track of all three possibilities, which is bad because only one situation can happen at a time so we don't want more than one variable. You could also use an integer here, but assigning numbers to book titles will make your code harder to read and remember, so a string is the best choice.

default bought_zoran_book = False
label start:
    menu:
        "At the store, you see some books."
        "Buy Pride & Prejudice for Zoran.":
            $ bought_zoran_book = "Pride & Prejudice"
        "Buy Frankenstein for Zoran.":
            $ bought_zoran_book = "Frankenstein"
        "Suggest you buy some ice cream together.":
            $ bought_zoran_book = False
    "It was a really nice day."
    if bought_zoran_book == "Frankenstein":
        "Zoran" "You know, it's really weird everyone thinks Frankenstein is the name of the monster, not the scientist."
    elif bought_zoran_book == "Pride & Prejudice":
        "Zoran" "I love the slow burn romance in this one."
    else:
        "Zoran" "Thanks again for the ice cream."
    return

Flow Chart

And finally, a flow chart to give you a rough idea of when to use each variable type.

  1. Does the thing you're keeping track of involve numbers or increasing/decreasing values? (e.g. increasing/decreasing number of items held, keeping track of personality points, using an energy system to determine if the player can perform an action, etc)
    • Yes -> Go to #2
    • No -> Go to #3
  2. (It involves numbers) Will you need decimal places? (e.g. for things like percentages or money amounts that involve cents)
    • Yes -> Floats
    • No -> Integers
  3. (It does not involve numbers) How many possibilities are you keeping track of?
    • Two -> Go to #4
    • More than two -> Go to #6
  4. (Only two possibilities) Will you be using this variable directly in dialogue or as part of an image definition? e.g. "It's [player_eye_color] because I wanted it to match your eyes." or image zoran = "Characters/Zoran/[zoran_outfit].png"
    • Yes -> String
    • No -> Go to #5
  5. (Won't use in dialogue or images) Will you use this value later to save something like a label name to jump to?
    • Yes -> String
    • No -> Boolean
  6. (More than two possibilities) Is it possible for two or more of the possible options to occur at the same time? (e.g. the player can own both a cat and a dog, just a cat, just a dog, or neither)
    • Yes -> Booleans (one for each possibility e.g. owns_cat = True, owns_dog = False)
      • (Bonus answer) This often a good place to use lists as well e.g. owned_pets = [] which can become owned_pets = ["cat", "dog"] and you can check if "dog" is in the list to know if the player owns a dog.
    • No -> Go to #7
  7. (Possibilities are mutually exclusive) What would make your code clearer, writing out a whole word or using numbers to represent each possibility? (e.g. the player has three possible shirt types. In your images folder, you have called these player_shirt1.png, player_shirt2.png, and player_shirt3.png. You could have a variable like player_shirt = 1 without loss of clarity.)
    • Whole words -> String
    • Numbers -> Integer

And that's all! Thank you for reading. Feel free to leave a comment or contact me if you have further questions or feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment