Demystifying Ren’Py

Visual Novels

visual novel is an interactive game style introduced in Japan in the early 1990s, featuring mostly static graphics, most often using anime-style art or occasionally live-action stills (and sometimes video footage). As the name might suggest, they resemble mixed-media novels.

Ren'Py is a cross-platform "Visual Novel" engine that runs on Python (hence the "Py"). It comes pre-packaged with a lot of the basic functionality of a standard visual novel: a menu system, dialog engine, and so on and so forth. It's completely free, even to sell and distribute the games you make with it, and you can build games for multiple platforms including mobile ones. It also uses a very simple scripting system that sort of looks like a screenplay, letting non-coders get started building out their games very quickly. You can also insert your own Python-based code to extend the functionality of the engine.

To see a cool game made with Ren'Py, check out Digital: A Love Story by Christine Love.

Popular games made in "visual novel" style are the series of Phoenix Wright: Ace Attorney, many Japanese interactive love stories, and the Professor Layton game series.

To get started, check out their quickstart guide at 

How to install the tools:


  1. You only need to download one of these three files.
  2. Each contains the full Ren'Py software development kit, with everything needed to develop Ren'Py games for Windows XP and up, Mac OS X 10.6 and up, and Linux x86/x86_64.
  3. The development environment contains the files needed to produce games for all three platforms, the Ren'Py tutorial, and "The Question", an example game.
  4. Ren'Py is free to download and use for commercial and non-commercial purposes. It may be modified and distributed under the terms of its license.

Using the Ren'Py Quickstart:

Ren'Py Quickstart

What is Ren'Py?

What is Ren'Py?

Sample Code

Writing Dialog

# A label statement names a place in the program.
# In this case, we're naming a place "start." 
# The "start" label marks the place a game begins running.

label start:

 "Wow, it's really dark in here."

# A say statement begins and ends with a double-quote.
# To include a double-quote (") in a string, type \".
# A single string on a line by itself is said by the narrator.

 "Lucy" "Better watch out."
 l "You don't want to be eaten by a Grue."

# Two strings separated by a space means the first string is
# used as a character's name, and the second is their message.
# This 2-argument form of the say statement is used for dialog,
# where a character is speaking out loud.

# Preserve the spacing before lines, aka indentation. 
# Indentation is used to group lines of script into blocks.

# Here's an example Character definition:

define l = Character(_("Lucy"), color="#ffcccc")

# Begins with the word "define," signifying we are defining something.
# Define is followed by a short name for the character, like "l."
# We'll be able to use that short name when writing dialog.
# This is followed by an = and the thing we're defining: a Character.
# Short names are case-sensitive. Capital L is a different name
# from lower-case l, so you'll need to be careful about that.

Adding Images

# Before showing images, first put the image files in the game directory.
# They should be placed at the start of the file, with no indentation.

image bg cave = "cave.jpg"
image lucy happy = "lucy_happy.png"
image lucy mad = "lucy_mad.png"

# Here are some sample image definitions. They should be placed at 
# the start of the file, without any indentation.
# Keyword image, followed by an image name (a space-separated list of words).
# The first word in the image name is the image tag. 
# For the first image, the tag is "bg," and for others, it's "lucy."

label start:

 scene bg cave
 show lucy happy

 l "Now that the lights are on..."

 show lucy mad at right

 l "What's the big idea?!"

# Two new statements: the scene and show statement.
# The scene statement clears the screen, then adds a background image.
# The show statement adds a background image on top of all other images.
# If there was already an image with the same tag, the new image
# would be used to replace the old one.
# The second show statement has an at clause, which gives a location.
# Common locations are left, right, and center, but you can define more.

 show logo base at logopos behind eileen
# This shows an image named logo base, shown at a user-defined position.
# It is also specified to be shown behind another image, eileen.
 hide logo
# This is the hide statement, which hides the image with the given tag.
# The show statement replaces an image.
# The scene statement clears the screen.
# The hide statement is used when something leaves before scene is over.


# Here's some sample code showing how we include transitions in-game.

 scene bg whitehouse
 with Dissolve(.5)

 pause .5

 scene bg washington
 show eileen happy
 with Dissolve(.5)

# This uses the with statement. The with statement causes the scene to 
# transition from the last things shown to the things currently shown.
# It takes a transition as an argument (in this case, Dissolve).
# This transition takes an argument of an amount of time.
# In this case, each transition takes half a second (.5).

define slowdissolve = Dissolve(1.0)

# Define a short name for a transition using a define statement. 

 scene bg whitehouse
 with slowdissolve

 scene bg washington
 show eileen happy
 with slowdissolve

# Ren'Py defines some transitions for you, like Dissolve, Fade, & Move.

Music and Sound

# Ren'Py breaks up sound into channels. The channel a sound is played on
# determines if the sound loops or if it's saved & restored with the game.
# When a sound is played on the music channel, it is looped & saved.
# When played on the sound channel, it is played once and then stopped.
# Ren'Py supports the Ogg Vorbis, mp3, mp2, and wav file formats.

 play music "sunflower-slow-drag.ogg" fadeout 1
 queue music "sunflower-slow-drag.ogg"

