32blogby StudioMitsu
renpy7 min read

Ren'Py Choices and Branching: From Menus to Multiple Endings

Learn how to implement choices and branching in Ren'Py 8.5. Covers menu syntax, jump vs call, flag management, conditional choices, and multiple endings.

renpyvisual-novelgame-devbeginner
On this page

What makes a visual novel worth replaying is choice. The story changes based on what the player picks, and that's what keeps them coming back.

This guide covers how to implement choices and branching in Ren'Py 8.5.2, from the basic menu statement to multiple endings.

The menu Statement

The menu statement presents choices to the player and branches based on their selection.

renpy
label start:
    "The road splits in two."

    menu:
        "Which way?"

        "Take the left path":
            "You chose the left path. A forest stretches ahead."

        "Take the right path":
            "You chose the right path. A river comes into view."

    "The journey continues."

The string directly under menu: ("Which way?") is the caption. The strings below it become the choices. Each choice block contains the code that runs when that option is selected.

After the choice block ends, execution continues at the line after the menu ("The journey continues."). Regardless of which option was picked, the flow merges here.

Having a Character Ask

Instead of a caption, you can have a character speak the question.

renpy
define e = Character("Eileen")

label start:
    menu:
        e "Which do you prefer?"

        "Tea":
            e "Tea it is."

        "Coffee":
            e "Coffee it is."

Writing e "Which do you prefer?" displays it as Eileen's dialogue, then shows the choices.

jump vs call

When choice blocks get long, move the logic to separate labels with jump or call. These two look similar but differ in one critical way: whether execution returns.

jump — One Way

renpy
label start:
    menu:
        "Which way?"

        "The forest":
            jump forest_route

        "The town":
            jump town_route

label forest_route:
    "You ventured deep into the forest."
    jump chapter_2

label town_route:
    "You arrived at a bustling town."
    jump chapter_2

label chapter_2:
    "Chapter 2 begins."

jump moves execution to the specified label and never comes back. Use it for route transitions and endings.

call — Go and Return

renpy
label start:
    "Time to prepare for the adventure."

    call shop

    "Shopping done. Time to set off."

label shop:
    "What will you buy?"

    menu:
        "Buy a sword":
            $ has_sword = True
        "Buy a shield":
            $ has_shield = True

    return

call moves execution to the specified label, and return brings it back to the caller. Use it for shared scenes (shops, minigames, flashbacks) that you want to invoke from multiple places.

Comparison

jumpcall
ReturnsNoYes, via return
Use forRoute branches, endingsShared scenes, subroutines
Call stackDoes not pushPushes (consumed by return)

Managing Branches with Flags

Save choice results in variables (flags) to reference them in later scenes.

Declaring Flags

renpy
# Declare with default (saved to save files)
default met_fairy = False
default affection = 0

label start:
    menu:
        "Talk to the fairy?"

        "Talk to her":
            $ met_fairy = True
            $ affection += 10
            "You became friends with the fairy."

        "Ignore her":
            "You walked past."

Always declare flags with default. Variables declared with default are saved to save files and correctly restored on load.

Branching on Flags

renpy
label chapter_2:
    if met_fairy:
        "The fairy from before appeared."
        $ affection += 5
    else:
        "An unfamiliar fairy appeared."

    if affection >= 10:
        "The fairy smiled at you."
    else:
        "The fairy is cautious."

Use if/elif/else to switch text and events based on variable values.

Conditional Choices

Add an if condition to a choice itself to show it only when the condition is met.

renpy
default has_key = False

label locked_door:
    "A locked door stands before you."

    menu:
        "Use the key" if has_key:
            "The door opened."
            jump secret_room

        "Turn back":
            "You decided to find another way."

When has_key is False, "Use the key" doesn't appear in the menu. The player only sees "Turn back."

Showing Disabled Choices as Greyed Out

Instead of hiding unmet choices, you can grey them out to signal "this exists but you can't pick it yet."

renpy
init python:
    config.menu_include_disabled = True

label locked_door:
    menu:
        "Use the key" if has_key:
            "The door opened."

        "Turn back":
            "You decided to find another way."

Setting config.menu_include_disabled = True displays unmet choices in a disabled (greyed-out) state. This hints to the player that different conditions could unlock them.

Excluding Already-Chosen Options

Use set to automatically remove choices that the player has already picked.

renpy
default explored = set()

label explore:
    "What do you examine?"

    menu:
        set explored

        "The desk":
            "You found a letter."

        "The bookshelf":
            "You found an old diary."

        "The window":
            "You saw someone's shadow outside."

    if len(explored) < 3:
        jump explore
    else:
        "You've examined everything."

Implementing Multiple Endings

Combine everything above to build multiple endings.

Point-Based: Branch by Affection

Add or subtract points based on player choices, then decide the ending by the final value.

renpy
default affection = 0

label start:
    "You met Eileen."

    menu:
        "Greet her":
            $ affection += 10
            "Eileen looks happy."

        "Walk past":
            "Eileen looks lonely."

    "The journey continued for a while."

    menu:
        "Help Eileen":
            $ affection += 20
            "Eileen's eyes lit up."

        "Look the other way":
            $ affection -= 10
            "Eileen looked disappointed."

    jump ending

label ending:
    if affection >= 20:
        jump good_ending
    elif affection >= 0:
        jump normal_ending
    else:
        jump bad_ending

label good_ending:
    "You and Eileen forged an unbreakable bond."
    "GOOD END"
    return

label normal_ending:
    "You and Eileen remained friends."
    "NORMAL END"
    return

label bad_ending:
    "Eileen left without a word."
    "BAD END"
    return

Flag-Based: Branch by Event Combinations

Instead of points, decide the ending based on whether specific events occurred.

renpy
default saved_cat = False
default found_letter = False

label start:
    menu:
        "Save the cat":
            $ saved_cat = True
        "Walk past":
            pass

    menu:
        "Read the letter":
            $ found_letter = True
        "Ignore the letter":
            pass

    jump ending

label ending:
    if saved_cat and found_letter:
        "You uncovered the whole truth."
        "TRUE END"
    elif saved_cat:
        "The cat came back to return the favor."
        "CAT END"
    elif found_letter:
        "You solved the mystery of the letter."
        "LETTER END"
    else:
        "You found nothing."
        "NORMAL END"
    return

In practice, most games combine both approaches — use points (affection) to determine the main route, and flags for smaller variations within each route.

Wrapping Up

What we covered:

  • menu statement: Basic choice syntax. Use captions or character dialogue to pose questions
  • jump vs call: jump is one-way, call returns via return. Use call for shared scenes
  • Flag management: Declare variables with default to save choice results
  • Conditional choices: Control visibility with if conditions. config.menu_include_disabled for greyed-out display
  • Multiple endings: Point-based (affection) and flag-based (event tracking) patterns

For Ren'Py basics, see the getting started guide. For variable management, see the stat management guide. For UI, see the screen language guide.

Official resources: