Welcome to the sponsor-exclusive content for the Ren'Py Patreon. Sponsors like you ensure this page exists. Thank you.

_images/timed-choice-menus-1.jpg

Timed Choice Menus link

I was asked on the Ren’Py Discord how to make a choice menu that times out after a certain amount of time, sending the player to a default label (section of the game) if they can’t make a choice in time. The idea is apparently to provide pressure on the player to make a decision.

I’m a little skeptical about timed choices like these. While they’re fine for literate players, there might be a bit of an accessibility issue here. A player that has trouble with English (or whatever other language the visual novel is written in) might not be able to decide as quickly as a native speaker.

But, I realized that this could be done well by making the timed menus controlled by a preference. Creators can make timed menus enabled by default, but allow players who need unlimited time to make a decision to disable them from the preferences screen. So, I’ll be breaking this tutorial up into two parts - first showing how the choice menu works, and then explaining how to hook up a new game-specific preference.

Changing the Choice Screen link

To start, edit screens.rpy, and replace the choice screen (around line 212) with the following. Please leave the defines and styles intact. If they’re removed, the changed screen code will not function properly. This tutorial assumes you’re using the new Ren’Py GUI.

# How long the player has to make a choice in timeout seconds.
default timeout = 5.0

# The label the player is sent to if they fail to make a choice in the time
# allotted. If None, the timeout is disabled.
default timeout_label = None

# A preference that enables or disables timed choices.
default persistent.timed_choices = True

screen choice(items):
    style_prefix "choice"

    vbox:
        for i in items:
            textbutton i.caption action i.action

    if (timeout_label is not None) and persistent.timed_choices:

        bar:
            xalign 0.5
            ypos 400
            xsize 740
            value AnimatedValue(old_value=0.0, value=1.0, range=1.0, delay=timeout)

        timer timeout action Jump(timeout_label)

How it Works link

The first thing we do is set the default values of three new variables we’ll use. The timeout variable determines how long the choice remains on the screen before timing out, in seconds. The timeout_label variable sets the label that the screen jumps to if the timeout expires before a choice is made. And by setting persistent.timed_choices, we create a preference controlling the timer and make it true by default. The default value of true means that the player will encounter timed choices unless they manually toggle the option off from the preferences screen.

The changes to the choice screen itself start with the if statement above. The if statement checks for two things: first, to see if the choice screen is hooked up to a timeout label, and second, to see if the preference for timeouts is enabled. If either of those is missing, then the timeout function gets disabled.

The timeout itself is implemented in two parts: a bar that indicates to the player how much time is remaining, and a timer running in the background that triggers the jump when it runs out.

The position of the bar is controlled by the xalign and ypos properties here, but we can use any position properties that we use elsewhere in Ren’Py to center it and move it on the screen. Since by default a bar grows as wide as possible, we use xsize to set an explicit width for it.

The bar itself is animated by AnimatedValue(). In this example, it takes timeout seconds to grow from 0.0 to 1.0. Since the range is also 1.0, this means that the bar visually grows from empty to full. If you wanted it to shrink instead, you could write:

value AnimatedValue(old_value=1.0, value=0.0, range=1.0, delay=timeout)

And that would cause it to shrink from full to empty.

Finally, the timer itself is fairly simple. It waits for the amount of seconds we provided in our “timeout” variable, and then uses the Jump() action to go to the label we provided.

Usage link

Once set up, this example is fairly easy to use. Immediately before a choice menu, we use a Python statement to set timeout_label:

label attack:

    # Set the label that is jumped to if the player doesn't make a decision.
    $ timeout_label = "attack_hit"

menu:
    "Dodge left.":

        "I dodge left, and it misses me completely."

        jump after_attack

    "Dodge right.":

        "I dodge right, just in time. Still, it grazes the left side of my body. Too close."

        jump after_attack

label attack_hit:

    "I'm paralyzed with fear, and it hits me square in the center of my body."

    "Good thing it was just a spitball."

label after_attack:

    "I'm not going to let him get away with that, though..."

As you can see from this example, if the player avoids making the choice to dodge in either direction before the timer runs out, we send them to a special label where he experiences the consequences, then flow right back into the main story.

To disable the timeout for a menu, set timeout_label to None.:

label godot_menu:

    $ timeout_label = None

menu:

    "Godot hasn't arrived yet."

    "Keep waiting.":

        "..."

    "Get going.":

        "..."

You might wonder why we need to set timeout_label to None if we already have an if statement to check if the choice screen is hooked up to a timeout label. The reason is that the timeout_label variable keeps its value once it has been set. If you don’t set it back to None, your menu will still time out and jump to the label last saved in the variable.

We can set the timeout variable in a similar way that we set timeout_label. However, it’s not necessary to do this unless we want different menus to have different timeout lengths.

That’s all for part one. We’ll explore how to hook up a new game-specific preference in the next article!