# The play music command replaces the currently playing music, 
# and replaces it with the named filename.
# If you specify the currently playing song, it will restart it.
# The fadeout clause fades out the current music before starting new music. 
# The queue statement also adds music to the named channel, but it waits
# until the currently playing song is finished before playing new music.

 stop music fadeout 1
# The third statement is the stop statement. It stops the music playing
# on a channel. It also takes the fadeout clause. 

 play sound "tower_clock.ogg"
# The sound channel causes it to play only once. 

 queue sound "tower_clock.ogg"
 queue sound "tower_clock.ogg"
 queue sound "tower_clock.ogg"
# You can queue up multiple sounds on the sound channel, 
# but they will only play one at a time.
# Ren'Py has separate mixers for sound, music, and voices.

In-Game Menus and Python

# Menus are introduced by the menu block statement.
# The menu statement takes an indented block, in which
# each line must contain a choice in quotes.
# The choices must end with a colon, as each choice has its
# own block of Ren'Py code run when that choice is selected.

 "Yes, I do.":
  jump choice1_yes
 "No, I don't.":
  jump choice1_no

# Here, each block jumps to a label. While you could put small
# amounts of Ren'Py code inside a menu label, it's probably a 
# best practice to usually jump to a bigger block of code. 
# See three labels here: choice1_yes, choice1_no, choice1_done.
# When the first menu is picked, we jump to choice1_yes,
# which runs 2 lines of script before jumping to choice1_done.

label choice1_yes:
 $ menu_flag = True
 e "While creating a multi-path game can be a bit more work..."
 jump choice1_done

# Picking the second option jumps to choice1_no.
# Lines that start with $ are Python used to set a flag
# based on the user's choice. The flag is named menu_flag.
# menu_flag is set to True orFalse based on the user's choice.
# if statements can be used to test a flag, 
# so the game can remember the user's choices.

label choice1_no:
 $ menu_flag = False
 e "Games without menus are called kinetic novels..."
 jump choice1_done

label choice1_done:
 # ... the game continues here.

# Here's an example that shows how we can test a flag,
# and do different things if it's true or not. 

if menu_flag:
 e "For example, I remember that you plan to use menus"
 e "For example, I remember that you're not using menus"

Screen Positions

There are 2 kinds of numbers to remember: integers and floating point numbers.
Integers are whole numbers. Floating point numbers have decimal points.
100 is an integer. 0.5 is a floating point number, or float for short.
0 is an integer, and 0.0 is a float.

The "origin" is the upper-left corner of the screen. That's where the x position (xpos) and the y position (ypos) are both at zero.
An xpos of 0.5 means half the width across the screen.
Increasing xpos to 1.0 moves us to the right-top border of the screen.
Changing the ypos to 0.5 moves halfway down the screen.
A ypos of 1.0 specifies a position at the bottom of the screen.

The anchor is a spot on a thing being positioned.
xanchor of 0.0, yanchor of 0.0 is the upper-LEFT corner of an image.
xanchor of 1.0, yanchor of 0.0 is the upper-RIGHT corner of an image.
xanchor of 1.0, yanchor of 1.0 is the LOWER-right corner of an image.
xanchor of 0.0, yanchor of 1.0 is the lower-left corner of an image.

To place an image on the screen, we need both the position and anchor.
It's useful to set xpos and xanchor to the same value: xalign.
When we set xalign to 0.0, things are aligned to left center screen.
When we set xalign to 1.0, things are aligned to right center screen.
Set xalign to 0.5 to align an image to the exact center of the screen.
Setting yalign is similar, except along the y-axis.

Animation and Transformation

# Moving images around the screen is called a Transform.
# Animation and Transformation Language (ATL) does this in Ren'Py.
# Animation is when the displayable being shown changes, like below: 

image eileen animated:
 "eileen vhappy"
 pause .5
 "eileen happy"
 pause .5
# (You can only have one repeat statement per block.)
# The first place ATL can be used is as part of an image statement. 
# Instead of a 'displayable,' an image may be defined as ATL code.
# When used this way, be sure ATL includes displayables to actually show.

transform topright:
 xalign 1.0 yalign 0.0

# The 2nd way is through the transform statement. This assigns the ATL
# block of code to a python variable, allowing it to be used in and 
# at clauses and inside other transforms. 

 show logo base:
  xalign .3 yalign .7
  linear 1.0 xalign .7 yalign .3
  linear 1.0 xalign .3 yalign .7
# Thirdly, an ATL block can be used as part of a show statement.

Transformation involves moving or distorting an image. This includes: placing it on the screen, zooming in & out, rotating, and changing its opacity. This can make it disappear and reappear.

image eileen animated twice:
 "eileen vhappy"
 pause .5
 "eileen happy"
 pause .5
 repeat 2
# Writing 'repeat 2' makes the animation loop twice, then stop.

image eileen animated once:
 "eileen vhappy"
 pause .5
 "eileen happy"
