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.
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.
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
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
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
jump | call | |
|---|---|---|
| Returns | No | Yes, via return |
| Use for | Route branches, endings | Shared scenes, subroutines |
| Call stack | Does not push | Pushes (consumed by return) |
Managing Branches with Flags
Save choice results in variables (flags) to reference them in later scenes.
Declaring Flags
# 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
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.
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."
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.
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.
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.
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:
jumpis one-way,callreturns viareturn. Usecallfor shared scenes - Flag management: Declare variables with
defaultto save choice results - Conditional choices: Control visibility with
ifconditions.config.menu_include_disabledfor 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:
- In-Game Menus — menu statement reference
- Labels & Control Flow — label/jump/call details
- Conditional Statements — if/elif/else details
- r/RenPy — community