# Omitting the repeat statement makes the animation stop after the end.

image bg atl transitions:
 "bg washington" with dissolve
 pause 1.0
 "bg whitehouse" with dissolve
 pause 1.0
# Displayables are replaced instantaneously. We can use a 'with' clause
# to give a transition between displayables. 

transform move_jump:
 xalign 1.0 yalign 0.0
 pause 1.0
 xalign 0.0
 pause 1.0
# This code starts the image off at the top-right of the screen, 
# waits one second, then moves it to the left side, waits 1 sec. Repeat.

The interpolation statement allows you to smoothly vary the value of a transform property, from an old value to a new value.

transform move_slide:
 xalign 1.0 yalign 0.0
 linear 3.0 xalign 0.0
 pause 1.0

It starts off with the name of a time function, in this case, linear. That's followed by an amount of time, in this case, 3 seconds. It ends with a list of properties, each followed by its new value.
The old value of the transform property is the start of the statement. By interpolating the property over time, we change things on the screen.

 show bg band:
  xpos 0 ypos -222 xanchor 0 yanchor 0
  linear 5.0 xpos -435 ypos 0

# Along with xalign and yalign, Ren'Py supports the xpos, ypos, 
# xanchor, and yanchor properties. 
# We can pan around an image by using xpos and ypos to position images
# off of the screen. This usually means giving them negative positions.

 show logo base:
  zoom 1.0
  linear 1.0 zoom 1.5
  linear 1.0 zoom 1.0
# The zoom property lets us scale the displayable by a factor, making it
# bigger and smaller. Zoom should always be greater than 0.5.

 show logo base:
  xzoom 0.75 yzoom 1.25
  linear 1.0 xzoom 1.25 yzoom 0.75
  linear 1.0 xzoom 0.75 yzoom 1.25
# The xzoom and yzoom properties allow the displayable to be scaled 
# in the X and Y directions independently (squashed and stretched).

 show logo base:
  size (300, 450)

# The size property can set a size, in pixels, to scale the displayable.

 show logo base:
  alpha 1.0
  linear 1.0 alpha 0.0
  linear 1.0 alpha 1.0
# The alpha property varies the opacity of a displayable.

 show logo base:
  xpos 0.5 ypos 0.5 xanchor 0.5 yanchor 0.5
  rotate 0
  linear 4.0 rotate 360
# The rotate property lets us rotate a displayable.

Since rotation can change the size, you should set x anchor and y anchor to 0.5 when positioning a rotated displayable.

 show bg washington:
  crop (0, 0, 800, 600)
  size (800, 600)
  linear 4.0 crop (350, 300, 400, 300)

# The crop property crops a rectangle out of a displayable, showing only
# part of it. When used together, they can be used to focus in on 
# specific parts of an image. 

 show eileen happy:
  pause 1.25
  pause 1.25
# We can use left, right, and center inside of an ATL block of code.

 show logo base:
  xalign 0.0 yalign 0.0
   linear 1.0 xalign 1.0
   linear 1.0 xalign 0.0
  time 11.5
  linear 0.5 xalign 1.0

The block statement allows you to include a block of ATL code.
Since the repeat statement applies to blocks, this lets you repeat only part of an ATL transform.
The time statement runs after the given number of seconds have elapsed from the start of the block. It will run even if another statement is running, stopping the other statement.
This code will bounce the image back and forth for 11.5 seconds, and then move back to the right side of the screen.

 show logo base:
   linear 1.0 xalign 0.0
   linear 1.0 xalign 1.0
   linear 1.3 yalign 1.0
   linear 1.3 yalign 0.0
# The parallel statement runs two blocks of ATL code at the same time.
# Here, the top block moves the image in the horizontal direction, and
# the bottom block moves it in the vertical direction. 
# Since they're moving at different speeds, it looks like the image
# is bouncing on the screen. 

 show logo base:
   linear 1.0 xalign 0.0
   linear 1.0 xalign 1.0
# The choice statement makes Ren'Py randomly pick a block of ATL code.

This allows you to add some variation as to what Ren'Py shows.


You may want to mix Ren'Py with other forms of gameplay. There are many ways to do this. The first is with the UI functions, which can be used to create powerful button- and menu-based interfaces. These are often enough for many simulation-style games.

We also have two more ways in which Ren'Py can be extended. Both require experience with Python programming, and so aren't for the faint of heart. Renpygame is a library that allows pygame games to be run inside Ren'Py. When using renpygame, Ren'Py steps out of the way and gives you total control over the user's experience. You can get renpygame from the Frameworks page of the Ren'Py website.

If you want to integrate your code with Ren'Py, you can write a user-defined displayable. User-defined displayables are somewhat more limited, but integrate better with the rest of Ren'Py. For example, one could support loading and saving while a user-defined displayable is shown. (Be careful about putting minigames in a visual novel, since not every visual novel player wants to be good at arcade games.)

For help and discussion, check out the Lemma Soft Forums. (

Sarah Sexton | Technical Evangelist | Twitter: @Saelia

Comments (0)

Skip to main content